diff --git a/app/Jobs/Cron/InvoiceTaxSummary.php b/app/Jobs/Cron/InvoiceTaxSummary.php index 5b2e7e5bf2..8a0731b4a6 100644 --- a/app/Jobs/Cron/InvoiceTaxSummary.php +++ b/app/Jobs/Cron/InvoiceTaxSummary.php @@ -12,19 +12,21 @@ namespace App\Jobs\Cron; +use Carbon\Carbon; +use App\Models\Company; use App\Models\Invoice; use App\Models\Webhook; -use App\Models\Company; use App\Models\Timezone; use App\Libraries\MultiDB; use Illuminate\Bus\Queueable; use App\Jobs\Entity\EmailEntity; +use App\Models\TransactionEvent; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Carbon\Carbon; use App\Listeners\Invoice\InvoiceTransactionEventEntry; +use App\Listeners\Invoice\InvoiceTransactionEventEntryAccrual; class InvoiceTaxSummary implements ShouldQueue { @@ -51,7 +53,6 @@ class InvoiceTaxSummary implements ShouldQueue $companies = $this->getCompaniesInTimezones($transitioningTimezones); foreach ($companies as $company) { - $this->processCompanyTaxSummary($company); } } @@ -64,6 +65,7 @@ class InvoiceTaxSummary implements ShouldQueue // Get all timezones from the database $timezones = app('timezones'); + /** @var \App\Models\Timezone $timezone */ foreach ($timezones as $timezone) { // Calculate the current UTC offset for this timezone (accounting for DST) $currentOffset = $this->getCurrentUtcOffset($timezone->name); @@ -152,11 +154,45 @@ class InvoiceTaxSummary implements ShouldQueue ->whereBetween('date', [$startDate, $endDate]) ->whereDoesntHave('transaction_events', function ($query) use ($todayStart, $todayEnd) { $query->where('timestamp', '>=', $todayStart) - ->where('timestamp', '<=', $todayEnd); + ->where('timestamp', '<=', $todayEnd) + ->where('event_id', TransactionEvent::INVOICE_UPDATED); }) ->cursor() ->each(function (Invoice $invoice) { (new InvoiceTransactionEventEntry())->run($invoice); }); + + Invoice::withTrashed() + ->with('payments') + ->where('company_id', $company->id) + ->whereIn('status_id', [3,4,5]) // Paid statuses + ->where('is_deleted', 0) + ->whereColumn('amount', '!=', 'balance') + ->whereHas('client', function ($query) { + $query->where('is_deleted', false); + }) + ->whereHas('company', function ($query) { + $query->where('is_disabled', 0) + ->whereHas('account', function ($q) { + $q->where('is_flagged', false); + }); + }) + ->whereHas('payments', function ($query) use ($startDate, $endDate) { + $query->whereHas('paymentables', function ($subQuery) use ($startDate, $endDate) { + $subQuery->where('paymentable_type', Invoice::class) + ->whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']); + }); + }) + ->whereDoesntHave('transaction_events', function ($q) use ($todayStart, $todayEnd) { + $q->where('event_id', TransactionEvent::PAYMENT_CASH) + ->where('timestamp', '>=', $todayStart) + ->where('timestamp', '<=', $todayEnd); + }) + ->cursor() + ->each(function (Invoice $invoice) use ($startDate, $endDate) { + (new InvoiceTransactionEventEntryAccrual())->run($invoice, $startDate, $endDate); + }); + } + } \ No newline at end of file diff --git a/app/Listeners/Invoice/InvoiceTransactionEventEntry.php b/app/Listeners/Invoice/InvoiceTransactionEventEntry.php index 6580b047ef..6f3da81f86 100644 --- a/app/Listeners/Invoice/InvoiceTransactionEventEntry.php +++ b/app/Listeners/Invoice/InvoiceTransactionEventEntry.php @@ -64,7 +64,7 @@ class InvoiceTransactionEventEntry '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' => $invoice->is_deleted ? TransactionEvent::INVOICE_DELETED : TransactionEvent::INVOICE_UPDATED, + 'event_id' => TransactionEvent::INVOICE_UPDATED, 'timestamp' => now()->timestamp, 'metadata' => $this->getMetadata($invoice), 'period' => now()->endOfMonth()->format('Y-m-d'), diff --git a/app/Listeners/Invoice/InvoiceTransactionEventEntryAccrual.php b/app/Listeners/Invoice/InvoiceTransactionEventEntryAccrual.php new file mode 100644 index 0000000000..ffa3ec1679 --- /dev/null +++ b/app/Listeners/Invoice/InvoiceTransactionEventEntryAccrual.php @@ -0,0 +1,227 @@ +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::INVOICE_UPDATED, + 'timestamp' => now()->timestamp, + 'metadata' => $this->getMetadata($invoice), + 'period' => now()->endOfMonth()->format('Y-m-d'), + ]); + } + + 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); + + } + + +} diff --git a/app/Listeners/Payment/PaymentTransactionEventEntry.php b/app/Listeners/Payment/PaymentTransactionEventEntry.php index 0e213f53d1..448dc58558 100644 --- a/app/Listeners/Payment/PaymentTransactionEventEntry.php +++ b/app/Listeners/Payment/PaymentTransactionEventEntry.php @@ -43,14 +43,15 @@ class PaymentTransactionEventEntry implements ShouldQueue private float $paid_ratio; private Collection $payments; + /** */ - public function __construct(private Payment $payment, private array $invoice_ids, private string $db) + public function __construct(private Payment $payment, private array $invoice_ids, private string $db, private float $invoice_adjustment = 0, private int $is_deleted = false) {} public function handle() { - nlog("PaymentTransactionEventEntry::handle"); + //payment vs refunded MultiDB::setDb($this->db); @@ -81,6 +82,12 @@ class PaymentTransactionEventEntry implements ShouldQueue $this->setPaidRatio($invoice); + //delete any other payment mutations here if this is a delete event, the refunds are redundant in this time period + $invoice->transaction_events() + ->where('event_id', TransactionEvent::PAYMENT_REFUNDED) + ->where('period', now()->endOfMonth()->format('Y-m-d')) + ->delete(); + TransactionEvent::create([ 'invoice_id' => $invoice->id, 'client_id' => $invoice->client_id, @@ -92,7 +99,7 @@ class PaymentTransactionEventEntry implements ShouldQueue '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' => $this->payment->is_deleted ? TransactionEvent::PAYMENT_DELETED : TransactionEvent::PAYMENT_REFUNDED, + 'event_id' => $this->is_deleted ? TransactionEvent::PAYMENT_DELETED : TransactionEvent::PAYMENT_REFUNDED, 'timestamp' => now()->timestamp, 'metadata' => $this->getMetadata($invoice), 'period' => now()->endOfMonth()->format('Y-m-d'), @@ -137,10 +144,14 @@ class PaymentTransactionEventEntry implements ShouldQueue $taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray()); foreach ($taxes as $tax) { + + $base_amount = $tax['base_amount'] ?? $calc->getNetSubtotal(); + + $tax_detail = [ 'tax_name' => $tax['name'], 'tax_rate' => $tax['tax_rate'], - 'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(), + 'taxable_amount' => $base_amount, 'tax_amount' => $tax['total'], 'tax_amount_paid' => $this->calculateRatio($tax['total']), 'tax_amount_remaining' => round($tax['total'] - $this->calculateRatio($tax['total']), 2), @@ -178,14 +189,26 @@ class PaymentTransactionEventEntry implements ShouldQueue $taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray()); foreach ($taxes as $tax) { + + + $base_amount = $tax['base_amount'] ?? $calc->getNetSubtotal(); + + if($this->invoice_adjustment > 0) + $tax_amount_paid = round(($this->invoice_adjustment / ($base_amount+$tax['total'])) * $tax['total'], 2); + else { + $tax_amount_paid = $this->calculateRatio($tax['total']); + } + $tax_detail = [ 'tax_name' => $tax['name'], 'tax_rate' => $tax['tax_rate'], - 'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(), + 'taxable_amount' => $base_amount, 'tax_amount' => $tax['total'], - 'tax_amount_paid' => $this->calculateRatio($tax['total']), + 'tax_amount_paid' => $tax_amount_paid, 'tax_amount_remaining' => 0, + 'tax_status' => 'payment_deleted', ]; + $details[] = $tax_detail; } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index c21d3b4039..6ce917f057 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -857,11 +857,15 @@ class Invoice extends BaseModel $formatted_string = "
"; - foreach($schedule->parameters['schedule'] as $item){ + $formatted_string .= "

