diff --git a/app/Http/Controllers/Reports/TaxPeriodReportController.php b/app/Http/Controllers/Reports/TaxPeriodReportController.php new file mode 100644 index 0000000000..8ce648806a --- /dev/null +++ b/app/Http/Controllers/Reports/TaxPeriodReportController.php @@ -0,0 +1,83 @@ +user(); + + if ($request->has('send_email') && $request->get('send_email') && $request->missing('output')) { + SendToAdmin::dispatch($user->company(), $request->all(), TaxPeriodReport::class, $this->filename); + + return response()->json(['message' => 'working...'], 200); + } + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), TaxPeriodReport::class, $hash); + + return response()->json(['message' => $hash], 200); + + } +} diff --git a/app/Jobs/Cron/InvoiceTaxSummary.php b/app/Jobs/Cron/InvoiceTaxSummary.php index 8a0731b4a6..3c14b36aba 100644 --- a/app/Jobs/Cron/InvoiceTaxSummary.php +++ b/app/Jobs/Cron/InvoiceTaxSummary.php @@ -26,7 +26,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use App\Listeners\Invoice\InvoiceTransactionEventEntry; -use App\Listeners\Invoice\InvoiceTransactionEventEntryAccrual; +use App\Listeners\Invoice\InvoiceTransactionEventEntryCash; class InvoiceTaxSummary implements ShouldQueue { @@ -165,7 +165,7 @@ class InvoiceTaxSummary implements ShouldQueue Invoice::withTrashed() ->with('payments') ->where('company_id', $company->id) - ->whereIn('status_id', [3,4,5]) // Paid statuses + ->whereIn('status_id', [3,4]) // Paid statuses ->where('is_deleted', 0) ->whereColumn('amount', '!=', 'balance') ->whereHas('client', function ($query) { @@ -190,7 +190,7 @@ class InvoiceTaxSummary implements ShouldQueue }) ->cursor() ->each(function (Invoice $invoice) use ($startDate, $endDate) { - (new InvoiceTransactionEventEntryAccrual())->run($invoice, $startDate, $endDate); + (new InvoiceTransactionEventEntryCash())->run($invoice, $startDate, $endDate); }); } diff --git a/app/Listeners/Invoice/InvoiceTransactionEventEntry.php b/app/Listeners/Invoice/InvoiceTransactionEventEntry.php index b4a117b579..4814ec5397 100644 --- a/app/Listeners/Invoice/InvoiceTransactionEventEntry.php +++ b/app/Listeners/Invoice/InvoiceTransactionEventEntry.php @@ -37,10 +37,12 @@ class InvoiceTransactionEventEntry * @param Invoice $invoice * @return void */ - public function run($invoice) + public function run(Invoice $invoice, ?string $force_period = null) { $this->setPaidRatio($invoice); + $period = $force_period ?? now()->endOfMonth()->format('Y-m-d'); + $this->payments = $invoice->payments->flatMap(function ($payment) { return $payment->invoices()->get()->map(function ($invoice) use ($payment) { return [ @@ -66,7 +68,7 @@ class InvoiceTransactionEventEntry 'event_id' => TransactionEvent::INVOICE_UPDATED, 'timestamp' => now()->timestamp, 'metadata' => $this->getMetadata($invoice), - 'period' => now()->endOfMonth()->format('Y-m-d'), + 'period' => $period, ]); } diff --git a/app/Listeners/Invoice/InvoiceTransactionEventEntryAccrual.php b/app/Listeners/Invoice/InvoiceTransactionEventEntryAccrual.php deleted file mode 100644 index 683c81f29a..0000000000 --- a/app/Listeners/Invoice/InvoiceTransactionEventEntryAccrual.php +++ /dev/null @@ -1,227 +0,0 @@ -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' => 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/Services/Report/XLS/TaxReport.php b/app/Services/Report/TaxPeriodReport.php similarity index 58% rename from app/Services/Report/XLS/TaxReport.php rename to app/Services/Report/TaxPeriodReport.php index b0f563f15a..c8ab4ebc62 100644 --- a/app/Services/Report/XLS/TaxReport.php +++ b/app/Services/Report/TaxPeriodReport.php @@ -1,21 +1,41 @@ start_date = Carbon::parse($this->start_date); - $this->end_date = Carbon::parse($this->end_date); - MultiDB::setDb($this->company->db); App::forgetInstance('translator'); App::setLocale($this->company->locale()); $t = app('translator'); $t->replace(Ninja::transformTranslations($this->company->settings)); - $this->spreadsheet = new Spreadsheet(); - - $this->buildData() - ->setCurrencyFormat() - ->createSummarySheet() - ->createInvoiceSummarySheetAccrual() - ->createInvoiceSummarySheetCash() - ->createInvoiceItemSummarySheetAccrual() - ->createInvoiceItemSummarySheetCash(); - - - return $this; + $this->calculateDateRange() + ->initializeData() + ->buildData(); } + + /** + * initializeData + * + * Ensure our dataset has the appropriate transaction events. + * + * @return self + */ + private function initializeData(): self + { + 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() + ->each(function($invoice){ + + if($invoice->status_id == Invoice::STATUS_SENT){ + (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])){ + + } + }); + + return $this; + } + + private function resolveQuery() + { + $query = Invoice::query() + ->withTrashed() + ->with('transaction_events') + ->where('company_id', $this->company->id) + ->where('is_deleted', 0); + + if($this->input['is_income_billed']) //acrrual + { + $query->whereIn('status_id', [2,3,4]) + ->whereHas('transaction_events', function($query){ + $query->where('event_id', TransactionEvent::INVOICE_UPDATED) + ->whereBetween('period', [$this->start_date, $this->end_date]); + }); + } + else //cash + { + $query->whereIn('status_id', [3,4]) + ->whereHas('transaction_events', function($query){ + $query->where('event_id', TransactionEvent::PAYMENT_CASH) + ->whereBetween('period', [$this->start_date, $this->end_date]); + }); + } + + $query->orderBy('balance', 'desc'); + + return $query; + } + + /** + * calculateDateRange + * + * We only support dates as of the end of the last month. + * @return self + */ + private function calculateDateRange(): self + { + + switch ($date_range) { + case 'last7': + case 'last30': + case 'this_month': + case 'last_month': + $this->start_date = now()->startOfMonth()->subMonth()->format('Y-m-d'); + $this->end_date = now()->startOfMonth()->subMonth()->endOfMonth()->format('Y-m-d'); + 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'); + 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'); + case 'last365_days': + $this->start_date = now()->startOfDay()->subDays(365)->format('Y-m-d'); + $this->end_date = now()->startOfDay()->format('Y-m-d'); + case 'this_year': + + $first_month_of_year = $this->company->first_month_of_year ?? 1; + $fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1); + + if (now()->lt($fin_year_start)) { + $fin_year_start->subYearNoOverflow(); + } + + $this->start_date = $fin_year_start->format('Y-m-d'); + $this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d'); + case 'last_year': + + $first_month_of_year = $this->company->first_month_of_year ?? 1; + $fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1); + $fin_year_start->subYearNoOverflow(); + + if (now()->subYear()->lt($fin_year_start)) { + $fin_year_start->subYearNoOverflow(); + } + + $this->start_date = $fin_year_start->format('Y-m-d'); + $this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d'); + case 'custom': + + try { + $custom_start_date = Carbon::parse($this->input['start_date']); + $custom_end_date = Carbon::parse($this->input['end_date']); + } catch (\Exception $e) { + $custom_start_date = now()->startOfYear(); + $custom_end_date = now(); + } + + $this->start_date = $custom_start_date->format('Y-m-d'); + $this->end_date = $custom_end_date->format('Y-m-d'); + case 'all': + default: + $this->start_date = now()->startOfYear()->format('Y-m-d'); + $this->end_date = now()->format('Y-m-d'); + } + + return $this; + } + + public function accrual() + { + + } + + public function cash() + { + + } + + public function getXlsFile() + { + } + public function setCurrencyFormat() { @@ -82,7 +244,7 @@ class TaxReport } // All invoices within a time period - regardless if they are paid or not! - public function createInvoiceSummarySheetAccrual() + public function createInvoiceSummarySheet() { $worksheet = $this->spreadsheet->createSheet(); @@ -98,24 +260,7 @@ class TaxReport return $this; } - // All paid invoices within a time period - public function createInvoiceSummarySheetCash() - { - - $worksheet = $this->spreadsheet->createSheet(); - $worksheet->setTitle(ctrans('texts.invoice')." ".ctrans('texts.cash_accounting')); - - $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 - $worksheet->getStyle('E:E')->getNumberFormat()->setFormatCode($this->currency_format); // Total taxes column - $worksheet->getStyle('F:F')->getNumberFormat()->setFormatCode($this->currency_format); // Tax paid column - - return $this; - } - - public function createInvoiceItemSummarySheetAccrual() + public function createInvoiceItemSummarySheet() { $worksheet = $this->spreadsheet->createSheet(); @@ -134,30 +279,11 @@ class TaxReport return $this; } - public function createInvoiceItemSummarySheetCash() - { - - $worksheet = $this->spreadsheet->createSheet(); - $worksheet->setTitle(ctrans('texts.invoice_item')." ".ctrans('texts.cash_accounting')); - $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 - $worksheet->getStyle('D:D')->getNumberFormat()->setFormatCode($this->currency_format); // Paid amount column - $worksheet->getStyle('F:F')->getNumberFormat()->setFormatCode($this->number_format."%"); // Tax rate column - $worksheet->getStyle('G:G')->getNumberFormat()->setFormatCode($this->currency_format); // Tax amount column - $worksheet->getStyle('H:H')->getNumberFormat()->setFormatCode($this->currency_format); // Tax paid column - $worksheet->getStyle('I:I')->getNumberFormat()->setFormatCode($this->currency_format); // Taxable amount column - // Column J (tax_nexus) is text, so no special formatting needed - - return $this; - } private function buildData() { - $start_date_instance = $this->start_date; - $end_date_instance = $this->end_date; + $query = $this->resolveQuery(); $this->data['invoices'] = []; $this->data['invoices'][] = @@ -192,20 +318,9 @@ class TaxReport $this->data['cash']['invoice_items'] = [$invoice_item_headers]; - 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() + $query->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)->where('period', now()->endOfMonth()->format('Y-m-d'))->orderBy('timestamp', 'desc')->first(); $payment_state = $invoice->transaction_events()->where('event_id', TransactionEvent::PAYMENT_CASH)->where('period', now()->endOfMonth()->format('Y-m-d'))->orderBy('timestamp', 'desc')->first(); @@ -281,4 +396,5 @@ class TaxReport return $fileContent; } + } diff --git a/lang/en/texts.php b/lang/en/texts.php index a26a5385d2..a36dbed6ee 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5613,6 +5613,7 @@ $lang = array( 'include_project_tasks' => 'Include Project Tasks', 'include_project_tasks_help' => 'Also invoice tasks that are part of a project', 'tax_nexus' => 'Tax Nexus', + 'tax_period_report' => 'Tax Period Report', ); return $lang; diff --git a/routes/api.php b/routes/api.php index a3910c5944..72d1aa322c 100644 --- a/routes/api.php +++ b/routes/api.php @@ -116,6 +116,7 @@ use App\Http\Controllers\Reports\ARDetailReportController; use App\Http\Controllers\Reports\DocumentReportController; use App\Http\Controllers\Reports\ARSummaryReportController; use App\Http\Controllers\Reports\QuoteItemReportController; +use App\Http\Controllers\Reports\TaxPeriodReportController; use App\Http\Controllers\Reports\UserSalesReportController; use App\Http\Controllers\Reports\TaxSummaryReportController; use App\Http\Controllers\Support\Messages\SendingController; @@ -372,6 +373,7 @@ Route::group(['middleware' => ['throttle:api', 'token_auth', 'valid_json','local Route::post('reports/client_balance_report', ClientBalanceReportController::class); Route::post('reports/client_sales_report', ClientSalesReportController::class); Route::post('reports/tax_summary_report', TaxSummaryReportController::class); + Route::post('reports/tax_period_report', TaxPeriodReportController::class); Route::post('reports/user_sales_report', UserSalesReportController::class); Route::post('reports/projects', ProjectReportController::class); Route::post('reports/preview/{hash}', ReportPreviewController::class); diff --git a/tests/Feature/Export/TaxSummaryReportTest.php b/tests/Feature/Export/TaxSummaryReportTest.php index 653604a23a..0756b6c21c 100644 --- a/tests/Feature/Export/TaxSummaryReportTest.php +++ b/tests/Feature/Export/TaxSummaryReportTest.php @@ -24,7 +24,7 @@ use App\Factory\InvoiceItemFactory; use App\Services\Report\TaxSummaryReport; use Illuminate\Routing\Middleware\ThrottleRequests; use App\Listeners\Invoice\InvoiceTransactionEventEntry; -use App\Listeners\Invoice\InvoiceTransactionEventEntryAccrual; +use App\Listeners\Invoice\InvoiceTransactionEventEntryCash; /** * @@ -226,7 +226,7 @@ class TaxSummaryReportTest extends TestCase $this->assertEquals($i3->amount, $i3->paid_to_date); - (new InvoiceTransactionEventEntryAccrual())->run($i3, now()->subDays(30)->format('Y-m-d'), now()->addDays(30)->format('Y-m-d')); + (new InvoiceTransactionEventEntryCash())->run($i3, now()->subDays(30)->format('Y-m-d'), now()->addDays(30)->format('Y-m-d')); } @@ -268,7 +268,7 @@ class TaxSummaryReportTest extends TestCase $i2 = $i2->fresh(); - (new InvoiceTransactionEventEntryAccrual())->run($i2, now()->subDays(30)->format('Y-m-d'), now()->addDays(30)->format('Y-m-d')); + (new InvoiceTransactionEventEntryCash())->run($i2, now()->subDays(30)->format('Y-m-d'), now()->addDays(30)->format('Y-m-d')); $payment = $i2->payments()->first(); @@ -294,18 +294,18 @@ class TaxSummaryReportTest extends TestCase $payment->refund($data); - $pl = new \App\Services\Report\XLS\TaxReport($this->company, '2025-01-01', '2025-12-31'); + // $pl = new \App\Services\Report\XLS\TaxReport($this->company, '2025-01-01', '2025-12-31'); - $response = $pl->run()->getXlsFile(); + // $response = $pl->run()->getXlsFile(); - $this->assertIsString($response); + // $this->assertIsString($response); - try{ - file_put_contents('/home/david/ttx.xlsx', $response); - } - catch(\Throwable $e){ - nlog($e->getMessage()); - } + // try{ + // file_put_contents('/home/david/ttx.xlsx', $response); + // } + // catch(\Throwable $e){ + // nlog($e->getMessage()); + // } config(['queue.default' => 'redis']); @@ -376,7 +376,7 @@ class TaxSummaryReportTest extends TestCase $i2 = $i2->calc()->getInvoice(); $i2->service()->markPaid(); - (new InvoiceTransactionEventEntryAccrual())->run($i2, now()->subDays(3000)->format('Y-m-d'), now()->addDays(3000)->format('Y-m-d')); + (new InvoiceTransactionEventEntryCash())->run($i2, now()->subDays(3000)->format('Y-m-d'), now()->addDays(3000)->format('Y-m-d')); $pl = new TaxSummaryReport($this->company, $this->payload); $response = $pl->run(); @@ -449,12 +449,12 @@ class TaxSummaryReportTest extends TestCase $i2 = $i2->calc()->getInvoice(); $i2->service()->markPaid()->save(); - (new InvoiceTransactionEventEntryAccrual())->run($i2, now()->subDays(30)->format('Y-m-d'), now()->addDays(30)->format('Y-m-d')); + (new InvoiceTransactionEventEntryCash())->run($i2, now()->subDays(30)->format('Y-m-d'), now()->addDays(30)->format('Y-m-d')); - $tr = new \App\Services\Report\XLS\TaxReport($this->company, '2025-01-01', '2025-12-31'); - $response = $tr->run()->getXlsFile(); + // $tr = new \App\Services\Report\XLS\TaxReport($this->company, '2025-01-01', '2025-12-31'); + // $response = $tr->run()->getXlsFile(); - $this->assertNotEmpty($response); + // $this->assertNotEmpty($response); $this->assertNotNull(TransactionEvent::where('invoice_id', $i->id)->first()); $this->assertNotNull(TransactionEvent::where('invoice_id', $i2->id)->first());