Improve ability for adjustments to be made between reporting periods

This commit is contained in:
David Bomba 2025-11-17 15:12:01 +11:00
parent 2903ac539c
commit 0d991036f4
3 changed files with 109 additions and 22 deletions

View File

@ -19,7 +19,7 @@ class TaxSummary
{
public float $total_taxes; // Tax collected and confirmed (ie. Invoice Paid)
public float $total_paid; // Tax pending collection (Outstanding tax of balance owing)
public string $status;
public string $status; // updated, deleted, cancelled
public float $adjustment;
public function __construct(array $attributes = [])
{

View File

@ -137,11 +137,23 @@ class InvoiceTaxSummary implements ShouldQueue
$todayStart = now()->subHours(15)->timestamp;
$todayEnd = now()->endOfDay()->timestamp;
// Convert company timezone dates to UTC for database query
// $startDate and $endDate are in Y-m-d format (e.g., "2024-01-01")
$timezone = $company->timezone()->name ?? 'UTC';
$startDateUtc = Carbon::createFromFormat('Y-m-d', $startDate, $timezone)
->startOfDay()
->setTimezone('UTC')
->format('Y-m-d H:i:s');
$endDateUtc = Carbon::createFromFormat('Y-m-d', $endDate, $timezone)
->endOfDay()
->setTimezone('UTC')
->format('Y-m-d H:i:s');
Invoice::withTrashed()
->with('payments')
->with('payments',)
->where('company_id', $company->id)
->whereIn('status_id', [2,3,4,5])
->where('is_deleted', 0)
// ->where('is_deleted', 0) I still need to assess deleted invoices, and ensure if there is an entry present, we reverse it!!!
->whereHas('client', function ($query) {
$query->where('is_deleted', false);
})
@ -151,12 +163,13 @@ class InvoiceTaxSummary implements ShouldQueue
$q->where('is_flagged', false);
});
})
->whereBetween('date', [$startDate, $endDate])
->whereDoesntHave('transaction_events', function ($query) use ($todayStart, $todayEnd) {
$query->where('timestamp', '>=', $todayStart)
->where('timestamp', '<=', $todayEnd)
->where('event_id', TransactionEvent::INVOICE_UPDATED);
})
// ->whereBetween('date', [$startDate, $endDate])
// ->whereDoesntHave('transaction_events', function ($query) use ($todayStart, $todayEnd) {
// $query->where('timestamp', '>=', $todayStart)
// ->where('timestamp', '<=', $todayEnd)
// ->where('event_id', TransactionEvent::INVOICE_UPDATED);
// })
->whereBetween('updated_at', [$startDateUtc, $endDateUtc])
->cursor()
->each(function (Invoice $invoice) {
(new InvoiceTransactionEventEntry())->run($invoice);
@ -177,10 +190,10 @@ class InvoiceTaxSummary implements ShouldQueue
$q->where('is_flagged', false);
});
})
->whereHas('payments', function ($query) use ($startDate, $endDate) {
$query->whereHas('paymentables', function ($subQuery) use ($startDate, $endDate) {
->whereHas('payments', function ($query) use ($startDateUtc, $endDateUtc) {
$query->whereHas('paymentables', function ($subQuery) use ($startDateUtc, $endDateUtc) {
$subQuery->where('paymentable_type', Invoice::class)
->whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']);
->whereBetween('created_at', [$startDateUtc, $endDateUtc]);
});
})
->whereDoesntHave('transaction_events', function ($q) use ($todayStart, $todayEnd) {

View File

@ -12,18 +12,11 @@
namespace App\Listeners\Invoice;
use App\Utils\BcMath;
use App\Models\Invoice;
use App\Models\Activity;
use App\Models\TransactionEvent;
use Illuminate\Support\Collection;
use App\DataMapper\TaxReport\TaxDetail;
use App\DataMapper\TaxReport\TaxReport;
use App\DataMapper\TaxReport\TaxSummary;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\DataMapper\TransactionEventMetadata;
use Illuminate\Queue\Middleware\WithoutOverlapping;
class InvoiceTransactionEventEntry
{
@ -31,6 +24,8 @@ class InvoiceTransactionEventEntry
private float $paid_ratio;
private string $entry_type = 'updated';
/**
* Handle the event.
*
@ -44,7 +39,37 @@ class InvoiceTransactionEventEntry
$this->setPaidRatio($invoice);
$period = $force_period ?? now()->endOfMonth()->format('Y-m-d');
$event = $invoice->transaction_events()
->where('event_id', TransactionEvent::INVOICE_UPDATED)
->orderBy('timestamp', 'desc')
->first();
if($event){
$this->entry_type = 'delta';
if($invoice->is_deleted && $event->metadata->tax_report->tax_summary->status == 'deleted'){
// Invoice was previously deleted, and is still deleted... return early!!
return;
}
else if(in_array($invoice->status_id,[Invoice::STATUS_CANCELLED]) && $event->metadata->tax_report->tax_summary->status == 'cancelled'){
// Invoice was previously cancelled, and is still cancelled... return early!!
return;
}
else if (!$invoice->is_deleted && $event->metadata->tax_report->tax_summary->status == 'deleted'){
//restored invoice must be reported!!!! _do not return early!!
$this->entry_type = 'restored';
}
/** If the invoice hasn't changed its state... return early!! */
else if(BcMath::comp($invoice->amount, $event->invoice_amount) == 0){
return;
}
}
//Long running tasks may spill over into the next day therefore month!
$period = $force_period ?? now()->endOfMonth()->subHours(5)->format('Y-m-d');
$this->payments = $invoice->payments->flatMap(function ($payment) {
return $payment->invoices()->get()->map(function ($invoice) use ($payment) {
@ -91,6 +116,53 @@ class InvoiceTransactionEventEntry
{
return round($amount * $this->paid_ratio, 2);
}
/**
* calculateDeltaMetaData
*
* Calculates the differential between this period and the previous period.
*
* @param mixed $invoice
*
*/
private function calculateDeltaMetaData($invoice)
{
$calc = $invoice->calc();
$details = [];
$taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray());
$previous_transaction_event = TransactionEvent::where('event_id', TransactionEvent::INVOICE_UPDATED)
->where('invoice_id', $invoice->id)
->orderBy('timestamp', 'desc')
->first();
foreach ($taxes as $tax) {
$tax_detail = [
'tax_name' => $tax['name'],
'tax_rate' => $tax['tax_rate'],
'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(),
'tax_amount' => $this->calculateRatio($tax['total']),
'tax_amount_paid' => $this->calculateRatio($tax['total']),
'tax_amount_remaining' => 0,
];
$details[] = $tax_detail;
}
return new TransactionEventMetadata([
'tax_report' => [
'tax_details' => $details,
'payment_history' => $this->payments->toArray() ?? [], //@phpstan-ignore-line
'tax_summary' => [
'total_taxes' => $invoice->total_taxes,
'total_paid' => $this->getTotalTaxPaid($invoice),
'status' => 'updated',
'adjustment' => round($invoice->amount - $previous_transaction_event->invoice_amount,2)
],
],
]);
}
/**
* Existing tax details are not deleted, but pending taxes are set to 0
@ -164,7 +236,7 @@ class InvoiceTransactionEventEntry
'payment_history' => $this->payments->toArray(),
'tax_summary' => [
'total_taxes' => $invoice->total_taxes,
'total_paid' => $this->getTotalTaxPaid($invoice),0,
'total_paid' => $this->getTotalTaxPaid($invoice),
'status' => 'deleted',
],
],
@ -179,6 +251,8 @@ class InvoiceTransactionEventEntry
return $this->getCancelledMetaData($invoice);
} elseif ($invoice->is_deleted) {
return $this->getDeletedMetaData($invoice);
} elseif ($this->entry_type == 'delta') {
return $this->calculateDeltaMetaData($invoice);
}
$calc = $invoice->calc();