".ctrans('texts.payment_schedule')."

"; + + foreach($schedule->parameters['schedule'] as $key => $item){ $amount = $item['is_amount'] ? $item['amount'] : round($this->amount * ($item['amount']/100),2); $amount = \App\Utils\Number::formatMoney($amount, $this->client); - $formatted_string .= "

".$this->formatDate($item['date'], $this->client->date_format()) . " - " . $amount."

"; + $schedule_text = ctrans('texts.payment_schedule_table', ['key' => $key+1, 'date' => $this->formatDate($item['date'], $this->client->date_format()), 'amount' => $amount]); + + $formatted_string .= "

".$schedule_text."

"; } $formatted_string .= "
"; diff --git a/app/Models/TransactionEvent.php b/app/Models/TransactionEvent.php index 38ff4aafaa..69f22457c1 100644 --- a/app/Models/TransactionEvent.php +++ b/app/Models/TransactionEvent.php @@ -38,7 +38,7 @@ use App\DataMapper\TransactionEventMetadata; * @property int $event_id * @property int $timestamp * @property array|null $payment_request - * @property array|null $metadata + * @property TransactionEventMetadata|null $metadata * @property string $credit_balance * @property string $credit_amount * @property int|null $credit_status @@ -65,4 +65,6 @@ class TransactionEvent extends StaticModel public const PAYMENT_REFUNDED = 2; public const PAYMENT_DELETED = 3; + + public const PAYMENT_CASH = 4; } diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 907c0e9edc..a13697e63a 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -89,7 +89,7 @@ class DeletePayment if ($this->payment->invoices()->exists()) { - $invoice_ids = $this->payment->invoices()->pluck('id'); + $invoice_ids = $this->payment->invoices()->pluck('invoices.id')->toArray(); $this->payment->invoices()->each(function ($paymentable_invoice) { $net_deletable = $paymentable_invoice->pivot->amount - $paymentable_invoice->pivot->refunded; @@ -161,10 +161,10 @@ class DeletePayment } + PaymentTransactionEventEntry::dispatch($this->payment, [$paymentable_invoice->id], $this->payment->company->db, $net_deletable, true); }); - PaymentTransactionEventEntry::dispatch($this->payment, $invoice_ids, $this->payment->company->db); } //sometimes the payment is NOT created properly, this catches the payment and prevents the paid to date reducing inappropriately. diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index 2f0e54cead..a58373f624 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -313,7 +313,7 @@ class RefundPayment } - PaymentTransactionEventEntry::dispatch($this->payment, array_column($this->refund_data['invoices'], 'invoice_id'), $this->payment->company->db); + PaymentTransactionEventEntry::dispatch($this->payment, array_column($this->refund_data['invoices'], 'invoice_id'), $this->payment->company->db, 0, false); } else { //if we are refunding and no payments have been tagged, then we need to decrement the client->paid_to_date by the total refund amount. diff --git a/app/Services/Report/XLS/TaxReport.php b/app/Services/Report/XLS/TaxReport.php index 958345516f..958816a22f 100644 --- a/app/Services/Report/XLS/TaxReport.php +++ b/app/Services/Report/XLS/TaxReport.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Builder; use PhpOffice\PhpSpreadsheet\Spreadsheet; use App\Models\Invoice; use App\Listeners\Invoice\InvoiceTransactionEventEntry; +use App\Models\TransactionEvent; class TaxReport { @@ -83,7 +84,7 @@ class TaxReport $worksheet = $this->spreadsheet->createSheet(); $worksheet->setTitle(ctrans('texts.invoice')." ".ctrans('texts.cash_vs_accrual')); - $worksheet->fromArray($this->data['invoices'], null, 'A1'); + $worksheet->fromArray($this->data['accrual']['invoices'], null, 'A1'); $worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column $worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column @@ -97,13 +98,11 @@ class TaxReport // All paid invoices within a time period public function createInvoiceSummarySheetCash() { - $cash_invoices = collect($this->data['invoices'])->filter(function($invoice){ - return $invoice[3] != 0; - })->toArray(); $worksheet = $this->spreadsheet->createSheet(); $worksheet->setTitle(ctrans('texts.invoice')." ".ctrans('texts.cash_accounting')); - $worksheet->fromArray($cash_invoices, null, 'A1'); + + $worksheet->fromArray($this->data['cash']['invoices'], null, 'A1'); $worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column $worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column $worksheet->getStyle('D:D')->getNumberFormat()->setFormatCode($this->currency_format); // Paid amount column @@ -118,7 +117,7 @@ class TaxReport $worksheet = $this->spreadsheet->createSheet(); $worksheet->setTitle(ctrans('texts.invoice_item')." ".ctrans('texts.cash_vs_accrual')); - $worksheet->fromArray($this->data['invoice_items'], null, 'A1'); + $worksheet->fromArray($this->data['accrual']['invoice_items'], null, 'A1'); $worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column $worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column @@ -135,13 +134,9 @@ class TaxReport public function createInvoiceItemSummarySheetCash() { - $cash_invoice_items = collect($this->data['invoice_items'])->filter(function($invoice_item){ - return $invoice_item[3] != 0; - })->toArray(); - $worksheet = $this->spreadsheet->createSheet(); $worksheet->setTitle(ctrans('texts.invoice_item')." ".ctrans('texts.cash_accounting')); - $worksheet->fromArray($cash_invoice_items, null, 'A1'); + $worksheet->fromArray($this->data['cash']['invoice_items'], null, 'A1'); $worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column $worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column @@ -162,17 +157,19 @@ class TaxReport $end_date_instance = Carbon::parse($this->tsr->end_date); $this->data['invoices'] = []; - $this->data['invoices'][] = [ + $this->data['invoices'][] = + + $invoice_headers = [ ctrans('texts.invoice_number'), ctrans('texts.invoice_date'), ctrans('texts.invoice_total'), ctrans('texts.paid'), ctrans('texts.total_taxes'), - ctrans('texts.tax_paid') + ctrans('texts.tax_paid'), + ctrans('texts.notes') ]; - $this->data['invoice_items'] = []; - $this->data['invoice_items'][] = [ + $invoice_item_headers = [ ctrans('texts.invoice_number'), ctrans('texts.invoice_date'), ctrans('texts.invoice_total'), @@ -185,26 +182,106 @@ class TaxReport ctrans('texts.tax_nexus'), ]; - $offset = $this->company->timezone_offset(); - /** @var Invoice $invoice */ - foreach($this->query->cursor() as $invoice){ + $this->data['accrual']['invoices'] = [$invoice_headers]; + $this->data['cash']['invoices'] = [$invoice_headers]; + $this->data['accrual']['invoice_items'] = [$invoice_item_headers]; + $this->data['cash']['invoice_items'] = [$invoice_item_headers]; - if($invoice->transaction_events->count() == 0){ - (new InvoiceTransactionEventEntry())->run($invoice); - } - //get the invoice state as at the end of the current period. - $invoice->transaction_events->each(function($event){ + Invoice::withTrashed() + ->with('transaction_events') + ->where('company_id', $this->company->id) + ->whereHas('transaction_events', function ($query){ + return $query->where('period', now()->endOfMonth()->format('Y-m-d')); + }) + ->cursor() + ->each(function($invoice){ + + if($invoice->transaction_events->count() == 0){ + (new InvoiceTransactionEventEntry())->run($invoice); + $invoice->load('transaction_events'); + } + + /** @var TransactionEvent $invoice_state */ + $invoice_state = $invoice->transaction_events->where('event_id', TransactionEvent::INVOICE_UPDATED)->sortByDesc('timestamp')->first(); + $adjustments = $invoice->transaction_events->whereIn('event_id',[TransactionEvent::PAYMENT_REFUNDED, TransactionEvent::PAYMENT_DELETED]); + + if($invoice_state->event_id == TransactionEvent::INVOICE_UPDATED){ + $this->data['accrual']['invoices'][] = [ + $invoice->number, + $invoice->date, + $invoice->amount, + $invoice_state->invoice_paid_to_date, + $invoice_state->metadata->tax_report->tax_summary->total_taxes, + $invoice_state->metadata->tax_report->tax_summary->total_paid, + 'payable', + ]; + } + elseif($invoice_state->event_id == TransactionEvent::PAYMENT_CASH){ + + $this->data['cash']['invoices'][] = [ + $invoice->number, + $invoice->date, + $invoice->amount, + $invoice_state->invoice_paid_to_date, + $invoice_state->metadata->tax_report->tax_summary->total_taxes, + $invoice_state->metadata->tax_report->tax_summary->total_paid, + 'payable', + ]; + + } + $_adjustments = []; + + foreach($adjustments as $adjustment){ + $_adjustments[] = [ + $invoice->number, + $invoice->date, + $invoice->amount, + $invoice_state->invoice_paid_to_date, + $invoice_state->metadata->tax_report->tax_summary->total_taxes, + $invoice_state->metadata->tax_report->tax_summary->adjustment, + 'adjustment', + ]; + } + + $this->data['accrual']['invoices'] = array_merge($this->data['accrual']['invoices'], $_adjustments); + $this->data['cash']['invoices'] = array_merge($this->data['cash']['invoices'], $_adjustments); }); - //anything period the reporting period is considered an ADJUSTMENT - - } - - return $this; + return $this; } + // $offset = $this->company->timezone_offset(); + + // /** @var Invoice $invoice */ + // foreach($this->query->cursor() as $invoice){ + + // if($invoice->transaction_events->count() == 0){ + // (new InvoiceTransactionEventEntry())->run($invoice); + // } + + // //get the invoice state as at the end of the current period. + // $invoice_state =$invoice->transaction_events() + // ->where('period', Carbon::parse($invoice->date)->endOfMonth()->format('Y-m-d')) + // ->where('event_id', TransactionEvent::INVOICE_UPDATED) + // ->latest() + // ->first(); + + + + // //anything period the reporting period is considered an ADJUSTMENT + // } + + // Invoice::withTrashed() + // ->where('company_id', $this->company->id) + // ->whereHas('transaction_events', function ($query){ + // return $query->where('period', Carbon::parse($invoice->date)->endOfMonth()->format('Y-m-d')) + // ->whereIn('event_id',[TransactionEvent::PAYMENT_REFUNDED, TransactionEvent::PAYMENT_DELETED]); + // }); + + // return $this; + // } public function getXlsFile() { @@ -214,7 +291,7 @@ class TaxReport $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($this->spreadsheet); $writer->save($tempFile); - // $writer->save('/home/david/ttx.xslx'); + $writer->save('/home/david/ttx.xlsx'); // Read file content $fileContent = file_get_contents($tempFile); diff --git a/lang/en/texts.php b/lang/en/texts.php index 2ab19130b9..a26a5385d2 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5607,6 +5607,7 @@ $lang = array( 'first_payment_date' => 'First Payment Date', 'first_payment_date_help' => 'The date of the first payment', 'payment_schedule_interval' => 'Payment :index of :total for :amount', + 'payment_schedule_table' => 'Payment :key on :date for :amount', 'auto_send' => 'Auto Send', 'auto_send_help' => 'Automatically emails the invoice to the client', 'include_project_tasks' => 'Include Project Tasks', diff --git a/tests/Feature/Export/TaxSummaryReportTest.php b/tests/Feature/Export/TaxSummaryReportTest.php index 6ac58603c5..13efe22ae6 100644 --- a/tests/Feature/Export/TaxSummaryReportTest.php +++ b/tests/Feature/Export/TaxSummaryReportTest.php @@ -11,17 +11,19 @@ namespace Tests\Feature\Export; -use App\DataMapper\CompanySettings; -use App\Factory\InvoiceItemFactory; -use App\Models\Account; +use Tests\TestCase; +use App\Models\User; use App\Models\Client; +use App\Models\Account; use App\Models\Company; use App\Models\Invoice; -use App\Models\User; -use App\Services\Report\TaxSummaryReport; use App\Utils\Traits\MakesHash; +use App\DataMapper\CompanySettings; +use App\Factory\InvoiceItemFactory; +use App\Services\Report\TaxSummaryReport; use Illuminate\Routing\Middleware\ThrottleRequests; -use Tests\TestCase; +use App\Listeners\Invoice\InvoiceTransactionEventEntry; +use App\Listeners\Invoice\InvoiceTransactionEventEntryAccrual; /** * @@ -162,6 +164,34 @@ class TaxSummaryReportTest extends TestCase $i = $i->calc()->getInvoice(); + (new InvoiceTransactionEventEntry())->run($i); + + $i2 = Invoice::factory()->create([ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 0, + 'balance' => 0, + 'status_id' => 2, + 'total_taxes' => 1, + 'date' => now()->format('Y-m-d'), + 'terms' => 'nada', + 'discount' => 0, + 'tax_rate1' => 10, + 'tax_rate2' => 17.5, + 'tax_rate3' => 5, + 'tax_name1' => 'GST', + 'tax_name2' => 'VAT', + 'tax_name3' => 'CA Sales Tax', + 'uses_inclusive_taxes' => false, + 'line_items' => $this->buildLineItems(), + ]); + + $i2 = $i2->calc()->getInvoice(); + $i2->service()->markPaid(); + + (new InvoiceTransactionEventEntryAccrual())->run($i2); + $pl = new TaxSummaryReport($this->company, $this->payload); $response = $pl->run(); @@ -206,6 +236,34 @@ class TaxSummaryReportTest extends TestCase $i = $i->calc()->getInvoice(); + (new InvoiceTransactionEventEntry())->run($i); + +$i2 = Invoice::factory()->create([ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 0, + 'balance' => 0, + 'status_id' => 2, + 'total_taxes' => 1, + 'date' => now()->format('Y-m-d'), + 'terms' => 'nada', + 'discount' => 0, + 'tax_rate1' => 10, + 'tax_rate2' => 17.5, + 'tax_rate3' => 5, + 'tax_name1' => 'GST', + 'tax_name2' => 'VAT', + 'tax_name3' => 'CA Sales Tax', + 'uses_inclusive_taxes' => false, + 'line_items' => $this->buildLineItems(), + ]); + +$i2 = $i2->calc()->getInvoice(); +$i2->service()->markPaid(); + +(new InvoiceTransactionEventEntryAccrual())->run($i2, now()->subDays(30)->format('Y-m-d'), now()->addDays(30)->format('Y-m-d')); + $pl = new TaxSummaryReport($this->company, $this->payload); $query = Invoice::query()