Tax Period Reports

This commit is contained in:
David Bomba 2025-08-06 14:33:00 +10:00
parent ef36f8d1e4
commit 739ef74f81
3 changed files with 248 additions and 14 deletions

View File

@ -53,16 +53,6 @@ class PreviewReport implements ShouldQueue
Cache::put($this->hash, base64_encode($report), 60 * 60);
}
// public function middleware()
// {
// return [
// (new WithoutOverlapping("report-{$this->company->company_key}-{$this->report_class}"))
// ->releaseAfter(60)
// ->expireAfter(60) // 5 minutes
// ->dontRelease(), // This prevents the job from being marked as a "release" which counts towards attempts
// ];
// }
/**
* Handle a job failure.
*/

View File

@ -0,0 +1,225 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\Invoice;
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 InvoiceTransactionEventEntryCash
{
private Collection $payments;
private float $paid_ratio;
/**
* Handle the event.
*
*/
public function run($invoice, $start_date, $end_date)
{
$this->setPaidRatio($invoice);
$this->payments = $invoice->payments->flatMap(function ($payment) use ($start_date, $end_date) {
return $payment->invoices()->get()->map(function ($invoice) use ($payment) {
return [
'number' => $payment->number,
'amount' => $invoice->pivot->amount,
'refunded' => $invoice->pivot->refunded,
'date' => $invoice->pivot->created_at->format('Y-m-d'),
];
})->filter(function ($payment) use ($start_date, $end_date) {
// Filter payments where the pivot created_at is within the date boundaries
return \Carbon\Carbon::parse($payment['date'])->isBetween($start_date, $end_date);
});
});
TransactionEvent::create([
'invoice_id' => $invoice->id,
'client_id' => $invoice->client_id,
'client_balance' => $invoice->client->balance,
'client_paid_to_date' => $invoice->client->paid_to_date,
'client_credit_balance' => $invoice->client->credit_balance,
'invoice_balance' => $invoice->balance ?? 0,
'invoice_amount' => $invoice->amount ?? 0 ,
'invoice_partial' => $invoice->partial ?? 0,
'invoice_paid_to_date' => $invoice->paid_to_date ?? 0,
'invoice_status' => $invoice->is_deleted ? 7 : $invoice->status_id,
'event_id' => TransactionEvent::PAYMENT_CASH,
'timestamp' => now()->timestamp,
'metadata' => $this->getMetadata($invoice),
'period' => $end_date,
]);
}
private function setPaidRatio(Invoice $invoice): self
{
if ($invoice->amount == 0) {
$this->paid_ratio = 0;
return $this;
}
$this->paid_ratio = $invoice->paid_to_date / $invoice->amount;
return $this;
}
private function calculateRatio(float $amount): float
{
return round($amount * $this->paid_ratio, 2);
}
/**
* Existing tax details are not deleted, but pending taxes are set to 0
*
* @param mixed $invoice
*/
private function getCancelledMetaData($invoice)
{
$calc = $invoice->calc();
$details = [];
$taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray());
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(),
'tax_summary' => [
'total_taxes' => $invoice->total_taxes,
'total_paid' => $this->getTotalTaxPaid($invoice),
'status' => 'cancelled',
],
],
]);
}
/**
* Set all tax details to 0
*
* @param mixed $invoice
*/
private function getDeletedMetaData($invoice)
{
$calc = $invoice->calc();
$details = [];
$taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray());
foreach ($taxes as $tax) {
$tax_detail = [
'tax_name' => $tax['name'],
'tax_rate' => $tax['tax_rate'],
'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(),
'tax_amount' => $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(),
'tax_summary' => [
'total_taxes' => $invoice->total_taxes,
'total_paid' => $this->getTotalTaxPaid($invoice),0,
'status' => 'deleted',
],
],
]);
}
private function getMetadata($invoice)
{
// if ($invoice->status_id == Invoice::STATUS_CANCELLED) {
// return $this->getCancelledMetaData($invoice);
// } elseif ($invoice->is_deleted) {
// return $this->getDeletedMetaData($invoice);
// }
$calc = $invoice->calc();
$details = [];
$taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray());
foreach ($taxes as $tax) {
$tax_detail = [
'tax_name' => $tax['name'],
'tax_rate' => $tax['tax_rate'],
'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(),
'tax_amount' => $tax['total'],
'tax_amount_paid' => $this->calculateRatio($tax['total']),
'tax_amount_remaining' => $tax['total'] - $this->calculateRatio($tax['total']),
];
$details[] = $tax_detail;
}
return new TransactionEventMetadata([
'tax_report' => [
'tax_details' => $details,
'payment_history' => $this->payments->toArray(),
'tax_summary' => [
'total_taxes' => $invoice->total_taxes,
'total_paid' => $this->getTotalTaxPaid($invoice),
'status' => 'updated',
],
],
]);
}
private function getTotalTaxPaid($invoice)
{
if ($invoice->amount == 0) {
return 0;
}
$total_paid = $this->payments->sum('amount') - $this->payments->sum('refunded');
return round($invoice->total_taxes * ($total_paid / $invoice->amount), 2);
}
}

