From ea383dd0065c52095943deb3a8d36bcd17ecf729 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 9 Oct 2025 21:32:30 +0800 Subject: [PATCH 01/20] E-Invoice - API, Sales Invoice Report --- .../General/Eloquent/Filters/DateRange.php | 51 +++++ .../Eloquent/Filters/DateRangeNoData.php | 60 ++++++ .../ListBookingsSalesInvoiceLogic.php | 52 +++++ .../ListBookingsSalesInvoiceController.php | 20 ++ ...ountSalesInvoiceBookingDetailsResource.php | 35 ++++ .../AutocountSalesInvoiceBookingResource.php | 198 ++++++++++++++++++ routes/apipub.php | 5 + 7 files changed, 421 insertions(+) create mode 100644 app/Classes/General/Eloquent/Filters/DateRange.php create mode 100644 app/Classes/General/Eloquent/Filters/DateRangeNoData.php create mode 100644 app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php create mode 100644 app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php create mode 100644 app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php create mode 100644 app/Http/Resources/AutocountSalesInvoiceBookingResource.php diff --git a/app/Classes/General/Eloquent/Filters/DateRange.php b/app/Classes/General/Eloquent/Filters/DateRange.php new file mode 100644 index 00000000..f1ffc82a --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/DateRange.php @@ -0,0 +1,51 @@ +startOfDay() + : Carbon::now()->subMonth()->startOfDay(); + + $endDate = isset($dt2) && $dt2 + ? Carbon::createFromFormat('Y-m-d', $dt2)->endOfDay() + : Carbon::now()->endOfDay(); + + return $builder->with([ + 'company', + 'transactions.transactionDetails', + 'transactions.voucherRedemption', + ]) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]) + ->latest('created_at'); + }) + ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->complete() + ->where(function ($q) use ($startDate, $endDate) { + $q->where('created_at', '<', $startDate) + ->orWhere('created_at', '>', $endDate); + }); + }); + } +} diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php new file mode 100644 index 00000000..25f9a343 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -0,0 +1,60 @@ +startOfDay() + : Carbon::now()->subMonth()->startOfDay(); + + $endDate = isset($dt2) && $dt2 + ? Carbon::createFromFormat('Y-m-d', $dt2)->endOfDay() + : Carbon::now()->endOfDay(); + + return $builder->with([ + 'company', + 'transactions.transactionDetails', + 'transactions.voucherRedemption', + ]) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]) + ->latest('created_at'); + }) + ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->complete() + ->where(function ($q) use ($startDate, $endDate) { + $q->where('created_at', '<', $startDate) + ->orWhere('created_at', '>', $endDate); + }); + }) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->where('type', TransactionType::INVOICE) + ->latest('created_at') + ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php new file mode 100644 index 00000000..a2134295 --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php @@ -0,0 +1,52 @@ + 'Retrieved Bookings with Sales Invoice', + 'message' => 'You have successfully retrieved a list of Bookings with Sales Invoice' + ]; + } + + /** @var CanListBookings */ + private $canListBookings; + + /** @var ListsBookings */ + private $listsBookings; + + /** + * ListBookingsSalesInvoiceLogic constructor. + * @param CanListBookings $canListBookings + * @param ListsBookings $listsBookings + */ + public function __construct(CanListBookings $canListBookings, ListsBookings $listsBookings) + { + $this->canListBookings = $canListBookings; + $this->listsBookings = $listsBookings; + } + + public function logic(Request $request) : JsonResponse + { + // $this->canListBookings->passes(); //cief todo: 90 - apipub + + $query = $this->listsBookings->execute($this->listsBookings->deserializeFilters($request->input('filters'))); + + return $this->collectionResponse(AutocountSalesInvoiceBookingResource::collection($query)); + + } +} diff --git a/app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php b/app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php new file mode 100644 index 00000000..249278af --- /dev/null +++ b/app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php @@ -0,0 +1,20 @@ +execute($request); + } +} diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php new file mode 100644 index 00000000..fa00fdc6 --- /dev/null +++ b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php @@ -0,0 +1,35 @@ + $this['DocNo'], + 'DocDate' => $this['DocDate'], + 'DebtorCode' => $this['DebtorCode'], + 'Ref' => $this['Ref'], + 'ShipInfo' => $this['ShipInfo'], + 'AccNo' => $this['AccNo'], + 'DetailDescription' => $this['DetailDescription'], + 'FurtherDescription' => $this['FurtherDescription'], + 'Classification' => $this['Classification'], + 'DeptNo' => $this['DeptNo'], + 'Qty' => $this['Qty'], + 'UnitPrice' => $this['UnitPrice'], + 'submiteinvoice' => $this['submiteinvoice'], + 'ConsolidatedEinvoice' => $this['ConsolidatedEinvoice'], + ]; + } + +} diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php new file mode 100644 index 00000000..f02d08be --- /dev/null +++ b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php @@ -0,0 +1,198 @@ +transactions->where('type', TransactionType::PURCHASE_ORDER)->first(); + $company = $this->company; + + $lastPaymentTransaction = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]) + ->sortByDesc('created_at') + ->first(); + + $documentDate = Carbon::parse($lastPaymentTransaction->created_at); + + // if ($documentDate < $this->startDate || $documentDate > $this->endDate) { + // return []; + // } + + if($company->e_invoice === 1){ + $documentDate = $documentDate->copy()->endOfMonth(); + } + + $invoiceTransaction = $this->transactions->where('type', TransactionType::INVOICE)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->first(); + + $currencyId = $this->fix_currency_id; + + $subtotal = 0; + $displayedSubtotal = 0; + $totalPayment = 0; + $averageCurrencyRate = $invoiceTransaction->currency_rate; + $paymentSum = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->sum(function ($transaction) { + return round($transaction->amount, 2); + }); + if ($paymentSum){ + $averageCurrencyRate = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->sum(function ($transaction) { + return $transaction->currency_rate; + }) / $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->count(); + + $booking = Booking::where('id', $this->id)->first(); + $refundedAmount = (App()->make(CalculatesBookingRefundAmount::class))->execute($booking, 1); + $refundedServiceCharge = (App()->make(CalculatesBookingRefundServiceCharge::class))->execute($booking, 1); + + $totalPayment = $paymentSum - $refundedAmount - $refundedServiceCharge; + } + + $formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y'); + + $firstItem = true; + $transactionDetails = $purchaseOrder->transactionDetails; + foreach ($transactionDetails as $detail) { + $displayUnitPrice = 0; + if($averageCurrencyRate && $currencyId){ + $exactUnitPrice = ($currencyId) === 1 ? $detail->price : bcdiv($detail->price, $averageCurrencyRate, 7); + $displayUnitPrice = round($exactUnitPrice, 2); + $itemTotal = bcmul($exactUnitPrice, $detail->quantity, 5); + $displayedItemTotal = round(bcmul($displayUnitPrice, $detail->quantity, 7), 2); + $displayedSubtotal = bcadd($displayedSubtotal, $displayedItemTotal, 2); + $subtotal = bcadd($subtotal, $itemTotal, 5); + } + + $detail = [ + 'DocNo' => $firstItem ? '<>' : '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction->bill_no, + 'ShipInfo' => $this->marking, + 'AccNo' => '500-0000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => $detail->product_name, + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => number_format($detail->quantity, 0), + 'UnitPrice' => $displayUnitPrice ? number_format($displayUnitPrice, 2) : '0', + 'submiteinvoice' => $firstItem ? 'T' : '', + 'ConsolidatedEinvoice' => $firstItem ? ($company->e_invoice ? 'F' : 'T') : '', + ]; + $details->push($detail); + + if($firstItem) { + $firstItem = false; + } + } + + // Service Charge - Starts + $serviceCharge = 0; + if (!$totalPayment) { + $serviceCharge = $invoiceTransaction->service_charge; + } + else { + $serviceCharge = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->sum(function ($transaction) { + return $transaction->service_charge; + }); + } + + $detail = [ + 'DocNo' => '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction->bill_no, + 'ShipInfo' => $this->marking, + 'AccNo' => '500-0000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => 'Service Charge', + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => '1', + 'UnitPrice' => $serviceCharge ? number_format($serviceCharge, 2) : '0', + 'submiteinvoice' => '', + 'ConsolidatedEinvoice' => '', + ]; + $details->push($detail); + // Service Charge - Ends + + // Adjustment - Starts + $adjustment = 0; + $voucherRedemption = $invoiceTransaction->voucherRedemption; + $voucherDiscount = $voucherRedemption ? bcmul((string)$voucherRedemption->value, "-1", 2) : "0"; + + $displayedSubtotal = is_numeric($displayedSubtotal) ? sprintf('%F', $displayedSubtotal) : '0'; + $serviceCharge = is_numeric($serviceCharge) ? sprintf('%F', $serviceCharge) : '0'; + $tax = is_numeric($invoiceTransaction->tax) ? sprintf('%F', $invoiceTransaction->tax) : '0'; + $voucherDiscount = is_numeric($voucherDiscount) ? sprintf('%F', $voucherDiscount) : '0'; + + $displayedTotal = bcadd( + bcadd( + bcadd($displayedSubtotal, $serviceCharge, 5), + $tax, + 5 + ), + $voucherDiscount, + 5 + ); + + $expectedTotal = bcadd(bcadd(bcadd($subtotal, $serviceCharge, 5), $invoiceTransaction->tax, 5), $voucherDiscount, 5); + $adjustment = bcsub($expectedTotal, $displayedTotal, 5); + + if ($totalPayment) { + $expectedTotal = $totalPayment; + $adjustment = bcsub($expectedTotal, $displayedTotal, 5); + } + + $detail = [ + 'DocNo' => '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction->bill_no, + 'ShipInfo' => $this->marking, + 'AccNo' => '500-0000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => 'Adjustment', + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => '1', + 'UnitPrice' => $adjustment ? number_format($adjustment, 2) : '0', + 'submiteinvoice' => '', + 'ConsolidatedEinvoice' => '', + ]; + $details->push($detail); + // Adjustment - Ends + + return AutocountSalesInvoiceBookingDetailsResource::collection( + collect($details) + ); + } +} diff --git a/routes/apipub.php b/routes/apipub.php index b2a5e2ce..3fdbcd80 100644 --- a/routes/apipub.php +++ b/routes/apipub.php @@ -1,9 +1,14 @@ 'apipub', 'prefix' => 'v1', 'as' => 'apipub.'], function () { Route::group(['middleware' => 'token.check'], function () { Route::get('transactions/mappable/query', 'Transactions\ListMappableTransactionsController@list')->name('transaction.mappable.list'); + + Route::group(['prefix' => 'booking', 'as' => 'booking.'], function () { + Route::get('/sales-invoice', [ListBookingsSalesInvoiceController::class, 'list'])->name('booking.sales-invoice.list'); + }); }); }); From 8cc81cc7f9f415d7a13bfd429e9b60618f2481dc Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Fri, 10 Oct 2025 02:00:20 +0800 Subject: [PATCH 02/20] E-Invoice - API, Sales Invoice Report with update post request --- .../Eloquent/Filters/DateRangeNoData.php | 4 ++ .../UpdateBookingSalesInvoiceLogic.php | 72 +++++++++++++++++++ .../UpdateBookingSalesInvoiceController.php | 20 ++++++ routes/apipub.php | 4 +- 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php create mode 100644 app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php index 25f9a343..a825ed0a 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -35,6 +35,10 @@ class DateRangeNoData implements Filter 'transactions.transactionDetails', 'transactions.voucherRedemption', ]) + + // ->whereHas('company', function ($query) { + // $query->where('debtor', '!=', 'N/A NO NEED TO IMPORT'); + // }) ->whereHas('transactions', function ($query) use ($startDate, $endDate){ $query->payments() ->complete() diff --git a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php new file mode 100644 index 00000000..47d8cc79 --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php @@ -0,0 +1,72 @@ + 'Updated Booking with Sales Invoice', + 'message' => 'You have successfully updated Booking with Sales Invoice' + ]; + } + + /** @var CanUpdateBooking */ + private $canUpdateBooking; + + + /** + * UpdateBookingSalesInvoiceLogic constructor. + * @param CanUpdateBooking $canUpdateBooking + */ + public function __construct( + CanUpdateBooking $canUpdateBooking + ) + { + $this->canUpdateBooking = $canUpdateBooking; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws ErrorException + */ + public function logic(Request $request) : JsonResponse + { + $data = [ + 'docno' => $request->input('DocNo'), + 'docdate' => $request->input('DocDate'), + 'debtorcode' => $request->input('DebtorCode'), + 'ref' => $request->input('Ref'), + 'shipinfo' => $request->input('ShipInfo'), + 'accno' => $request->input('AccNo'), + 'detaildescription' => $request->input('DetailDescription'), + 'furtherdescription' => $request->input('FurtherDescription'), + 'classification' => $request->input('Classification'), + 'deptno' => $request->input('DeptNo'), + 'qty' => $request->input('Qty'), + 'unitprice' => $request->input('UnitPrice'), + 'submiteinvoice' => $request->input('SubmitEinvoice'), + 'consolidatedeinvoice' => $request->input('ConsolidatedEinvoice'), + 'einvoicevalidationlink' => $request->input('EInvoiceValidationLink'), + ]; + + Log::info('UpdateBookingSalesInvoiceLogic: ' . json_encode($data)); + + // ProcessSalesInvoiceReportV2CommandJob::dispatch($data); + + return $this->response([]); + } +} diff --git a/app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php b/app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php new file mode 100644 index 00000000..c7aec55a --- /dev/null +++ b/app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php @@ -0,0 +1,20 @@ +execute($request); + } +} diff --git a/routes/apipub.php b/routes/apipub.php index 3fdbcd80..5884f946 100644 --- a/routes/apipub.php +++ b/routes/apipub.php @@ -1,6 +1,7 @@ 'apipub', 'prefix' => 'v1', 'as' => 'apipub.'], function () { @@ -8,7 +9,8 @@ Route::group(['middleware' => 'apipub', 'prefix' => 'v1', 'as' => 'apipub.'], fu Route::get('transactions/mappable/query', 'Transactions\ListMappableTransactionsController@list')->name('transaction.mappable.list'); Route::group(['prefix' => 'booking', 'as' => 'booking.'], function () { - Route::get('/sales-invoice', [ListBookingsSalesInvoiceController::class, 'list'])->name('booking.sales-invoice.list'); + Route::get('/sales-invoice/list', [ListBookingsSalesInvoiceController::class, 'list'])->name('booking.sales-invoice.list'); + Route::post('/sales-invoice', [UpdateBookingSalesInvoiceController::class, 'update'])->name('booking.sales-invoice.update'); }); }); }); From ddba628070f2fe3f6b1860c9bec2e27945af3d79 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Fri, 10 Oct 2025 10:59:21 +0800 Subject: [PATCH 03/20] E-Invoice - API, Sales Invoice Report GET request --- .../AutocountSalesInvoiceBookingDetailsResource.php | 2 +- app/Http/Resources/AutocountSalesInvoiceBookingResource.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php index fa00fdc6..647fe915 100644 --- a/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php +++ b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php @@ -27,7 +27,7 @@ class AutocountSalesInvoiceBookingDetailsResource extends JsonResource 'DeptNo' => $this['DeptNo'], 'Qty' => $this['Qty'], 'UnitPrice' => $this['UnitPrice'], - 'submiteinvoice' => $this['submiteinvoice'], + 'SubmitEinvoice' => $this['SubmitEinvoice'], 'ConsolidatedEinvoice' => $this['ConsolidatedEinvoice'], ]; } diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php index f02d08be..500fe1aa 100644 --- a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php +++ b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php @@ -101,7 +101,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'DeptNo' => 'C', 'Qty' => number_format($detail->quantity, 0), 'UnitPrice' => $displayUnitPrice ? number_format($displayUnitPrice, 2) : '0', - 'submiteinvoice' => $firstItem ? 'T' : '', + 'SubmitEinvoice' => $firstItem ? 'T' : '', 'ConsolidatedEinvoice' => $firstItem ? ($company->e_invoice ? 'F' : 'T') : '', ]; $details->push($detail); @@ -138,7 +138,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'DeptNo' => 'C', 'Qty' => '1', 'UnitPrice' => $serviceCharge ? number_format($serviceCharge, 2) : '0', - 'submiteinvoice' => '', + 'SubmitEinvoice' => '', 'ConsolidatedEinvoice' => '', ]; $details->push($detail); @@ -185,7 +185,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'DeptNo' => 'C', 'Qty' => '1', 'UnitPrice' => $adjustment ? number_format($adjustment, 2) : '0', - 'submiteinvoice' => '', + 'SubmitEinvoice' => '', 'ConsolidatedEinvoice' => '', ]; $details->push($detail); From 74d287550e76e2a5ad750136015a276b69043718 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Mon, 13 Oct 2025 14:21:04 +0800 Subject: [PATCH 04/20] E-Invoice - API, Sales Invoice Report with update post request to include validation --- .../UpdateBookingSalesInvoiceLogic.php | 48 ++--- .../UpdateBookingSalesInvoiceDTO.php | 184 ++++++++++++++++++ .../Rules/CanUpdateBookingSalesInvoice.php | 54 +++++ .../BookingSalesInvoiceValidation.php | 57 ++++++ 4 files changed, 320 insertions(+), 23 deletions(-) create mode 100644 app/Classes/Modules/Bookings/DataTransferObjects/UpdateBookingSalesInvoiceDTO.php create mode 100644 app/Classes/Modules/Bookings/Standards/Rules/CanUpdateBookingSalesInvoice.php create mode 100644 app/Classes/Modules/Bookings/Standards/Validators/BookingSalesInvoiceValidation.php diff --git a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php index 47d8cc79..ef604c2c 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php @@ -4,7 +4,8 @@ namespace App\Classes\Modules\Bookings\ControllersLogic; use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Jobs\Commands\V2\ProcessSalesInvoiceReportV2CommandJob; -use App\Classes\Modules\Bookings\Standards\Rules\CanUpdateBooking; +use App\Classes\Modules\Bookings\DataTransferObjects\UpdateBookingSalesInvoiceDTO; +use App\Classes\Modules\Bookings\Standards\Rules\CanUpdateBookingSalesInvoice; use ErrorException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -23,19 +24,19 @@ class UpdateBookingSalesInvoiceLogic extends AbstractControllerLogic ]; } - /** @var CanUpdateBooking */ - private $canUpdateBooking; + /** @var CanUpdateBookingSalesInvoice */ + private $canUpdateBookingSalesInvoice; /** * UpdateBookingSalesInvoiceLogic constructor. - * @param CanUpdateBooking $canUpdateBooking + * @param CanUpdateBookingSalesInvoice $canUpdateBookingSalesInvoice */ public function __construct( - CanUpdateBooking $canUpdateBooking + CanUpdateBookingSalesInvoice $canUpdateBookingSalesInvoice ) { - $this->canUpdateBooking = $canUpdateBooking; + $this->canUpdateBookingSalesInvoice = $canUpdateBookingSalesInvoice; } /** @@ -46,26 +47,27 @@ class UpdateBookingSalesInvoiceLogic extends AbstractControllerLogic public function logic(Request $request) : JsonResponse { $data = [ - 'docno' => $request->input('DocNo'), - 'docdate' => $request->input('DocDate'), - 'debtorcode' => $request->input('DebtorCode'), - 'ref' => $request->input('Ref'), - 'shipinfo' => $request->input('ShipInfo'), - 'accno' => $request->input('AccNo'), - 'detaildescription' => $request->input('DetailDescription'), - 'furtherdescription' => $request->input('FurtherDescription'), - 'classification' => $request->input('Classification'), - 'deptno' => $request->input('DeptNo'), - 'qty' => $request->input('Qty'), - 'unitprice' => $request->input('UnitPrice'), - 'submiteinvoice' => $request->input('SubmitEinvoice'), - 'consolidatedeinvoice' => $request->input('ConsolidatedEinvoice'), - 'einvoicevalidationlink' => $request->input('EInvoiceValidationLink'), + 'docno' => $request->input('docno', $request->input('DocNo')), + 'docdate' => $request->input('docdate', $request->input('DocDate')), + 'debtorcode' => $request->input('debtorcode', $request->input('DebtorCode')), + 'ref' => $request->input('ref', $request->input('Ref')), + 'shipinfo' => $request->input('shipinfo', $request->input('ShipInfo')), + 'accno' => $request->input('accno', $request->input('AccNo')), + 'detaildescription' => $request->input('detaildescription', $request->input('DetailDescription')), + 'furtherdescription' => $request->input('furtherdescription', $request->input('FurtherDescription')), + 'classification' => $request->input('classification', $request->input('Classification')), + 'deptno' => $request->input('deptno', $request->input('DeptNo')), + 'qty' => $request->input('qty', $request->input('Qty')), + 'unitprice' => $request->input('unitprice', $request->input('UnitPrice')), + 'submiteinvoice' => $request->input('submiteinvoice', $request->input('SubmitEinvoice')), + 'consolidatedeinvoice' => $request->input('consolidatedeinvoice', $request->input('ConsolidatedEinvoice')), + 'einvoicevalidationlink' => $request->input('einvoicevalidationlink', $request->input('EInvoiceValidationLink')), ]; - Log::info('UpdateBookingSalesInvoiceLogic: ' . json_encode($data)); + $dto = new UpdateBookingSalesInvoiceDTO($data); + $this->canUpdateBookingSalesInvoice->passes($dto); - // ProcessSalesInvoiceReportV2CommandJob::dispatch($data); + ProcessSalesInvoiceReportV2CommandJob::dispatch($dto->toArray()); return $this->response([]); } diff --git a/app/Classes/Modules/Bookings/DataTransferObjects/UpdateBookingSalesInvoiceDTO.php b/app/Classes/Modules/Bookings/DataTransferObjects/UpdateBookingSalesInvoiceDTO.php new file mode 100644 index 00000000..096749f2 --- /dev/null +++ b/app/Classes/Modules/Bookings/DataTransferObjects/UpdateBookingSalesInvoiceDTO.php @@ -0,0 +1,184 @@ +docNo = $data['docno']; + $this->docDate = $data['docdate']; + $this->debtorCode = $data['debtorcode']; + $this->ref = $data['ref'] ?? null; + $this->shipInfo = $data['shipinfo'] ?? null; + $this->accNo = $data['accno'] ?? null; + $this->detailDescription = $data['detaildescription'] ?? null; + $this->furtherDescription = $data['furtherdescription'] ?? null; + $this->classification = $data['classification'] ?? null; + $this->deptNo = $data['deptno'] ?? null; + $this->qty = (float) ($data['qty'] ?? 0); + $this->unitPrice = (float) ($data['unitprice'] ?? 0); + $this->submitEinvoice = filter_var($data['submiteinvoice'] ?? false, FILTER_VALIDATE_BOOLEAN); + $this->consolidatedEinvoice = filter_var($data['consolidatedeinvoice'] ?? false, FILTER_VALIDATE_BOOLEAN); + $this->einvoiceValidationLink = $data['einvoicevalidationlink'] ?? null; + } + + /** + * @return string|null + */ + public function getDocNo(): ?string + { + return $this->docNo; + } + + /** + * @return string|null + */ + public function getDocDate(): ?string + { + return $this->docDate; + } + + /** + * @return string|null + */ + public function getDebtorCode(): ?string + { + return $this->debtorCode; + } + + /** + * @return string|null + */ + public function getRef(): ?string + { + return $this->ref; + } + + /** + * @return string|null + */ + public function getShipInfo(): ?string + { + return $this->shipInfo; + } + + /** + * @return string|null + */ + public function getAccNo(): ?string + { + return $this->accNo; + } + + /** + * @return string|null + */ + public function getDetailDescription(): ?string + { + return $this->detailDescription; + } + + /** + * @return string|null + */ + public function getFurtherDescription(): ?string + { + return $this->furtherDescription; + } + + /** + * @return string|null + */ + public function getClassification(): ?string + { + return $this->classification; + } + + /** + * @return string|null + */ + public function getDeptNo(): ?string + { + return $this->deptNo; + } + + /** + * @return float + */ + public function getQty(): float + { + return $this->qty; + } + + /** + * @return float + */ + public function getUnitPrice(): float + { + return $this->unitPrice; + } + + /** + * @return bool + */ + public function getSubmitEinvoice(): bool + { + return $this->submitEinvoice; + } + + /** + * @return bool + */ + public function getConsolidatedEinvoice(): bool + { + return $this->consolidatedEinvoice; + } + + /** + * @return string|null + */ + public function getEinvoiceValidationLink(): ?string + { + return $this->einvoiceValidationLink; + } + + public function toArray(): array + { + return [ + 'docno' => $this->docNo, + 'docdate' => $this->docDate, + 'debtorcode' => $this->debtorCode, + 'ref' => $this->ref, + 'shipinfo' => $this->shipInfo, + 'accno' => $this->accNo, + 'detaildescription' => $this->detailDescription, + 'furtherdescription' => $this->furtherDescription, + 'classification' => $this->classification, + 'deptno' => $this->deptNo, + 'qty' => $this->qty, + 'unitprice' => $this->unitPrice, + 'submiteinvoice' => $this->submitEinvoice, + 'consolidatedeinvoice' => $this->consolidatedEinvoice, + 'einvoicevalidationlink' => $this->einvoiceValidationLink, + ]; + } +} diff --git a/app/Classes/Modules/Bookings/Standards/Rules/CanUpdateBookingSalesInvoice.php b/app/Classes/Modules/Bookings/Standards/Rules/CanUpdateBookingSalesInvoice.php new file mode 100644 index 00000000..de06dafb --- /dev/null +++ b/app/Classes/Modules/Bookings/Standards/Rules/CanUpdateBookingSalesInvoice.php @@ -0,0 +1,54 @@ +bookingSalesInvoiceValidation = $bookingSalesInvoiceValidation; + } + + /** + * @return bool + */ + protected function authorized($object): bool + { + return true; + } + + /** + * @param UpdateBookingSalesInvoiceDTO $object + * @return bool + * @throws \App\Classes\Exceptions\RequestValidationException + */ + protected function validators($object): bool + { + return $this->bookingSalesInvoiceValidation->validate($object); + } + + /** + * @param UpdateBookingSalesInvoiceDTO $object + * @return bool + */ + protected function criteria($object): bool + { + return true; + } + +} diff --git a/app/Classes/Modules/Bookings/Standards/Validators/BookingSalesInvoiceValidation.php b/app/Classes/Modules/Bookings/Standards/Validators/BookingSalesInvoiceValidation.php new file mode 100644 index 00000000..b13037ff --- /dev/null +++ b/app/Classes/Modules/Bookings/Standards/Validators/BookingSalesInvoiceValidation.php @@ -0,0 +1,57 @@ + $object->getDocNo(), + 'docdate' => $object->getDocDate(), + 'debtorcode' => $object->getDebtorCode(), + 'ref' => $object->getRef(), + 'shipinfo' => $object->getShipInfo(), + 'accno' => $object->getAccNo(), + 'detaildescription' => $object->getDetailDescription(), + 'furtherdescription' => $object->getFurtherDescription(), + 'classification' => $object->getClassification(), + 'deptno' => $object->getDeptNo(), + 'qty' => $object->getQty(), + 'unitprice' => $object->getUnitPrice(), + 'submiteinvoice' => $object->getSubmitEinvoice(), + 'consolidatedeinvoice' => $object->getConsolidatedEinvoice(), + 'einvoicevalidationlink' => $object->getEinvoiceValidationLink(), + ]; + } + + /** + * @return array + */ + protected function rules(): array + { + return [ + 'docno' => 'required|string', + 'docdate' => 'required|date', + 'debtorcode' => 'required|string', + 'ref' => 'required|string', + 'shipinfo' => 'required|string', + 'einvoicevalidationlink' => 'required|string', + ]; + } + + /** + * @return array + */ + protected function messages(): array + { + return []; + } +} From 7ccf83bb95cf64e7bc6809995ed19d821bf99ed0 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 14 Oct 2025 13:19:01 +0800 Subject: [PATCH 05/20] E-Invoice - API, Sales Invoice Report with update post request to include validation --- .../ControllersLogic/UpdateBookingSalesInvoiceLogic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php index ef604c2c..ba994141 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php @@ -67,7 +67,7 @@ class UpdateBookingSalesInvoiceLogic extends AbstractControllerLogic $dto = new UpdateBookingSalesInvoiceDTO($data); $this->canUpdateBookingSalesInvoice->passes($dto); - ProcessSalesInvoiceReportV2CommandJob::dispatch($dto->toArray()); + // ProcessSalesInvoiceReportV2CommandJob::dispatch($dto->toArray()); return $this->response([]); } From 28c12f440257e0a94d741730f91118f942b02140 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 14 Oct 2025 13:34:18 +0800 Subject: [PATCH 06/20] E-Invoice - API, Sales Invoice Report - added more checking and validation --- .../ListBookingsSalesInvoiceLogic.php | 43 +++++++++++++++++++ .../Rules/CanListBookingsSalesInvoice.php | 40 +++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 app/Classes/Modules/Bookings/Standards/Rules/CanListBookingsSalesInvoice.php diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php index a2134295..ddaacd85 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php @@ -43,6 +43,49 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic public function logic(Request $request) : JsonResponse { // $this->canListBookings->passes(); //cief todo: 90 - apipub + $filters = $request->input('filters'); + + // Decode JSON if sent as string + if (is_string($filters)) { + $filters = json_decode($filters, true); + } + + if (!is_array($filters)) { + return response()->json([ + 'success' => false, + 'message' => 'Invalid filters format. Expected JSON object.' + ], 400); + } + + // Only these filters are allowed + $allowedKeys = ['per_page', 'status', 'order_by', 'date_range', 'date_range_no_data']; + + $extra = array_diff(array_keys($filters), $allowedKeys); + if (!empty($extra)) { + return response()->json([ + 'success' => false, + 'message' => 'Invalid filter(s) provided: ' . implode(', ', $extra), + ], 400); + } + + // Required filters that must always exist + $alwaysRequired = ['per_page', 'status']; + + $missing = array_diff($alwaysRequired, array_keys($filters)); + if (!empty($missing)) { + return response()->json([ + 'success' => false, + 'message' => 'Missing required filter(s): ' . implode(', ', $missing), + ], 400); + } + + // Required at least one of: date_range or date_range_no_data + if (!isset($filters['date_range']) && !isset($filters['date_range_no_data'])) { + return response()->json([ + 'success' => false, + 'message' => 'Either "date_range" or "date_range_no_data" filter is required.', + ], 400); + } $query = $this->listsBookings->execute($this->listsBookings->deserializeFilters($request->input('filters'))); diff --git a/app/Classes/Modules/Bookings/Standards/Rules/CanListBookingsSalesInvoice.php b/app/Classes/Modules/Bookings/Standards/Rules/CanListBookingsSalesInvoice.php new file mode 100644 index 00000000..b2f409da --- /dev/null +++ b/app/Classes/Modules/Bookings/Standards/Rules/CanListBookingsSalesInvoice.php @@ -0,0 +1,40 @@ + Date: Tue, 14 Oct 2025 13:53:28 +0800 Subject: [PATCH 07/20] E-Invoice - API, Sales Invoice Report - added more checking and validation --- .../ListBookingsSalesInvoiceLogic.php | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php index ddaacd85..67a092fc 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php @@ -43,17 +43,16 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic public function logic(Request $request) : JsonResponse { // $this->canListBookings->passes(); //cief todo: 90 - apipub - $filters = $request->input('filters'); - // Decode JSON if sent as string + $filters = $request->input('filters'); if (is_string($filters)) { $filters = json_decode($filters, true); } if (!is_array($filters)) { return response()->json([ - 'success' => false, - 'message' => 'Invalid filters format. Expected JSON object.' + 'message' => 'Invalid filters format. Expected JSON object.', + 'payload' => new \stdClass(), ], 400); } @@ -63,8 +62,8 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic $extra = array_diff(array_keys($filters), $allowedKeys); if (!empty($extra)) { return response()->json([ - 'success' => false, 'message' => 'Invalid filter(s) provided: ' . implode(', ', $extra), + 'payload' => new \stdClass(), ], 400); } @@ -74,16 +73,23 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic $missing = array_diff($alwaysRequired, array_keys($filters)); if (!empty($missing)) { return response()->json([ - 'success' => false, 'message' => 'Missing required filter(s): ' . implode(', ', $missing), + 'payload' => new \stdClass(), + ], 400); + } + + if ((int) $filters['status'] !== 3) { + return response()->json([ + 'message' => 'Invalid status value.', + 'payload' => new \stdClass(), ], 400); } // Required at least one of: date_range or date_range_no_data if (!isset($filters['date_range']) && !isset($filters['date_range_no_data'])) { return response()->json([ - 'success' => false, 'message' => 'Either "date_range" or "date_range_no_data" filter is required.', + 'payload' => new \stdClass(), ], 400); } From 3992d0e0fb5f601c5a6f8314a55e803c87159aff Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 14 Oct 2025 14:11:52 +0800 Subject: [PATCH 08/20] E-Invoice - API, Sales Invoice Report - added more checking and validation --- app/Classes/General/Eloquent/Filters/DateRange.php | 6 ++++++ app/Classes/General/Eloquent/Filters/DateRangeNoData.php | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/Classes/General/Eloquent/Filters/DateRange.php b/app/Classes/General/Eloquent/Filters/DateRange.php index f1ffc82a..28092da0 100644 --- a/app/Classes/General/Eloquent/Filters/DateRange.php +++ b/app/Classes/General/Eloquent/Filters/DateRange.php @@ -28,6 +28,12 @@ class DateRange implements Filter ? Carbon::createFromFormat('Y-m-d', $dt2)->endOfDay() : Carbon::now()->endOfDay(); + $minAllowedDate = Carbon::createFromFormat('Y-m-d', '2025-07-01')->startOfDay(); + if ($startDate->lt($minAllowedDate) || $endDate->lt($minAllowedDate)) { + $startDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); + $endDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); + } + return $builder->with([ 'company', 'transactions.transactionDetails', diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php index a825ed0a..3bfb27e6 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -30,6 +30,12 @@ class DateRangeNoData implements Filter ? Carbon::createFromFormat('Y-m-d', $dt2)->endOfDay() : Carbon::now()->endOfDay(); + $minAllowedDate = Carbon::createFromFormat('Y-m-d', '2025-07-01')->startOfDay(); + if ($startDate->lt($minAllowedDate) || $endDate->lt($minAllowedDate)) { + $startDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); + $endDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); + } + return $builder->with([ 'company', 'transactions.transactionDetails', From 453f25335aa469dbf181c738fa366af979b99c0f Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 16 Oct 2025 13:22:37 +0800 Subject: [PATCH 09/20] E-Invoice - API, Sales Invoice Report (update) --- .../AutocountSalesInvoiceBookingResource.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php index 500fe1aa..2dfeefaa 100644 --- a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php +++ b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php @@ -7,6 +7,7 @@ use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Models\Booking; use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount; use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundServiceCharge; +use App\Classes\ValueObjects\Constants\KVPKey; use Illuminate\Http\Resources\Json\JsonResource; use Carbon\Carbon; use Illuminate\Support\Facades\Log; @@ -75,7 +76,19 @@ class AutocountSalesInvoiceBookingResource extends JsonResource $formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y'); + $docNo = '<>'; $firstItem = true; + if($invoiceTransaction){ + $invoiceTransactionKVP = $invoiceTransaction->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first(); + if($invoiceTransactionKVP){ + $docNo = $invoiceTransactionKVP->value; + } + else { + $bookingKVP = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first(); + $docNo = $bookingKVP ? $bookingKVP->value :'<>'; + } + } + $transactionDetails = $purchaseOrder->transactionDetails; foreach ($transactionDetails as $detail) { $displayUnitPrice = 0; @@ -89,7 +102,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource } $detail = [ - 'DocNo' => $firstItem ? '<>' : '', + 'DocNo' => $firstItem ? $docNo : '', 'DocDate' => $formattedDocumentDate, 'DebtorCode' => $company->debtor, 'Ref' => $invoiceTransaction->bill_no, From 03a8244ea92cf4e6dc974745313512670e86fae7 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Sat, 18 Oct 2025 14:22:24 +0800 Subject: [PATCH 10/20] E-Invoice - API, Sales Invoice Report (update) --- .../ControllersLogic/ListBookingsSalesInvoiceLogic.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php index 67a092fc..493be93f 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php @@ -92,6 +92,12 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic 'payload' => new \stdClass(), ], 400); } + $filters = $request->input('filters'); + if (is_string($filters)) { + $decoded = json_decode($filters, true) ?? []; + $decoded['order_by'] = ['column' => 'id', 'DESC' => false]; + $request->merge(['filters' => json_encode($decoded)]); + } $query = $this->listsBookings->execute($this->listsBookings->deserializeFilters($request->input('filters'))); From 0604d098b957a92a18dc81bf5faf25e781e3f456 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Sat, 18 Oct 2025 16:19:00 +0800 Subject: [PATCH 11/20] E-Invoice - API, Sales Invoice Report with batch update post request --- .../UpdateBatchBookingSalesInvoiceLogic.php | 93 +++++++++++++++++++ .../UpdateBookingSalesInvoiceLogic.php | 2 +- .../UpdateBookingSalesInvoiceController.php | 9 ++ routes/apipub.php | 1 + 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 app/Classes/Modules/Bookings/ControllersLogic/UpdateBatchBookingSalesInvoiceLogic.php diff --git a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBatchBookingSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBatchBookingSalesInvoiceLogic.php new file mode 100644 index 00000000..55a863eb --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBatchBookingSalesInvoiceLogic.php @@ -0,0 +1,93 @@ + 'Updated Bookings with Sales Invoices', + 'message' => 'You have successfully updated multiple bookings with Sales Invoices', + ]; + } + + /** @var CanUpdateBookingSalesInvoice */ + private $canUpdateBookingSalesInvoice; + + /** + * UpdateBatchBookingSalesInvoiceLogic constructor. + * @param CanUpdateBookingSalesInvoice $canUpdateBookingSalesInvoice + */ + public function __construct( + CanUpdateBookingSalesInvoice $canUpdateBookingSalesInvoice + ) { + $this->canUpdateBookingSalesInvoice = $canUpdateBookingSalesInvoice; + } + + /** + * Handles batch update for multiple booking sales invoices + * + * @param Request $request + * @return JsonResponse + * @throws ErrorException + */ + public function logic(Request $request): JsonResponse + { + $collections = $request->input('collections', []); + + if (!is_array($collections) || empty($collections)) { + return response()->json([ + 'error' => 'Invalid or empty collections array provided.' + ], 400); + } + + $jobs = []; + $processed = []; + + foreach ($collections as $index => $bookingData) { + try { + $data = [ + 'docno' => $bookingData['docno'] ?? $bookingData['DocNo'] ?? null, + 'docdate' => $bookingData['docdate'] ?? $bookingData['DocDate'] ?? null, + 'debtorcode' => $bookingData['debtorcode'] ?? $bookingData['DebtorCode'] ?? null, + 'ref' => $bookingData['ref'] ?? $bookingData['Ref'] ?? null, + 'shipinfo' => $bookingData['shipinfo'] ?? $bookingData['ShipInfo'] ?? null, + 'accno' => $bookingData['accno'] ?? $bookingData['AccNo'] ?? null, + 'detaildescription' => $bookingData['detaildescription'] ?? $bookingData['DetailDescription'] ?? null, + 'furtherdescription' => $bookingData['furtherdescription'] ?? $bookingData['FurtherDescription'] ?? null, + 'classification' => $bookingData['classification'] ?? $bookingData['Classification'] ?? null, + 'deptno' => $bookingData['deptno'] ?? $bookingData['DeptNo'] ?? null, + 'qty' => $bookingData['qty'] ?? $bookingData['Qty'] ?? null, + 'unitprice' => $bookingData['unitprice'] ?? $bookingData['UnitPrice'] ?? null, + 'submiteinvoice' => $bookingData['submiteinvoice'] ?? $bookingData['SubmitEinvoice'] ?? null, + 'consolidatedeinvoice' => $bookingData['consolidatedeinvoice'] ?? $bookingData['ConsolidatedEinvoice'] ?? null, + 'einvoicevalidationlink' => $bookingData['einvoicevalidationlink'] ?? $bookingData['EInvoiceValidationLink'] ?? null, + ]; + + $dto = new UpdateBookingSalesInvoiceDTO($data); + $this->canUpdateBookingSalesInvoice->passes($dto); + + ProcessSalesInvoiceReportV2CommandJob::dispatch($dto->toArray()); + + } catch (\Throwable $e) { + Log::error("Failed to process booking at index {$index}: " . $e->getMessage()); + } + } + + return $this->response([]); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php index ba994141..ef604c2c 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingSalesInvoiceLogic.php @@ -67,7 +67,7 @@ class UpdateBookingSalesInvoiceLogic extends AbstractControllerLogic $dto = new UpdateBookingSalesInvoiceDTO($data); $this->canUpdateBookingSalesInvoice->passes($dto); - // ProcessSalesInvoiceReportV2CommandJob::dispatch($dto->toArray()); + ProcessSalesInvoiceReportV2CommandJob::dispatch($dto->toArray()); return $this->response([]); } diff --git a/app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php b/app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php index c7aec55a..6339f464 100644 --- a/app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php +++ b/app/Http/Controllers/Bookings/UpdateBookingSalesInvoiceController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Bookings; use App\Classes\Modules\Bookings\ControllersLogic\UpdateBookingSalesInvoiceLogic; +use App\Classes\Modules\Bookings\ControllersLogic\UpdateBatchBookingSalesInvoiceLogic; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -17,4 +18,12 @@ class UpdateBookingSalesInvoiceController public function update(Request $request, UpdateBookingSalesInvoiceLogic $logic) : JsonResponse { return $logic->execute($request); } + + /** + * @param Request $request + * @return JsonResponse + */ + public function updateBatch(Request $request, UpdateBatchBookingSalesInvoiceLogic $logic) : JsonResponse { + return $logic->execute($request); + } } diff --git a/routes/apipub.php b/routes/apipub.php index 5884f946..6e2cdb19 100644 --- a/routes/apipub.php +++ b/routes/apipub.php @@ -11,6 +11,7 @@ Route::group(['middleware' => 'apipub', 'prefix' => 'v1', 'as' => 'apipub.'], fu Route::group(['prefix' => 'booking', 'as' => 'booking.'], function () { Route::get('/sales-invoice/list', [ListBookingsSalesInvoiceController::class, 'list'])->name('booking.sales-invoice.list'); Route::post('/sales-invoice', [UpdateBookingSalesInvoiceController::class, 'update'])->name('booking.sales-invoice.update'); + Route::post('/sales-invoice/batch', [UpdateBookingSalesInvoiceController::class, 'updateBatch'])->name('booking.sales-invoice.batch.update'); }); }); }); From 53afbc5284c701ef05d588dea78aba3cebcf70be Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Sat, 18 Oct 2025 17:10:49 +0800 Subject: [PATCH 12/20] E-Invoice - API, Sales Invoice Report Get request new filter --- .../Eloquent/Filters/DateRangeNoData.php | 3 + .../Eloquent/Filters/DateRangeWithData.php | 73 +++++++++++++++++++ .../ListBookingsSalesInvoiceLogic.php | 6 +- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 app/Classes/General/Eloquent/Filters/DateRangeWithData.php diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php index 3bfb27e6..278fe9dd 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -65,6 +65,9 @@ class DateRangeNoData implements Filter ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); }); + }) + ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); }); } } diff --git a/app/Classes/General/Eloquent/Filters/DateRangeWithData.php b/app/Classes/General/Eloquent/Filters/DateRangeWithData.php new file mode 100644 index 00000000..12fd2d67 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/DateRangeWithData.php @@ -0,0 +1,73 @@ +startOfDay() + : Carbon::now()->subMonth()->startOfDay(); + + $endDate = isset($dt2) && $dt2 + ? Carbon::createFromFormat('Y-m-d', $dt2)->endOfDay() + : Carbon::now()->endOfDay(); + + $minAllowedDate = Carbon::createFromFormat('Y-m-d', '2025-07-01')->startOfDay(); + if ($startDate->lt($minAllowedDate) || $endDate->lt($minAllowedDate)) { + $startDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); + $endDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); + } + + return $builder->with([ + 'company', + 'transactions.transactionDetails', + 'transactions.voucherRedemption', + ]) + + // ->whereHas('company', function ($query) { + // $query->where('debtor', '!=', 'N/A NO NEED TO IMPORT'); + // }) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]) + ->latest('created_at'); + }) + ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->complete() + ->where(function ($q) use ($startDate, $endDate) { + $q->where('created_at', '<', $startDate) + ->orWhere('created_at', '>', $endDate); + }); + }) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->where('type', TransactionType::INVOICE) + ->latest('created_at') + ->whereHas('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }); + // ->WhereHas('attributesKVP', function ($invoiceQuery) { + // $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + // }); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php index 493be93f..b0a330b7 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php @@ -57,7 +57,7 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic } // Only these filters are allowed - $allowedKeys = ['per_page', 'status', 'order_by', 'date_range', 'date_range_no_data']; + $allowedKeys = ['per_page', 'status', 'order_by', 'date_range', 'date_range_no_data', 'date_range_with_data']; $extra = array_diff(array_keys($filters), $allowedKeys); if (!empty($extra)) { @@ -86,9 +86,9 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic } // Required at least one of: date_range or date_range_no_data - if (!isset($filters['date_range']) && !isset($filters['date_range_no_data'])) { + if (!isset($filters['date_range']) && !isset($filters['date_range_no_data']) && !isset($filters['date_range_with_data'])) { return response()->json([ - 'message' => 'Either "date_range" or "date_range_no_data" filter is required.', + 'message' => 'Either "date_range" or "date_range_no_data" or "date_range_with_data" filter is required.', 'payload' => new \stdClass(), ], 400); } From 28911fb00e3556dc9eedc1a1df39de171a179da2 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 4 Nov 2025 08:57:48 +0800 Subject: [PATCH 13/20] E-Invoice - API, Sales Invoice Report (update with voucherify info) --- .../General/Eloquent/Filters/DateRange.php | 80 ++++++++--- .../Eloquent/Filters/DateRangeNoData.php | 126 +++++++++++++---- .../Eloquent/Filters/DateRangeWithData.php | 133 ++++++++++++++---- .../ListBookingsSalesInvoiceLogic.php | 1 + .../Services/ExportsSalesInvoiceReport.php | 5 + .../ExportsSalesInvoiceWithRefundReport.php | 5 + .../AutocountSalesInvoiceBookingResource.php | 116 +++++++++------ 7 files changed, 348 insertions(+), 118 deletions(-) diff --git a/app/Classes/General/Eloquent/Filters/DateRange.php b/app/Classes/General/Eloquent/Filters/DateRange.php index 28092da0..d55709b4 100644 --- a/app/Classes/General/Eloquent/Filters/DateRange.php +++ b/app/Classes/General/Eloquent/Filters/DateRange.php @@ -2,6 +2,7 @@ namespace App\Classes\General\Eloquent\Filters; +use App\Classes\ValueObjects\Constants\ApprovalStatus; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Log; @@ -34,24 +35,71 @@ class DateRange implements Filter $endDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); } - return $builder->with([ + // return $builder->with([ + // 'company', + // 'transactions.transactionDetails', + // 'transactions.voucherRedemption', + // ]) + // ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + // $query->payments() + // ->complete() + // ->whereBetween('created_at', [$startDate, $endDate]) + // ->latest('created_at'); + // }) + // ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + // $query->payments() + // ->complete() + // ->where(function ($q) use ($startDate, $endDate) { + // $q->where('created_at', '<', $startDate) + // ->orWhere('created_at', '>', $endDate); + // }); + // }); + + return $builder->whereIn('status', [ + ApprovalStatus::APPROVED, + ApprovalStatus::COMPLETED, + ApprovalStatus::SUSPENDED, + ]) + ->where(function ($q) use ($startDate, $endDate) { + // Bookings without refunds + $q->where(function ($q1) use ($startDate, $endDate) { + $q1->where('status', ApprovalStatus::COMPLETED) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]); + }) + ->whereDoesntHave('transactions', function ($query) { + $query->payments() + ->complete() + ->where(function ($q) { + $q->where('created_at', '<', '2025-07-01 00:00:00'); + }); + }); + }) + // OR bookings with refunds + ->orWhere(function ($q2) use ($startDate, $endDate) { + $q2->whereHas('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->whereBetween('created_at', [$startDate, $endDate]) + ->whereHas('transactions', function ($refundQuery) { + $refundQuery->refunds() + ->whereIn('status', [ApprovalStatus::APPROVED]); + }); + }) + ->whereDoesntHave('transactions', function ($query) { + $query->payments() + ->complete() + ->where(function ($q) { + $q->where('created_at', '<', '2025-07-01 00:00:00'); + }); + }); + }); + }) + ->with([ 'company', 'transactions.transactionDetails', 'transactions.voucherRedemption', - ]) - ->whereHas('transactions', function ($query) use ($startDate, $endDate){ - $query->payments() - ->complete() - ->whereBetween('created_at', [$startDate, $endDate]) - ->latest('created_at'); - }) - ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { - $query->payments() - ->complete() - ->where(function ($q) use ($startDate, $endDate) { - $q->where('created_at', '<', $startDate) - ->orWhere('created_at', '>', $endDate); - }); - }); + ]); } } diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php index 278fe9dd..833477ae 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -2,6 +2,7 @@ namespace App\Classes\General\Eloquent\Filters; +use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\KVPKey; use App\Classes\ValueObjects\Constants\TransactionType; use Carbon\Carbon; @@ -36,38 +37,103 @@ class DateRangeNoData implements Filter $endDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); } - return $builder->with([ + // return $builder->with([ + // 'company', + // 'transactions.transactionDetails', + // 'transactions.voucherRedemption', + // ]) + + // // ->whereHas('company', function ($query) { + // // $query->where('debtor', '!=', 'N/A NO NEED TO IMPORT'); + // // }) + // ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + // $query->payments() + // ->complete() + // ->whereBetween('created_at', [$startDate, $endDate]) + // ->latest('created_at'); + // }) + // ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + // $query->payments() + // ->complete() + // ->where(function ($q) use ($startDate, $endDate) { + // $q->where('created_at', '<', $startDate) + // ->orWhere('created_at', '>', $endDate); + // }); + // }) + // ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + // $query->where('type', TransactionType::INVOICE) + // ->latest('created_at') + // ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + // $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + // }); + // }) + // ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + // $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + // }); + + return $builder->whereIn('status', [ + ApprovalStatus::APPROVED, + ApprovalStatus::COMPLETED, + ApprovalStatus::SUSPENDED, + ]) + ->where(function ($q) use ($startDate, $endDate) { + // Bookings without refunds + $q->where(function ($q1) use ($startDate, $endDate) { + $q1->where('status', ApprovalStatus::COMPLETED) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]); + }) + ->whereDoesntHave('transactions', function ($query) { + $query->payments() + ->complete() + ->where(function ($q) { + $q->where('created_at', '<', '2025-07-01 00:00:00'); + }); + }) + ->whereHas('transactions', function ($query) { + $query->where('type', TransactionType::INVOICE) + ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }) + ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }) + // OR bookings with refunds + ->orWhere(function ($q2) use ($startDate, $endDate) { + $q2->whereHas('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->whereBetween('created_at', [$startDate, $endDate]) + ->whereHas('transactions', function ($refundQuery) { + $refundQuery->refunds() + ->whereIn('status', [ApprovalStatus::APPROVED]); + }); + }) + ->whereDoesntHave('transactions', function ($query) { + $query->payments() + ->complete() + ->where(function ($q) { + $q->where('created_at', '<', '2025-07-01 00:00:00'); + }); + }) + ->whereHas('transactions', function ($query) { + $query->where('type', TransactionType::INVOICE) + ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }) + ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }); + }) + ->with([ 'company', 'transactions.transactionDetails', 'transactions.voucherRedemption', - ]) - - // ->whereHas('company', function ($query) { - // $query->where('debtor', '!=', 'N/A NO NEED TO IMPORT'); - // }) - ->whereHas('transactions', function ($query) use ($startDate, $endDate){ - $query->payments() - ->complete() - ->whereBetween('created_at', [$startDate, $endDate]) - ->latest('created_at'); - }) - ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { - $query->payments() - ->complete() - ->where(function ($q) use ($startDate, $endDate) { - $q->where('created_at', '<', $startDate) - ->orWhere('created_at', '>', $endDate); - }); - }) - ->whereHas('transactions', function ($query) use ($startDate, $endDate){ - $query->where('type', TransactionType::INVOICE) - ->latest('created_at') - ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { - $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); - }); - }) - ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { - $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); - }); + ]); } } diff --git a/app/Classes/General/Eloquent/Filters/DateRangeWithData.php b/app/Classes/General/Eloquent/Filters/DateRangeWithData.php index 12fd2d67..61c5f437 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeWithData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeWithData.php @@ -2,6 +2,7 @@ namespace App\Classes\General\Eloquent\Filters; +use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\KVPKey; use App\Classes\ValueObjects\Constants\TransactionType; use Carbon\Carbon; @@ -36,38 +37,110 @@ class DateRangeWithData implements Filter $endDate = Carbon::createFromFormat('Y-m-d', '1970-01-01')->startOfDay(); } - return $builder->with([ + // return $builder->with([ + // 'company', + // 'transactions.transactionDetails', + // 'transactions.voucherRedemption', + // ]) + + // // ->whereHas('company', function ($query) { + // // $query->where('debtor', '!=', 'N/A NO NEED TO IMPORT'); + // // }) + // ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + // $query->payments() + // ->complete() + // ->whereBetween('created_at', [$startDate, $endDate]) + // ->latest('created_at'); + // }) + // ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + // $query->payments() + // ->complete() + // ->where(function ($q) use ($startDate, $endDate) { + // $q->where('created_at', '<', $startDate) + // ->orWhere('created_at', '>', $endDate); + // }); + // }) + // ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + // $query->where('type', TransactionType::INVOICE) + // ->latest('created_at') + // ->whereHas('attributesKVP', function ($invoiceQuery) { + // $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + // }); + // }); + // // ->WhereHas('attributesKVP', function ($invoiceQuery) { + // // $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + // // }); + + + return $builder->whereIn('status', [ + ApprovalStatus::APPROVED, + ApprovalStatus::COMPLETED, + ApprovalStatus::SUSPENDED, + ]) + ->where(function ($q) use ($startDate, $endDate) { + // Bookings without refunds + $q->where(function ($q1) use ($startDate, $endDate) { + $q1->where('status', ApprovalStatus::COMPLETED) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]); + }) + ->whereDoesntHave('transactions', function ($query) { + $query->payments() + ->complete() + ->where(function ($q) { + $q->where('created_at', '<', '2025-07-01 00:00:00'); + }); + }) + ->where(function ($subQuery){ + $subQuery + ->whereHas('transactions', function ($query) { + $query->where('type', TransactionType::INVOICE) + ->whereHas('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }) + ->orWhereHas('attributesKVP', function ($noInvoiceQuery) { + $noInvoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }); + }) + // OR bookings with refunds + ->orWhere(function ($q2) use ($startDate, $endDate) { + $q2->whereHas('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->whereBetween('created_at', [$startDate, $endDate]) + ->whereHas('transactions', function ($refundQuery) { + $refundQuery->refunds() + ->whereIn('status', [ApprovalStatus::APPROVED]); + }); + }) + ->whereDoesntHave('transactions', function ($query) { + $query->payments() + ->complete() + ->where(function ($q) { + $q->where('created_at', '<', '2025-07-01 00:00:00'); + }); + }) + ->where(function ($subQuery){ + $subQuery + ->whereHas('transactions', function ($query) { + $query->where('type', TransactionType::INVOICE) + ->whereHas('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }) + ->orWhereHas('attributesKVP', function ($noInvoiceQuery) { + $noInvoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }); + }); + }) + ->with([ 'company', 'transactions.transactionDetails', 'transactions.voucherRedemption', - ]) - - // ->whereHas('company', function ($query) { - // $query->where('debtor', '!=', 'N/A NO NEED TO IMPORT'); - // }) - ->whereHas('transactions', function ($query) use ($startDate, $endDate){ - $query->payments() - ->complete() - ->whereBetween('created_at', [$startDate, $endDate]) - ->latest('created_at'); - }) - ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { - $query->payments() - ->complete() - ->where(function ($q) use ($startDate, $endDate) { - $q->where('created_at', '<', $startDate) - ->orWhere('created_at', '>', $endDate); - }); - }) - ->whereHas('transactions', function ($query) use ($startDate, $endDate){ - $query->where('type', TransactionType::INVOICE) - ->latest('created_at') - ->whereHas('attributesKVP', function ($invoiceQuery) { - $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); - }); - }); - // ->WhereHas('attributesKVP', function ($invoiceQuery) { - // $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); - // }); + ]); } } diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php index b0a330b7..d8847d2d 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php @@ -95,6 +95,7 @@ class ListBookingsSalesInvoiceLogic extends AbstractControllerLogic $filters = $request->input('filters'); if (is_string($filters)) { $decoded = json_decode($filters, true) ?? []; + unset($decoded['status']); $decoded['order_by'] = ['column' => 'id', 'DESC' => false]; $request->merge(['filters' => json_encode($decoded)]); } diff --git a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceReport.php b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceReport.php index 3142441b..7257daec 100644 --- a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceReport.php +++ b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceReport.php @@ -80,6 +80,11 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR }); } + public function getRecordCount(): int + { + return $this->query()->count(); + } + /** * @param Booking $booking * @return array diff --git a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php index c6ac49cf..9f134a04 100644 --- a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php +++ b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php @@ -84,6 +84,11 @@ class ExportsSalesInvoiceWithRefundReport implements FromQuery, WithHeadings, Wi }); } + public function getRecordCount(): int + { + return $this->query()->count(); + } + /** * @param Booking $booking * @return array diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php index 2dfeefaa..8c134967 100644 --- a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php +++ b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php @@ -22,6 +22,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource */ public function toArray($request) { + $booking = $this; $details = collect(); $purchaseOrder = $this->transactions->where('type', TransactionType::PURCHASE_ORDER)->first(); $company = $this->company; @@ -32,6 +33,9 @@ class AutocountSalesInvoiceBookingResource extends JsonResource ->sortByDesc('created_at') ->first(); + if(!$lastPaymentTransaction){ + $lastPaymentTransaction = $this->transactions()->payments()->whereIn('status', [ApprovalStatus::COMPLETED, ApprovalStatus::REFUNDED])->latest()->first(); + } $documentDate = Carbon::parse($lastPaymentTransaction->created_at); // if ($documentDate < $this->startDate || $documentDate > $this->endDate) { @@ -49,7 +53,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource $subtotal = 0; $displayedSubtotal = 0; $totalPayment = 0; - $averageCurrencyRate = $invoiceTransaction->currency_rate; + $averageCurrencyRate = $invoiceTransaction ? $invoiceTransaction->currency_rate : 0; $paymentSum = $this->transactions ->where('type', TransactionType::PAYMENT) ->where('status', ApprovalStatus::COMPLETED) @@ -105,7 +109,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'DocNo' => $firstItem ? $docNo : '', 'DocDate' => $formattedDocumentDate, 'DebtorCode' => $company->debtor, - 'Ref' => $invoiceTransaction->bill_no, + 'Ref' => $invoiceTransaction ? $invoiceTransaction->bill_no : '', 'ShipInfo' => $this->marking, 'AccNo' => '500-0000', 'DetailDescription' => 'PRODUCT NAME :', @@ -124,9 +128,35 @@ class AutocountSalesInvoiceBookingResource extends JsonResource } } + // Voucherify - Starts + $voucherRedemption = $lastPaymentTransaction->voucherRedemption; + if($voucherRedemption){ + $voucherDiscount = $voucherRedemption ? bcmul((string)$voucherRedemption->value, "-1", 2) : "0"; + $voucher = $voucherRedemption->voucher; + $category = $voucher->campaign ? $voucher->campaign->category : null; + $detail = [ + 'DocNo' => '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction ? $invoiceTransaction->bill_no : '', + 'ShipInfo' => $this->marking, + 'AccNo' => $category === 'Compensation Voucher' ? '1000-000' : '949-2000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => $voucher->code, + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => '1', + 'UnitPrice' => $voucherDiscount ? number_format($voucherDiscount, 2): '0', + 'SubmitEinvoice' => '', + 'ConsolidatedEinvoice' => '', + ]; + $details->push($detail); + } + // Voucherify - Ends + // Service Charge - Starts $serviceCharge = 0; - if (!$totalPayment) { + if (!$totalPayment && $invoiceTransaction) { $serviceCharge = $invoiceTransaction->service_charge; } else { @@ -142,7 +172,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'DocNo' => '', 'DocDate' => $formattedDocumentDate, 'DebtorCode' => $company->debtor, - 'Ref' => $invoiceTransaction->bill_no, + 'Ref' => $invoiceTransaction ? $invoiceTransaction->bill_no : '', 'ShipInfo' => $this->marking, 'AccNo' => '500-0000', 'DetailDescription' => 'PRODUCT NAME :', @@ -158,50 +188,52 @@ class AutocountSalesInvoiceBookingResource extends JsonResource // Service Charge - Ends // Adjustment - Starts - $adjustment = 0; - $voucherRedemption = $invoiceTransaction->voucherRedemption; - $voucherDiscount = $voucherRedemption ? bcmul((string)$voucherRedemption->value, "-1", 2) : "0"; + if($invoiceTransaction){ + $adjustment = 0; + $voucherRedemption = $invoiceTransaction->voucherRedemption; + $voucherDiscount = $voucherRedemption ? bcmul((string)$voucherRedemption->value, "-1", 2) : "0"; - $displayedSubtotal = is_numeric($displayedSubtotal) ? sprintf('%F', $displayedSubtotal) : '0'; - $serviceCharge = is_numeric($serviceCharge) ? sprintf('%F', $serviceCharge) : '0'; - $tax = is_numeric($invoiceTransaction->tax) ? sprintf('%F', $invoiceTransaction->tax) : '0'; - $voucherDiscount = is_numeric($voucherDiscount) ? sprintf('%F', $voucherDiscount) : '0'; + $displayedSubtotal = is_numeric($displayedSubtotal) ? sprintf('%F', $displayedSubtotal) : '0'; + $serviceCharge = is_numeric($serviceCharge) ? sprintf('%F', $serviceCharge) : '0'; + $tax = is_numeric($invoiceTransaction->tax) ? sprintf('%F', $invoiceTransaction->tax) : '0'; + $voucherDiscount = is_numeric($voucherDiscount) ? sprintf('%F', $voucherDiscount) : '0'; - $displayedTotal = bcadd( - bcadd( - bcadd($displayedSubtotal, $serviceCharge, 5), - $tax, + $displayedTotal = bcadd( + bcadd( + bcadd($displayedSubtotal, $serviceCharge, 5), + $tax, + 5 + ), + $voucherDiscount, 5 - ), - $voucherDiscount, - 5 - ); + ); - $expectedTotal = bcadd(bcadd(bcadd($subtotal, $serviceCharge, 5), $invoiceTransaction->tax, 5), $voucherDiscount, 5); - $adjustment = bcsub($expectedTotal, $displayedTotal, 5); - - if ($totalPayment) { - $expectedTotal = $totalPayment; + $expectedTotal = bcadd(bcadd(bcadd($subtotal, $serviceCharge, 5), $invoiceTransaction->tax, 5), $voucherDiscount, 5); $adjustment = bcsub($expectedTotal, $displayedTotal, 5); - } - $detail = [ - 'DocNo' => '', - 'DocDate' => $formattedDocumentDate, - 'DebtorCode' => $company->debtor, - 'Ref' => $invoiceTransaction->bill_no, - 'ShipInfo' => $this->marking, - 'AccNo' => '500-0000', - 'DetailDescription' => 'PRODUCT NAME :', - 'FurtherDescription' => 'Adjustment', - 'Classification' => '022', - 'DeptNo' => 'C', - 'Qty' => '1', - 'UnitPrice' => $adjustment ? number_format($adjustment, 2) : '0', - 'SubmitEinvoice' => '', - 'ConsolidatedEinvoice' => '', - ]; - $details->push($detail); + if ($totalPayment) { + $expectedTotal = $totalPayment; + $adjustment = bcsub($expectedTotal, $displayedTotal, 5); + } + + $detail = [ + 'DocNo' => '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction->bill_no, + 'ShipInfo' => $this->marking, + 'AccNo' => '500-0000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => 'Adjustment', + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => '1', + 'UnitPrice' => $adjustment ? number_format($adjustment, 2) : '0', + 'SubmitEinvoice' => '', + 'ConsolidatedEinvoice' => '', + ]; + $details->push($detail); + } // Adjustment - Ends return AutocountSalesInvoiceBookingDetailsResource::collection( From b7eeb3fcacde7480952e76108ce809180ab4d67c Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 4 Nov 2025 11:55:29 +0800 Subject: [PATCH 14/20] E-Invoice - API, Sales Invoice Report (update with voucherify info) --- .../Eloquent/Filters/DateRangeNoData.php | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php index 833477ae..e0d730e5 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -92,14 +92,14 @@ class DateRangeNoData implements Filter $q->where('created_at', '<', '2025-07-01 00:00:00'); }); }) - ->whereHas('transactions', function ($query) { - $query->where('type', TransactionType::INVOICE) - ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { - $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + ->where(function ($query){ + $query + ->whereDoesntHave('transactions.attributesKVP', function ($subQuery) { + $subQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }) + ->whereDoesntHave('attributesKVP', function ($subQuery) { + $subQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); }); - }) - ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { - $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); }); }) // OR bookings with refunds @@ -119,14 +119,15 @@ class DateRangeNoData implements Filter $q->where('created_at', '<', '2025-07-01 00:00:00'); }); }) - ->whereHas('transactions', function ($query) { - $query->where('type', TransactionType::INVOICE) - ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { - $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + ->where(function ($query){ + $query + ->whereDoesntHave('transactions.attributesKVP', function ($subQuery) { + $subQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }) + + ->whereDoesntHave('attributesKVP', function ($subQuery) { + $subQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); }); - }) - ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { - $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); }); }); }) From f0f6822b7ebe317b6c7a58b62e17fe71edbcd0ba Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Sat, 15 Nov 2025 12:19:53 +0800 Subject: [PATCH 15/20] E-Invoice - API, Sales Invoice Report (update to exclude data that is marked not to be imported) --- app/Classes/General/Eloquent/Filters/DateRange.php | 7 ++++++- app/Classes/General/Eloquent/Filters/DateRangeNoData.php | 7 ++++++- app/Classes/General/Eloquent/Filters/DateRangeWithData.php | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/Classes/General/Eloquent/Filters/DateRange.php b/app/Classes/General/Eloquent/Filters/DateRange.php index d55709b4..798b2d4d 100644 --- a/app/Classes/General/Eloquent/Filters/DateRange.php +++ b/app/Classes/General/Eloquent/Filters/DateRange.php @@ -61,8 +61,13 @@ class DateRange implements Filter ApprovalStatus::SUSPENDED, ]) ->where(function ($q) use ($startDate, $endDate) { + $q->where(function ($q) { + $q->whereDoesntHave('company', function ($query) { + $query->where('debtor', 'N/A NO NEED TO IMPORT'); + }); + }) // Bookings without refunds - $q->where(function ($q1) use ($startDate, $endDate) { + ->where(function ($q1) use ($startDate, $endDate) { $q1->where('status', ApprovalStatus::COMPLETED) ->whereHas('transactions', function ($query) use ($startDate, $endDate){ $query->payments() diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php index e0d730e5..9c2fc6eb 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -77,8 +77,13 @@ class DateRangeNoData implements Filter ApprovalStatus::SUSPENDED, ]) ->where(function ($q) use ($startDate, $endDate) { + $q->where(function ($q) { + $q->whereDoesntHave('company', function ($query) { + $query->where('debtor', 'N/A NO NEED TO IMPORT'); + }); + }) // Bookings without refunds - $q->where(function ($q1) use ($startDate, $endDate) { + ->where(function ($q1) use ($startDate, $endDate) { $q1->where('status', ApprovalStatus::COMPLETED) ->whereHas('transactions', function ($query) use ($startDate, $endDate){ $query->payments() diff --git a/app/Classes/General/Eloquent/Filters/DateRangeWithData.php b/app/Classes/General/Eloquent/Filters/DateRangeWithData.php index 61c5f437..fa2e5161 100644 --- a/app/Classes/General/Eloquent/Filters/DateRangeWithData.php +++ b/app/Classes/General/Eloquent/Filters/DateRangeWithData.php @@ -78,8 +78,13 @@ class DateRangeWithData implements Filter ApprovalStatus::SUSPENDED, ]) ->where(function ($q) use ($startDate, $endDate) { + $q->where(function ($q) { + $q->whereDoesntHave('company', function ($query) { + $query->where('debtor', 'N/A NO NEED TO IMPORT'); + }); + }) // Bookings without refunds - $q->where(function ($q1) use ($startDate, $endDate) { + ->where(function ($q1) use ($startDate, $endDate) { $q1->where('status', ApprovalStatus::COMPLETED) ->whereHas('transactions', function ($query) use ($startDate, $endDate){ $query->payments() From 4c25ef660fd402c70645909b7626f0c02bafe0bc Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 25 Nov 2025 09:49:53 +0800 Subject: [PATCH 16/20] E-Invoice - Fix booking with full refund has no Ref at Sales Report API that generates record with no autocount data --- .../UpdateRefundTransactionStatusLogic.php | 18 +++++++++++++++++- .../CreateInvoiceTransactionV2Processor.php | 17 +++++++++++++---- .../BookingDetailsSectionComponent.vue | 2 +- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php index 0a29023d..5836bf20 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php @@ -15,6 +15,7 @@ use App\Classes\Modules\Wallets\Processors\CreditWalletProcessor; use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount; use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount; use App\Classes\Modules\Transactions\Standards\Rules\CanUpdateRefundTransactionStatus; +use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionV2Processor; use App\Classes\ValueObjects\Constants\RemarkRefundReason; use App\Classes\ValueObjects\Constants\TransactionType; use Illuminate\Support\Facades\Log; @@ -59,6 +60,9 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic /** @var CanUpdateRefundTransactionStatus */ private $canUpdateRefundTransactionStatus; + /** @var CreateInvoiceTransactionV2Processor */ + private $createInvoiceTransactionV2Processor; + /** * CreatePaymentVerificationDocumentLogic constructor. * @param FetchesCompany $fetchesCompany @@ -70,8 +74,9 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic * @param CalculatesBookingRefundAmount $calculatesBookingRefundAmount * @param UpdateBookingAmountLogic $updateBookingAmountLogic * @param CanUpdateRefundTransactionStatus $canUpdateRefundTransactionStatus + * @param CreateInvoiceTransactionV2Processor $createInvoiceTransactionV2Processor */ - public function __construct(FetchesCompany $fetchesCompany, FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, DeletesDocument $deletesDocument, CreditWalletProcessor $creditWalletProcessor, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingRefundAmount $calculatesBookingRefundAmount, UpdateBookingAmountLogic $updateBookingAmountLogic, CanUpdateRefundTransactionStatus $canUpdateRefundTransactionStatus) + public function __construct(FetchesCompany $fetchesCompany, FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, DeletesDocument $deletesDocument, CreditWalletProcessor $creditWalletProcessor, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingRefundAmount $calculatesBookingRefundAmount, UpdateBookingAmountLogic $updateBookingAmountLogic, CanUpdateRefundTransactionStatus $canUpdateRefundTransactionStatus, CreateInvoiceTransactionV2Processor $createInvoiceTransactionV2Processor) { $this->fetchesCompany = $fetchesCompany; $this->fetchesTransaction = $fetchesTransaction; @@ -82,6 +87,7 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic $this->calculatesBookingRefundAmount = $calculatesBookingRefundAmount; $this->updateBookingAmountLogic = $updateBookingAmountLogic; $this->canUpdateRefundTransactionStatus = $canUpdateRefundTransactionStatus; + $this->createInvoiceTransactionV2Processor = $createInvoiceTransactionV2Processor; } /** @@ -140,6 +146,16 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic $this->updatesTransactionStatus->execute($paymentTransaction, ApprovalStatus::REFUNDED); } + //A full refund will need invoice generated + if($refundAmount === $booking->fix_amount){ + $this->createInvoiceTransactionV2Processor->execute($booking, "", null, [ + 'generateEInvoice' => false, + 'generateEInvoiceWithNormalInvoiceTemplate' => false, + 'generateEInvoiceRefund' => true, + 'bookingOriginalStatus' => $booking->status + ]); + } + return $this->response([]); } } diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php index 504dece5..bba4cf8b 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php @@ -100,10 +100,6 @@ class CreateInvoiceTransactionV2Processor $generateEInvoiceRefund = $options['generateEInvoiceRefund'] ?? false; $bookingOriginalStatus = $options['bookingOriginalStatus'] ?? ApprovalStatus::COMPLETED; - if($generateEInvoiceRefund){ - $generateEInvoice = true; //With or without refund, these 2 flags are meant to Generate E-Invoice, so cannot have opposite indicator - } - if ($booking->status === ApprovalStatus::COMPLETED && !$generateEInvoiceRefund) { return; } @@ -229,6 +225,19 @@ class CreateInvoiceTransactionV2Processor $voucherRedemption = $transaction->voucherRedemption; + if($generateEInvoiceRefund){ + $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first(); + if($metadata){ + $generateEInvoice = true; + } + else{ + $metadata = $transaction->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE)->first(); + if($metadata){ + $generateEInvoice = true; + } + } + } + // purchase order if(!$generateEInvoiceRefund){ $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::PURCHASE_ORDER, $voucherRedemption, $generateEInvoiceWithNormalInvoiceTemplate); diff --git a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue index 0e702efc..78f169a1 100644 --- a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue +++ b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue @@ -133,7 +133,7 @@ -
+
From 0dbfa60b1a93e5445a4eca9d422257c76066d851 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 2 Dec 2025 16:35:51 +0800 Subject: [PATCH 17/20] E-Invoice - Fix booking with full refund has no Ref at Sales Report API that generates record with no autocount data --- .../Processors/CreateInvoiceTransactionV2Processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php index 1b6a671e..1450cef6 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php @@ -114,7 +114,7 @@ class CreateInvoiceTransactionV2Processor $refund_amount = $this->calculatesBookingRefundAmount->execute($booking, $booking->fix_currency_id); $booking_amount = $booking->fix_amount; - if ((float) $booking_amount === (float) $refund_amount && !$generateEInvoice) { + if ((float) $booking_amount === (float) $refund_amount && !$generateEInvoiceRefund) { return; } From 4bd14fec7369c14583c4f0e03f1b041448158f4b Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 2 Dec 2025 16:59:02 +0800 Subject: [PATCH 18/20] E-Invoice - Fix booking with full refund has no Ref at Sales Report API that generates record with no autocount data --- .../CreateInvoiceTransactionV2Processor.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php index 1450cef6..d4e6ee8e 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php @@ -127,18 +127,16 @@ class CreateInvoiceTransactionV2Processor return; } - if($generateEInvoiceRefund){ + $purchaseOrder = $booking->transactions() + ->where('type', TransactionType::PURCHASE_ORDER) + ->complete() + ->first(); + if(!$purchaseOrder && $generateEInvoiceRefund){ $purchaseOrder = $booking->transactions() ->where('type', TransactionType::PURCHASE_ORDER) ->where('status', ApprovalStatus::PENDING_SUBMISSION) ->first(); } - else{ - $purchaseOrder = $booking->transactions() - ->where('type', TransactionType::PURCHASE_ORDER) - ->complete() - ->first(); - } $constants = SegmentConstant::where('reference', SegmentConstants::SERVICE_TYPE)->where('detail->id', $booking->service->id)->first(); From 4f9cadcb651651dd6e11f426ac9a5fb63bae1c64 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Mon, 29 Dec 2025 12:51:20 +0800 Subject: [PATCH 19/20] E-Invoice - EXC2005, Test Result FeedBack 01 fixes, API amendment for middleware --- .../Resources/AutocountSalesInvoiceBookingDetailsResource.php | 1 + app/Http/Resources/AutocountSalesInvoiceBookingResource.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php index 647fe915..e4069d2f 100644 --- a/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php +++ b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php @@ -29,6 +29,7 @@ class AutocountSalesInvoiceBookingDetailsResource extends JsonResource 'UnitPrice' => $this['UnitPrice'], 'SubmitEinvoice' => $this['SubmitEinvoice'], 'ConsolidatedEinvoice' => $this['ConsolidatedEinvoice'], + 'InvoiceStatus' => $this['InvoiceStatus'], ]; } diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php index 8c134967..83f41170 100644 --- a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php +++ b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php @@ -120,6 +120,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'UnitPrice' => $displayUnitPrice ? number_format($displayUnitPrice, 2) : '0', 'SubmitEinvoice' => $firstItem ? 'T' : '', 'ConsolidatedEinvoice' => $firstItem ? ($company->e_invoice ? 'F' : 'T') : '', + 'InvoiceStatus' => (string) $this->invoice_status, ]; $details->push($detail); @@ -149,6 +150,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'UnitPrice' => $voucherDiscount ? number_format($voucherDiscount, 2): '0', 'SubmitEinvoice' => '', 'ConsolidatedEinvoice' => '', + 'InvoiceStatus' => (string) $this->invoice_status, ]; $details->push($detail); } @@ -183,6 +185,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'UnitPrice' => $serviceCharge ? number_format($serviceCharge, 2) : '0', 'SubmitEinvoice' => '', 'ConsolidatedEinvoice' => '', + 'InvoiceStatus' => (string) $this->invoice_status, ]; $details->push($detail); // Service Charge - Ends @@ -231,6 +234,7 @@ class AutocountSalesInvoiceBookingResource extends JsonResource 'UnitPrice' => $adjustment ? number_format($adjustment, 2) : '0', 'SubmitEinvoice' => '', 'ConsolidatedEinvoice' => '', + 'InvoiceStatus' => (string) $this->invoice_status, ]; $details->push($detail); } From 63332dc603a16d25ac4a1bcb65c4021257d687f0 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Fri, 16 Jan 2026 12:55:33 +0800 Subject: [PATCH 20/20] E-Invoice - Quick fix for PO that is not yet approve but invoice transaction generated --- .../components/bookings/forms/PurchaseOrderFormComponent.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue index e3b5ded3..9e2ad18f 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -184,7 +184,7 @@
-
+