Tax Period Reports
This commit is contained in:
parent
ef36f8d1e4
commit
739ef74f81
|
|
@ -53,16 +53,6 @@ class PreviewReport implements ShouldQueue
|
||||||
Cache::put($this->hash, base64_encode($report), 60 * 60);
|
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.
|
* Handle a job failure.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -61,7 +61,7 @@ class TaxPeriodReport extends BaseExport
|
||||||
|
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
|
nlog($this->input);
|
||||||
MultiDB::setDb($this->company->db);
|
MultiDB::setDb($this->company->db);
|
||||||
App::forgetInstance('translator');
|
App::forgetInstance('translator');
|
||||||
App::setLocale($this->company->locale());
|
App::setLocale($this->company->locale());
|
||||||
|
|
@ -97,16 +97,20 @@ class TaxPeriodReport extends BaseExport
|
||||||
*/
|
*/
|
||||||
private function initializeData(): self
|
private function initializeData(): self
|
||||||
{
|
{
|
||||||
Invoice::withTrashed()
|
$q = Invoice::withTrashed()
|
||||||
->where('company_id', $this->company->id)
|
->where('company_id', $this->company->id)
|
||||||
->where('is_deleted', 0)
|
->where('is_deleted', 0)
|
||||||
->whereIn('status_id', [2,3,4,5])
|
->whereIn('status_id', [2,3,4,5])
|
||||||
->whereBetween('date', ['1970-01-01', now()->subMonth()->endOfMonth()->format('Y-m-d')])
|
->whereBetween('date', ['1970-01-01', now()->subMonth()->endOfMonth()->format('Y-m-d')])
|
||||||
->whereDoesntHave('transaction_events')
|
->whereDoesntHave('transaction_events');
|
||||||
->cursor()
|
|
||||||
|
nlog($q->count(). " records to update");
|
||||||
|
|
||||||
|
$q->cursor()
|
||||||
->each(function($invoice){
|
->each(function($invoice){
|
||||||
|
|
||||||
if($invoice->status_id == Invoice::STATUS_SENT){
|
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'));
|
(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])){
|
elseif(in_array($invoice->status_id, [Invoice::STATUS_PAID, Invoice::STATUS_PARTIAL])){
|
||||||
|
|
@ -121,10 +125,14 @@ class TaxPeriodReport extends BaseExport
|
||||||
->map(function ($group) {
|
->map(function ($group) {
|
||||||
return $group->first();
|
return $group->first();
|
||||||
})->each(function ($pp){
|
})->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'));
|
(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;
|
return $this;
|
||||||
|
|
@ -132,6 +140,8 @@ class TaxPeriodReport extends BaseExport
|
||||||
|
|
||||||
private function resolveQuery()
|
private function resolveQuery()
|
||||||
{
|
{
|
||||||
|
nlog($this->start_date. " - ".$this->end_date);
|
||||||
|
nlog($this->company->id);
|
||||||
$query = Invoice::query()
|
$query = Invoice::query()
|
||||||
->withTrashed()
|
->withTrashed()
|
||||||
->with('transaction_events')
|
->with('transaction_events')
|
||||||
|
|
@ -180,15 +190,19 @@ class TaxPeriodReport extends BaseExport
|
||||||
case 'last_month':
|
case 'last_month':
|
||||||
$this->start_date = now()->startOfMonth()->subMonth()->format('Y-m-d');
|
$this->start_date = now()->startOfMonth()->subMonth()->format('Y-m-d');
|
||||||
$this->end_date = now()->startOfMonth()->subMonth()->endOfMonth()->format('Y-m-d');
|
$this->end_date = now()->startOfMonth()->subMonth()->endOfMonth()->format('Y-m-d');
|
||||||
|
break;
|
||||||
case 'this_quarter':
|
case 'this_quarter':
|
||||||
$this->start_date = (new \Carbon\Carbon('0 months'))->startOfQuarter()->format('Y-m-d');
|
$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');
|
$this->end_date = (new \Carbon\Carbon('0 months'))->endOfQuarter()->format('Y-m-d');
|
||||||
|
break;
|
||||||
case 'last_quarter':
|
case 'last_quarter':
|
||||||
$this->start_date = (new \Carbon\Carbon('-3 months'))->startOfQuarter()->format('Y-m-d');
|
$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');
|
$this->end_date = (new \Carbon\Carbon('-3 months'))->endOfQuarter()->format('Y-m-d');
|
||||||
|
break;
|
||||||
case 'last365_days':
|
case 'last365_days':
|
||||||
$this->start_date = now()->startOfDay()->subDays(365)->format('Y-m-d');
|
$this->start_date = now()->startOfDay()->subDays(365)->format('Y-m-d');
|
||||||
$this->end_date = now()->startOfDay()->format('Y-m-d');
|
$this->end_date = now()->startOfDay()->format('Y-m-d');
|
||||||
|
break;
|
||||||
case 'this_year':
|
case 'this_year':
|
||||||
|
|
||||||
$first_month_of_year = $this->company->first_month_of_year ?? 1;
|
$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->start_date = $fin_year_start->format('Y-m-d');
|
||||||
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
|
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
|
||||||
|
break;
|
||||||
case 'last_year':
|
case 'last_year':
|
||||||
|
|
||||||
$first_month_of_year = $this->company->first_month_of_year ?? 1;
|
$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->start_date = $fin_year_start->format('Y-m-d');
|
||||||
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
|
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
|
||||||
|
|
||||||
|
break;
|
||||||
case 'custom':
|
case 'custom':
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -224,6 +241,7 @@ class TaxPeriodReport extends BaseExport
|
||||||
|
|
||||||
$this->start_date = $custom_start_date->format('Y-m-d');
|
$this->start_date = $custom_start_date->format('Y-m-d');
|
||||||
$this->end_date = $custom_end_date->format('Y-m-d');
|
$this->end_date = $custom_end_date->format('Y-m-d');
|
||||||
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
default:
|
default:
|
||||||
$this->start_date = now()->startOfYear()->format('Y-m-d');
|
$this->start_date = now()->startOfYear()->format('Y-m-d');
|
||||||
|
|
@ -310,6 +328,7 @@ class TaxPeriodReport extends BaseExport
|
||||||
|
|
||||||
$query = $this->resolveQuery();
|
$query = $this->resolveQuery();
|
||||||
|
|
||||||
|
nlog($query->count(). "records to iterate");
|
||||||
$this->data['invoices'] = [];
|
$this->data['invoices'] = [];
|
||||||
$this->data['invoices'][] =
|
$this->data['invoices'][] =
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue