Working on tax reports, delta changes and adjustments

This commit is contained in:
David Bomba 2025-11-19 12:51:43 +11:00
parent 340d5e1f6c
commit f3263b9ce5
5 changed files with 362 additions and 24 deletions

View File

@ -29,7 +29,7 @@ class ValidRefundableRequest implements Rule
* @param mixed $value
* @return bool
*/
private $error_msg;
private $error_msg = '';
private $input;
@ -64,7 +64,7 @@ class ValidRefundableRequest implements Rule
$this->checkInvoiceIsPaymentable($request_invoice, $payment);
}
if (strlen($this->error_msg) > 0) {
if (strlen($this->error_msg) > 1) {
return false;
}

View File

@ -53,6 +53,7 @@ class InvoiceTransactionEventEntryCash
});
});
TransactionEvent::create([
'invoice_id' => $invoice->id,
'client_id' => $invoice->client_id,
@ -64,6 +65,9 @@ class InvoiceTransactionEventEntryCash
'invoice_partial' => $invoice->partial ?? 0,
'invoice_paid_to_date' => $invoice->paid_to_date ?? 0,
'invoice_status' => $invoice->is_deleted ? 7 : $invoice->status_id,
'payment_refunded' => $this->payments->sum('refunded'),
'payment_applied' => $this->payments->sum('amount'),
'payment_amount' => $this->payments->sum('amount'),
'event_id' => TransactionEvent::PAYMENT_CASH,
'timestamp' => now()->timestamp,
'metadata' => $this->getMetadata($invoice),
@ -101,8 +105,8 @@ class InvoiceTransactionEventEntryCash
$tax_detail = [
'tax_name' => $tax['name'],
'tax_rate' => $tax['tax_rate'],
'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(),
'tax_amount' => $tax['total'],
'taxable_amount' => ($tax['base_amount'] ?? $calc->getNetSubtotal()) * $this->paid_ratio,
'tax_amount' => $tax['total'] * $this->paid_ratio,
'tax_amount_paid' => $this->calculateRatio($tax['total']),
'tax_amount_remaining' => $tax['total'] - $this->calculateRatio($tax['total']),
];

View File

@ -20,7 +20,6 @@ use App\DataMapper\TransactionEventMetadata;
use App\Libraries\MultiDB;
use App\Models\Payment;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@ -47,7 +46,6 @@ class PaymentTransactionEventEntry implements ShouldQueue
public function handle()
{
try{
$this->runLog();
}
@ -91,6 +89,7 @@ class PaymentTransactionEventEntry implements ShouldQueue
})
->each(function($invoice){
nlog(" I am inserting!!! ");
$this->setPaidRatio($invoice);
//delete any other payment mutations here if this is a delete event, the refunds are redundant in this time period
@ -157,15 +156,18 @@ class PaymentTransactionEventEntry implements ShouldQueue
foreach ($taxes as $tax) {
$base_amount = $tax['base_amount'] ?? $calc->getNetSubtotal();
$tax_detail = [
'tax_name' => $tax['name'],
'tax_rate' => $tax['tax_rate'],
'taxable_amount' => $base_amount,
'tax_amount' => $tax['total'],
'taxable_amount' => $base_amount * $this->paid_ratio,
'tax_amount' => $tax['total'] * $this->paid_ratio,
'tax_amount_paid' => $this->calculateRatio($tax['total']),
'tax_amount_remaining' => round($tax['total'] - $this->calculateRatio($tax['total']), 2),
'taxable_amount_adjustment' => ($base_amount * $this->paid_ratio) - $base_amount,
'tax_amount_adjustment' => ($tax['total'] * $this->paid_ratio) - $tax['total'],
'tax_amount_paid_adjustment' => ($tax['total'] * $this->paid_ratio) - $tax['total'],
'tax_amount_remaining_adjustment' => round($tax['total'] - $this->calculateRatio($tax['total']) - ($tax['total'] * $this->paid_ratio), 2),
];
$details[] = $tax_detail;
}
@ -175,11 +177,11 @@ class PaymentTransactionEventEntry implements ShouldQueue
'tax_details' => $details,
'payment_history' => $this->payments->toArray(),
'tax_summary' => [
'total_taxes' => $invoice->total_taxes,
'total_paid' => $this->getTotalTaxPaid($invoice),
'total_taxes' => round($invoice->total_taxes - $this->getTotalTaxPaid($invoice), 2) * -1,
'total_paid' => 0,
'tax_adjustment' => round($invoice->total_taxes - $this->getTotalTaxPaid($invoice), 2) * -1,
'status' => 'adjustment',
'taxable_amount' => $calc->getNetSubtotal(),
'taxable_amount' => ($calc->getNetSubtotal() * $this->paid_ratio) - $calc->getNetSubtotal(),
'adjustment' => 0,
],
],

View File

@ -90,7 +90,6 @@ class TaxPeriodReport extends BaseExport
*/
public function boot(): self
{
$this->setAccountingType()
->setCurrencyFormat()
->calculateDateRange()
@ -104,6 +103,8 @@ class TaxPeriodReport extends BaseExport
{
$this->cash_accounting = $this->input['is_income_billed'] ? false : true;
nlog("cash_accounting = ");
nlog($this->cash_accounting ? "true" : "false");
return $this;
}
@ -163,17 +164,17 @@ class TaxPeriodReport extends BaseExport
->where('company_id', $this->company->id);
// ->where('is_deleted', 0);
if($this->cash_accounting) //accrual
if($this->cash_accounting) //cash
{
$query->whereIn('status_id', [3,4])
->whereHas('transaction_events', function ($query) {
$query->where('event_id', TransactionEvent::PAYMENT_CASH)
$query->where('event_id', '!=', TransactionEvent::INVOICE_UPDATED)
->whereBetween('period', [$this->start_date, $this->end_date]);
});
}
else //cash
else //accrual
{
$query->whereIn('status_id', [2,3,4,5])
@ -353,7 +354,7 @@ class TaxPeriodReport extends BaseExport
ctrans('texts.invoice_total'),
ctrans('texts.paid'),
ctrans('texts.total_taxes'),
ctrans('texts.tax_paid'),
ctrans('texts.taxable_amount'),
ctrans('texts.notes')
];
@ -420,6 +421,10 @@ class TaxPeriodReport extends BaseExport
->cursor()
->each(function($event) use ($invoice){
// nlog($event->metadata->tax_report->tax_summary->status);
// nlog($event->event_id);
// nlog($event->metadata->toArray());
/** @var TransactionEvent $event */
switch($event->metadata->tax_report->tax_summary->status){
case 'delta':
@ -473,7 +478,8 @@ class TaxPeriodReport extends BaseExport
$invoice->number,
$invoice->date,
$invoice->amount,
$state->metadata->tax_report->payment_history?->sum('amount') ?? 0,
$invoice->paid_to_date,
// $state->metadata->tax_report->payment_history?->sum('amount') ?? 0,
$state->metadata->tax_report->tax_summary->total_taxes,
$state->metadata->tax_report->tax_summary->total_paid,
'payable',
@ -539,7 +545,7 @@ class TaxPeriodReport extends BaseExport
$state->metadata->tax_report->tax_summary->adjustment,
$state->metadata->tax_report->payment_history?->sum('amount') ?? 0,
$state->metadata->tax_report->tax_summary->tax_adjustment,
$state->metadata->tax_report->tax_summary->total_paid,
$state->metadata->tax_report->tax_summary->taxable_amount,
'payable',
$this->is_usa ? $invoice->tax_data->geoState : '',
$this->is_usa ? $invoice->tax_data->stateSalesTax : '',
@ -601,7 +607,8 @@ class TaxPeriodReport extends BaseExport
$invoice->amount,
$state->invoice_paid_to_date,
$state->metadata->tax_report->tax_summary->total_taxes,
$state->metadata->tax_report->tax_summary->adjustment,
$state->invoice_paid_to_date - $invoice->amount,
// $state->metadata->tax_report->tax_summary->adjustment,
'adjustment',
$this->is_usa ? $invoice->tax_data->geoState : '',
$this->is_usa ? $invoice->tax_data->stateSalesTax : '',
@ -649,7 +656,7 @@ class TaxPeriodReport extends BaseExport
$state->invoice_paid_to_date,
$state->metadata->tax_report->payment_history?->sum('amount') ?? 0,
($state->invoice_paid_to_date / $state->invoice_amount) * $state->metadata->tax_report->tax_summary->total_taxes,
$state->metadata->tax_report->tax_summary->total_paid,
$state->metadata->tax_report->tax_summary->taxable_amount,
'payable',
$this->is_usa ? $invoice->tax_data->geoState : '',
$this->is_usa ? $invoice->tax_data->stateSalesTax : '',
@ -696,7 +703,7 @@ class TaxPeriodReport extends BaseExport
$invoice->amount * -1,
$state->metadata->tax_report->payment_history?->sum('amount') * -1,
$state->metadata->tax_report->tax_summary->total_taxes * -1,
$state->metadata->tax_report->tax_summary->total_paid * -1,
$state->metadata->tax_report->tax_summary->taxable_amount * -1,
'deleted',
$this->is_usa ? $invoice->tax_data->geoState : '',
$this->is_usa ? $invoice->tax_data->stateSalesTax : '',

View File

@ -19,11 +19,13 @@ use App\Models\Account;
use App\Models\Company;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use App\Models\TransactionEvent;
use App\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Services\Report\TaxPeriodReport;
use Illuminate\Routing\Middleware\ThrottleRequests;
use App\Listeners\Invoice\InvoiceTransactionEventEntry;
use App\Listeners\Payment\PaymentTransactionEventEntry;
use App\Listeners\Invoice\InvoiceTransactionEventEntryCash;
/**
@ -35,6 +37,8 @@ class TaxPeriodReportTest extends TestCase
public $faker;
private $_token;
protected function setUp(): void
{
parent::setUp();
@ -46,6 +50,7 @@ class TaxPeriodReportTest extends TestCase
);
$this->withoutExceptionHandling();
}
public $company;
@ -112,12 +117,14 @@ class TaxPeriodReportTest extends TestCase
'settings' => null,
]);
$this->_token =\Illuminate\Support\Str::random(64);
$company_token = new \App\Models\CompanyToken();
$company_token->user_id = $this->user->id;
$company_token->company_id = $this->company->id;
$company_token->account_id = $this->account->id;
$company_token->name = 'test token';
$company_token->token = \Illuminate\Support\Str::random(64);
$company_token->token = $this->_token;
$company_token->is_system = true;
$company_token->save();
@ -478,9 +485,327 @@ class TaxPeriodReportTest extends TestCase
$this->assertEquals(-10, $item_report[4]); //adjusted tax amount
}
public function invoiceReportingOverMultiplePeriodsWithCashAccountingCheckAdjustments()
public function testInvoiceReportingOverMultiplePeriodsWithCashAccountingCheckAdjustments()
{
$this->buildData();
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 10, 1)->startOfDay());
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 300;
$item->type_id = 1;
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10;
$line_items[] = $item;
$invoice = Invoice::factory()->create([
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'line_items' => $line_items,
'status_id' => Invoice::STATUS_DRAFT,
'discount' => 0,
'is_amount_discount' => false,
'uses_inclusive_taxes' => false,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'custom_surcharge1' => 0,
'custom_surcharge2' => 0,
'custom_surcharge3' => 0,
'custom_surcharge4' => 0,
'date' => now()->format('Y-m-d'),
'due_date' => now()->addDays(30)->format('Y-m-d'),
]);
$invoice = $invoice->calc()->getInvoice();
$invoice->service()->markSent()->createInvitations()->markPaid()->save();
$invoice = $invoice->fresh();
// (new InvoiceTransactionEventEntry())->run($invoice);
// (new InvoiceTransactionEventEntryCash())->run($invoice, '2025-10-01', '2025-10-31');
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 11, 2)->startOfDay());
$payload = [
'start_date' => '2025-10-01',
'end_date' => '2025-10-31',
'date_range' => 'custom',
'is_income_billed' => true, //accrual
];
$pl = new TaxPeriodReport($this->company, $payload);
$data = $pl->boot()->getData();
$transaction_event = $invoice->transaction_events()
->where('event_id', '!=', TransactionEvent::INVOICE_UPDATED)
->first();
$this->assertNotNull($transaction_event);
$this->assertEquals('2025-10-31', $transaction_event->period->format('Y-m-d'));
$this->assertEquals(330, $transaction_event->invoice_amount);
$this->assertEquals(30, $transaction_event->metadata->tax_report->tax_summary->total_taxes);
$this->assertEquals(330, $transaction_event->invoice_paid_to_date);
}
public function testInvoiceWithRefundAndCashReportsAreCorrect()
{
$this->buildData();
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 10, 1)->startOfDay());
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 300;
$item->type_id = 1;
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10;
$line_items[] = $item;
$invoice = Invoice::factory()->create([
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'line_items' => $line_items,
'status_id' => Invoice::STATUS_DRAFT,
'discount' => 0,
'is_amount_discount' => false,
'uses_inclusive_taxes' => false,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'custom_surcharge1' => 0,
'custom_surcharge2' => 0,
'custom_surcharge3' => 0,
'custom_surcharge4' => 0,
'date' => now()->format('Y-m-d'),
'due_date' => now()->addDays(30)->format('Y-m-d'),
]);
$invoice = $invoice->calc()->getInvoice();
$invoice->service()->markSent()->createInvitations()->markPaid()->save();
$invoice = $invoice->fresh();
$payment = $invoice->payments()->first();
/**
* refund one third of the total invoice amount
*
* this should result in a tax adjustment of -10
* and a reportable taxable_amount adjustment of -100
*
*/
$refund_data = [
'id' => $payment->hashed_id,
'date' => '2025-10-15',
'invoices' => [
[
'invoice_id' => $invoice->hashed_id,
'amount' => 110,
],
]
];
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 10, 15)->startOfDay());
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->_token,
])->postJson('/api/v1/payments/refund', $refund_data);
$response->assertStatus(200);
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 11, 02)->startOfDay());
//cash should have NONE
$payload = [
'start_date' => '2025-10-01',
'end_date' => '2025-10-31',
'date_range' => 'custom',
'is_income_billed' => false, //cash
];
$pl = new TaxPeriodReport($this->company, $payload);
$data = $pl->boot()->getData();
$invoice = $invoice->fresh();
$payment = $invoice->payments()->first();
$te = $invoice->transaction_events()->where('event_id', '!=', TransactionEvent::INVOICE_UPDATED)->get();
// nlog($te->toArray());
$this->assertEquals(110, $invoice->balance);
$this->assertEquals(220, $invoice->paid_to_date);
$this->assertEquals(3, $invoice->status_id);
$this->assertEquals(110, $payment->refunded);
$this->assertEquals(330, $payment->applied);
$this->assertEquals(330, $payment->amount);
$this->assertEquals(110, $te->first()->payment_refunded);
$this->assertEquals(330, $te->first()->payment_applied);
$this->assertEquals(330, $te->first()->payment_amount);
$this->assertEquals(220, $te->first()->invoice_paid_to_date);
$this->assertEquals(110, $te->first()->invoice_balance);
}
public function testInvoiceWithRefundAndCashReportsAreCorrectAcrossReportingPeriods()
{
$this->buildData();
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 10, 1)->startOfDay());
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 300;
$item->type_id = 1;
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10;
$line_items[] = $item;
$invoice = Invoice::factory()->create([
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'line_items' => $line_items,
'status_id' => Invoice::STATUS_DRAFT,
'discount' => 0,
'is_amount_discount' => false,
'uses_inclusive_taxes' => false,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'custom_surcharge1' => 0,
'custom_surcharge2' => 0,
'custom_surcharge3' => 0,
'custom_surcharge4' => 0,
'date' => now()->format('Y-m-d'),
'due_date' => now()->addDays(30)->format('Y-m-d'),
]);
$invoice = $invoice->calc()->getInvoice();
$invoice->service()->markSent()->createInvitations()->markPaid()->save();
$invoice = $invoice->fresh();
$payment = $invoice->payments()->first();
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 11, 02)->startOfDay());
//cash should have NONE
$payload = [
'start_date' => '2025-10-01',
'end_date' => '2025-10-31',
'date_range' => 'custom',
'is_income_billed' => false, //cash
];
$pl = new TaxPeriodReport($this->company, $payload);
$data = $pl->boot()->getData();
/**
* refund one third of the total invoice amount
*
* this should result in a tax adjustment of -10
* and a reportable taxable_amount adjustment of -100
*
*/
$refund_data = [
'id' => $payment->hashed_id,
'date' => '2025-11-02',
'invoices' => [
[
'invoice_id' => $invoice->hashed_id,
'amount' => 110,
],
]
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->_token,
])->postJson('/api/v1/payments/refund', $refund_data);
$response->assertStatus(200);
$invoice = $invoice->fresh();
$payment = $invoice->payments()->first();
(new PaymentTransactionEventEntry($payment, [$invoice->id], $payment->company->db, 0, false))->handle();
$this->travelTo(\Carbon\Carbon::createFromDate(2025, 12, 02)->startOfDay());
$invoice = $invoice->fresh();
nlog($invoice->transaction_events()->where('event_id', 2)->first()->toArray());
//cash should have NONE
$payload = [
'start_date' => '2025-11-01',
'end_date' => '2025-11-30',
'date_range' => 'custom',
'is_income_billed' => false, //cash
];
$pl = new TaxPeriodReport($this->company, $payload);
$data = $pl->boot()->getData();
nlog($data);
$this->assertCount(2, $data['invoices']);
// $invoice = $invoice->fresh();
// $payment = $invoice->payments()->first();
// $te = $invoice->transaction_events()->where('event_id', '!=', TransactionEvent::INVOICE_UPDATED)->get();
// // nlog($te->toArray());
// $this->assertEquals(110, $invoice->balance);
// $this->assertEquals(220, $invoice->paid_to_date);
// $this->assertEquals(3, $invoice->status_id);
// $this->assertEquals(110, $payment->refunded);
// $this->assertEquals(330, $payment->applied);
// $this->assertEquals(330, $payment->amount);
// $this->assertEquals(110, $te->first()->payment_refunded);
// $this->assertEquals(330, $te->first()->payment_applied);
// $this->assertEquals(330, $te->first()->payment_amount);
// $this->assertEquals(220, $te->first()->invoice_paid_to_date);
// $this->assertEquals(110, $te->first()->invoice_balance);
}
//scenarios.