You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Core]: The user shall be able to produce a report with total Taxes amount that is due by the company according to the net profit #74
The user shall be able to produce a report with total Taxes amount that is due by the company according to the net profit
Specific:
As a finance user, I want to generate a report showing the total tax amount due based on net profit so that I can accurately determine the company's tax obligations.
Measurable:
The tax report will accurately calculate and display the total tax amount due to within a precision of two decimal places.
Achievable:
The system has access to the required net profit data and tax calculation formulas, making report generation feasible.
Relevant:
This feature directly supports compliance with tax regulations and accurate financial reporting.
Time-bound:
The tax report generation feature will be fully implemented and tested within the next sprint, ending in two weeks.
### Arrange:// 1. Create a mock NetProfitRepository that returns a fixed net profit amount.// 2. Define a TaxCalculator class (or interface) and create a mock TaxCalculator that returns a specific tax amount based on the mocked net profit.// 3. Instantiate the TaxReportGenerator class (the class responsible for generating the report) with the mocked NetProfitRepository and TaxCalculator.### Act:// 1. Call the `generate()` method on the TaxReportGenerator instance. This method should use the mocked NetProfitRepository to retrieve the net profit, the mocked TaxCalculator to calculate the tax, and format the output.### Assert:// 1. Assert that the `generate()` method returns a string.// 2. Assert that the returned string contains the correct formatted net profit amount, formatted to two decimal places.// 3. Assert that the returned string contains the correct formatted tax amount, formatted to two decimal places.// 4. Assert that the returned string contains the expected labels for "Net Profit" and "Tax Amount Due".
Let a company generate a report showing tax due, calculated from net profit (income minus expenses) over a period, using a configurable tax rate. Current state: the issue body's Arrange/Act/Assert block references a NetProfitRepository, a TaxCalculator, and a TaxReportGenerator — none of these classes exist in the codebase, so that part of the body is boilerplate and is disregarded. What does exist: Modules\Core\Models\TaxRate and Modules\Core\Enums\TaxRateType (exclusive/inclusive/zero/exempt) — but these model sales tax (VAT) behaviour on invoice line items, not a corporate/income tax rate applied to net profit. There is currently no concept anywhere of a company-level income tax rate. Income data is available via Payment (payment_amount, paid_at) and Invoice (invoice_total); expense data via Modules\Expenses\Models\Expense (expense_amount, expensed_at). This is a distinct feature from #73 (VAT report) even though both are tax reports — #73 sums existing per-invoice VAT, this one needs a net-profit calculation plus a tax-rate concept that doesn't exist yet.
Measurable
The company gains a configurable income_tax_rate (percentage) setting — there's no equivalent field on Company or Setting today.
NetProfitTaxReportService::generate($company, $from, $to) computes net profit as income (payment_amount sum) minus expenses (expense_amount sum) within the period.
Migration to add a nullable income_tax_rate decimal column to companies (there is no existing field for this — it's distinct from TaxRate, which is per-line-item VAT).
New Modules/Core/Services/NetProfitTaxReportService.php summing Payment::whereBetween('paid_at', [...])->sum('payment_amount') minus Expense::whereBetween('expensed_at', [...])->sum('expense_amount'), both already company-scoped via BelongsToCompany.
Decide and document whether "income" should be payments received or invoiced totals (invoice_total) — these differ for unpaid/overdue invoices, and the issue doesn't specify which; confirm with the reporter before implementing.
Expose the rate as a form field in company settings, and add a Filament page to run the report over a chosen period.
Add PHPUnit coverage in Modules/Core/Tests/Feature/.
Relevant
Net-profit-based tax (corporate/income tax) is a distinct legal obligation from VAT and currently has zero support in the system — not even a place to configure the rate.
Time-bound
Sprint 7 (income definition — cash received vs. invoiced — needs confirming with the reporter before implementation starts).
Activity
nielsdrost7 commentedon Jul 16, 2026
SMART Plan — Tax due on net profit report
Specific
Let a company generate a report showing tax due, calculated from net profit (income minus expenses) over a period, using a configurable tax rate. Current state: the issue body's Arrange/Act/Assert block references a
NetProfitRepository, aTaxCalculator, and aTaxReportGenerator— none of these classes exist in the codebase, so that part of the body is boilerplate and is disregarded. What does exist:Modules\Core\Models\TaxRateandModules\Core\Enums\TaxRateType(exclusive/inclusive/zero/exempt) — but these model sales tax (VAT) behaviour on invoice line items, not a corporate/income tax rate applied to net profit. There is currently no concept anywhere of a company-level income tax rate. Income data is available viaPayment(payment_amount,paid_at) andInvoice(invoice_total); expense data viaModules\Expenses\Models\Expense(expense_amount,expensed_at). This is a distinct feature from #73 (VAT report) even though both are tax reports — #73 sums existing per-invoice VAT, this one needs a net-profit calculation plus a tax-rate concept that doesn't exist yet.Measurable
income_tax_rate(percentage) setting — there's no equivalent field onCompanyorSettingtoday.it_saves_the_configured_income_tax_rate_on_the_company()#[Test] public function it_saves_the_configured_income_tax_rate_on_the_company(): void { /* Arrange */ $this->company->update(['income_tax_rate' => null]); /* Act */ $this->company->update(['income_tax_rate' => 21.5]); /* Assert */ $this->assertSame(21.5, $this->company->fresh()->income_tax_rate); }NetProfitTaxReportService::generate($company, $from, $to)computes net profit as income (payment_amountsum) minus expenses (expense_amountsum) within the period.it_computes_net_profit_as_income_minus_expenses()#[Test] public function it_computes_net_profit_as_income_minus_expenses(): void { /* Arrange */ Payment::factory()->for($this->company)->create(['payment_amount' => 1000, 'paid_at' => now()->subDays(5)]); Expense::factory()->for($this->company)->create(['expense_amount' => 300, 'expensed_at' => now()->subDays(3)]); /* Act */ $report = app(NetProfitTaxReportService::class)->generate($this->company, now()->subMonth(), now()); /* Assert */ $this->assertSame(700.0, $report->netProfit); }income_tax_rateto net profit to compute tax due.it_applies_the_configured_tax_rate_to_net_profit()#[Test] public function it_applies_the_configured_tax_rate_to_net_profit(): void { /* Arrange */ $this->company->update(['income_tax_rate' => 20]); Payment::factory()->for($this->company)->create(['payment_amount' => 1000, 'paid_at' => now()]); /* Act */ $report = app(NetProfitTaxReportService::class)->generate($this->company, now()->subMonth(), now()); /* Assert */ $this->assertSame(200.0, $report->taxDue); }it_reports_zero_tax_due_when_net_profit_is_negative()#[Test] public function it_reports_zero_tax_due_when_net_profit_is_negative(): void { /* Arrange */ $this->company->update(['income_tax_rate' => 20]); Expense::factory()->for($this->company)->create(['expense_amount' => 500, 'expensed_at' => now()]); /* Act */ $report = app(NetProfitTaxReportService::class)->generate($this->company, now()->subMonth(), now()); /* Assert */ $this->assertSame(0.0, $report->taxDue); }it_formats_the_report_totals_to_two_decimal_places()#[Test] public function it_formats_the_report_totals_to_two_decimal_places(): void { /* Arrange */ $this->company->update(['income_tax_rate' => 33.333]); Payment::factory()->for($this->company)->create(['payment_amount' => 100, 'paid_at' => now()]); /* Act */ $report = app(NetProfitTaxReportService::class)->generate($this->company, now()->subMonth(), now()); /* Assert */ $this->assertSame('33.33', number_format($report->taxDue, 2)); }Achievable
income_tax_ratedecimal column tocompanies(there is no existing field for this — it's distinct fromTaxRate, which is per-line-item VAT).Modules/Core/Services/NetProfitTaxReportService.phpsummingPayment::whereBetween('paid_at', [...])->sum('payment_amount')minusExpense::whereBetween('expensed_at', [...])->sum('expense_amount'), both already company-scoped viaBelongsToCompany.invoice_total) — these differ for unpaid/overdue invoices, and the issue doesn't specify which; confirm with the reporter before implementing.Modules/Core/Tests/Feature/.Relevant
Net-profit-based tax (corporate/income tax) is a distinct legal obligation from VAT and currently has zero support in the system — not even a place to configure the rate.
Time-bound
Sprint 7 (income definition — cash received vs. invoiced — needs confirming with the reporter before implementation starts).