View File

@ -61,7 +61,7 @@ class TaxPeriodReport extends BaseExport
public function run()
{
nlog($this->input);
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
@ -97,16 +97,20 @@ class TaxPeriodReport extends BaseExport
*/
private function initializeData(): self
{
Invoice::withTrashed()
$q = Invoice::withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [2,3,4,5])
->whereBetween('date', ['1970-01-01', now()->subMonth()->endOfMonth()->format('Y-m-d')])
->whereDoesntHave('transaction_events')
->cursor()
->whereDoesntHave('transaction_events');
nlog($q->count(). " records to update");
$q->cursor()
->each(function($invoice){
if($invoice->status_id == Invoice::STATUS_SENT){
nlog($invoice->id. " - ".$invoice->number);
(new InvoiceTransactionEventEntry())->run($invoice, \Carbon\Carbon::parse($invoice->date)->endOfMonth()->format('Y-m-d'));
}
elseif(in_array($invoice->status_id, [Invoice::STATUS_PAID, Invoice::STATUS_PARTIAL])){
@ -121,10 +125,14 @@ class TaxPeriodReport extends BaseExport
->map(function ($group) {
return $group->first();
})->each(function ($pp){
nlog($pp->paymentable->id. " - Paid Updater");
(new InvoiceTransactionEventEntryCash())->run($pp->paymentable, \Carbon\Carbon::parse($pp->created_at)->startOfMonth()->format('Y-m-d'), \Carbon\Carbon::parse($pp->created_at)->endOfMonth()->format('Y-m-d'));
});
}
else {
nlog($invoice->id. " - ".$invoice->status_id. " NOT PROCESSED");
}
});
return $this;
@ -132,6 +140,8 @@ class TaxPeriodReport extends BaseExport
private function resolveQuery()
{
nlog($this->start_date. " - ".$this->end_date);
nlog($this->company->id);
$query = Invoice::query()
->withTrashed()
->with('transaction_events')
@ -180,15 +190,19 @@ class TaxPeriodReport extends BaseExport
case 'last_month':
$this->start_date = now()->startOfMonth()->subMonth()->format('Y-m-d');
$this->end_date = now()->startOfMonth()->subMonth()->endOfMonth()->format('Y-m-d');
break;
case 'this_quarter':
$this->start_date = (new \Carbon\Carbon('0 months'))->startOfQuarter()->format('Y-m-d');
$this->end_date = (new \Carbon\Carbon('0 months'))->endOfQuarter()->format('Y-m-d');
break;
case 'last_quarter':
$this->start_date = (new \Carbon\Carbon('-3 months'))->startOfQuarter()->format('Y-m-d');
$this->end_date = (new \Carbon\Carbon('-3 months'))->endOfQuarter()->format('Y-m-d');
break;
case 'last365_days':
$this->start_date = now()->startOfDay()->subDays(365)->format('Y-m-d');
$this->end_date = now()->startOfDay()->format('Y-m-d');
break;
case 'this_year':
$first_month_of_year = $this->company->first_month_of_year ?? 1;
@ -200,6 +214,7 @@ class TaxPeriodReport extends BaseExport
$this->start_date = $fin_year_start->format('Y-m-d');
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
break;
case 'last_year':
$first_month_of_year = $this->company->first_month_of_year ?? 1;
@ -212,6 +227,8 @@ class TaxPeriodReport extends BaseExport
$this->start_date = $fin_year_start->format('Y-m-d');
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
break;
case 'custom':
try {
@ -224,6 +241,7 @@ class TaxPeriodReport extends BaseExport
$this->start_date = $custom_start_date->format('Y-m-d');
$this->end_date = $custom_end_date->format('Y-m-d');
break;
case 'all':
default:
$this->start_date = now()->startOfYear()->format('Y-m-d');
@ -310,6 +328,7 @@ class TaxPeriodReport extends BaseExport
$query = $this->resolveQuery();
nlog($query->count(). "records to iterate");
$this->data['invoices'] = [];
$this->data['invoices'][] =