From b2bd5f2a5cdb56e4f5327a75ca35016f3df1f0b3 Mon Sep 17 00:00:00 2001 From: Edmond Lang Date: Thu, 24 Jul 2025 22:27:16 +0800 Subject: [PATCH 01/18] add route preview-unfinished-payment-orders --- routes/web.php | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/routes/web.php b/routes/web.php index 36aeb598..be760be6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -36,6 +36,7 @@ use App\Classes\Modules\Transactions\Services\DeletesTransaction; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use App\Classes\General\AWSS3Helper; +use App\Classes\Modules\Bookings\Services\CalculatesBookingPaidAmount; use Illuminate\Support\Facades\File; @@ -1369,3 +1370,45 @@ Route::get('/test-test', function () { Route::get('/maintenance', function () { return response()->view('errors.503', [], 503); }); + +Route::get('/preview-unfinished-payment-orders', function (Request $request) { + echo ''; + echo ' + + + + + + + '; + echo ''; + + $cutOffDate = $request->cut_off_date; + $cutOffDate = Carbon::parse($cutOffDate); + + $bookings = Booking::where('status', '<', ApprovalStatus::COMPLETED); + if ($cutOffDate) { + $bookings->where('created_at', '<=', $cutOffDate); + } + $bookings = $bookings->orderBy('id', 'desc')->get(); + + $calculateBookingPaidAmount = new CalculatesBookingPaidAmount(); + + foreach ($bookings as $booking) { + $paid = $calculateBookingPaidAmount->execute($booking); + $outstanding = $booking->fix_amount - $paid; + + if ($paid > 0 && $outstanding > 0) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + } + + echo '
Order RefBooking AmountPaid AmountOutstanding AmountCustomerOrder Created Date
'.$booking->marking.'' . number_format($booking->fix_amount, 2) . '' . number_format($paid, 2) . '' . number_format($outstanding, 2) . ''.$booking->company->reference.'' . $booking->created_at->format('d-m-Y') . '
'; +}); \ No newline at end of file From f60241eafcb73b0af2c81c78b029511d2c75c0ce Mon Sep 17 00:00:00 2001 From: Edmond Lang Date: Thu, 24 Jul 2025 22:46:54 +0800 Subject: [PATCH 02/18] add route preview-unfinished-payment-orders --- routes/web.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/routes/web.php b/routes/web.php index be760be6..6f1184ce 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1372,6 +1372,13 @@ Route::get('/maintenance', function () { }); Route::get('/preview-unfinished-payment-orders', function (Request $request) { + $cutOffDate = $request->cut_off_date; + + if ($cutOffDate) { + echo 'Cut Off Date: ' . $cutOffDate; + echo '

'; + } + echo ''; echo ' @@ -1383,7 +1390,6 @@ Route::get('/preview-unfinished-payment-orders', function (Request $request) { '; echo ''; - $cutOffDate = $request->cut_off_date; $cutOffDate = Carbon::parse($cutOffDate); $bookings = Booking::where('status', '<', ApprovalStatus::COMPLETED); From ed317ed176b85c16aace71d4ee3b4802f67dddbd Mon Sep 17 00:00:00 2001 From: Edmond Lang Date: Fri, 25 Jul 2025 14:55:55 +0800 Subject: [PATCH 03/18] add route preview-unfinished-payment-orders --- .../Bookings/Services/CalculatesBookingPaidAmount.php | 10 ++++++++++ routes/web.php | 9 ++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php b/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php index 42ce7589..d3f36916 100644 --- a/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php +++ b/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php @@ -15,4 +15,14 @@ class CalculatesBookingPaidAmount return $booking->transactions()->payments()->complete()->sum('original_amount'); } + public function executeUntilDate(Booking $booking, Carbon $cutOffDate = null){ + $amount = $booking->transactions()->payments()->complete(); + + if($cutOffDate){ + $amount->where('created_at', '<=', $cutOffDate); + } + + return $amount->sum('original_amount'); + } + } \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 6f1184ce..521d91d1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1372,11 +1372,13 @@ Route::get('/maintenance', function () { }); Route::get('/preview-unfinished-payment-orders', function (Request $request) { - $cutOffDate = $request->cut_off_date; + $cutOffDate = $request->cut_off_date ?? null; if ($cutOffDate) { echo 'Cut Off Date: ' . $cutOffDate; echo '

'; + + $cutOffDate = Carbon::parse($cutOffDate); } echo '
Order Ref
'; @@ -1389,9 +1391,6 @@ Route::get('/preview-unfinished-payment-orders', function (Request $request) { '; echo ''; - - $cutOffDate = Carbon::parse($cutOffDate); - $bookings = Booking::where('status', '<', ApprovalStatus::COMPLETED); if ($cutOffDate) { $bookings->where('created_at', '<=', $cutOffDate); @@ -1401,7 +1400,7 @@ Route::get('/preview-unfinished-payment-orders', function (Request $request) { $calculateBookingPaidAmount = new CalculatesBookingPaidAmount(); foreach ($bookings as $booking) { - $paid = $calculateBookingPaidAmount->execute($booking); + $paid = $calculateBookingPaidAmount->executeUntilDate($booking, $cutOffDate); $outstanding = $booking->fix_amount - $paid; if ($paid > 0 && $outstanding > 0) { From 1bb0a73bd9b2ea368d762482f7c2b862b1e15a07 Mon Sep 17 00:00:00 2001 From: Edmond Lang Date: Fri, 25 Jul 2025 18:59:02 +0800 Subject: [PATCH 04/18] add route preview-unfinished-payment-orders --- .../Reports/UnfinishedPaymentOrders.php | 155 ++++++++++++++++++ routes/web.php | 52 +----- 2 files changed, 162 insertions(+), 45 deletions(-) create mode 100644 app/Http/Controllers/Reports/UnfinishedPaymentOrders.php diff --git a/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php new file mode 100644 index 00000000..c3bda14a --- /dev/null +++ b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php @@ -0,0 +1,155 @@ +input('page', 1); + $perPage = 5000; + $offset = ($page - 1) * $perPage; + $cutOffDate = $request->cut_off_date ? Carbon::parse($request->cut_off_date) : null; + + $baseQuery = Booking::with('company') + ->when($cutOffDate, fn($q) => $q->where('created_at', '<=', $cutOffDate)) + ->orderByDesc('id'); + + $total = $baseQuery->count(); + $bookings = $baseQuery->offset($offset)->limit($perPage)->get(); + + $calculator = new CalculatesBookingPaidAmount(); + + $filtered = $bookings->filter(function ($b) use ($calculator, $cutOffDate) { + $paid = $calculator->executeUntilDate($b, $cutOffDate); + $outstanding = $b->fix_amount - $paid; + return $paid > 0 && $outstanding > 0; + }); + + return response()->json([ + 'success' => true, + 'current_page' => $page, + 'next_page' => ($offset + $perPage < $total) ? $page + 1 : null, + 'count' => $filtered->count(), + 'data' => $filtered->map(function ($b) use ($calculator, $cutOffDate) { + $paid = $calculator->executeUntilDate($b, $cutOffDate); + return [ + 'id' => $b->id, + 'order_ref' => $b->marking ?? $b->id, + 'booking_amount' => number_format($b->fix_amount, 2), + 'paid_amount' => number_format($paid, 2), + 'outstanding_amount' => number_format($b->fix_amount - $paid, 2), + 'customer' => optional($b->company)->reference, + 'created_at' => $b->created_at->toDateTimeString(), + ]; + })->values(), + ]); + } + + public function loadView(Request $request) + { + $cutOffDateString = $request->cut_off_date ?? ''; + $cutOffDateParsed = $cutOffDateString ? Carbon::parse($cutOffDateString)->toDateString() : '-'; + + echo <<Cut Off Date: {$cutOffDateParsed}

+
🔄 Processing... Total 0
+
+ + + +
Order Created Date
+ + + + + + + + + + + +
Order RefBooking AmountPaid AmountOutstanding AmountCustomerOrder Created Date
+ + +HTML; + } +} diff --git a/routes/web.php b/routes/web.php index 521d91d1..b5cb1767 100644 --- a/routes/web.php +++ b/routes/web.php @@ -36,7 +36,7 @@ use App\Classes\Modules\Transactions\Services\DeletesTransaction; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use App\Classes\General\AWSS3Helper; -use App\Classes\Modules\Bookings\Services\CalculatesBookingPaidAmount; +use App\Http\Controllers\Reports\UnfinishedPaymentOrders; use Illuminate\Support\Facades\File; @@ -1371,49 +1371,11 @@ Route::get('/maintenance', function () { return response()->view('errors.503', [], 503); }); +// web route to view the result Route::get('/preview-unfinished-payment-orders', function (Request $request) { - $cutOffDate = $request->cut_off_date ?? null; - - if ($cutOffDate) { - echo 'Cut Off Date: ' . $cutOffDate; - echo '

'; - - $cutOffDate = Carbon::parse($cutOffDate); - } - - echo ''; - echo ' - - - - - - - '; - echo ''; - $bookings = Booking::where('status', '<', ApprovalStatus::COMPLETED); - if ($cutOffDate) { - $bookings->where('created_at', '<=', $cutOffDate); - } - $bookings = $bookings->orderBy('id', 'desc')->get(); - - $calculateBookingPaidAmount = new CalculatesBookingPaidAmount(); - - foreach ($bookings as $booking) { - $paid = $calculateBookingPaidAmount->executeUntilDate($booking, $cutOffDate); - $outstanding = $booking->fix_amount - $paid; - - if ($paid > 0 && $outstanding > 0) { - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - } - } - - echo '
Order RefBooking AmountPaid AmountOutstanding AmountCustomerOrder Created Date
'.$booking->marking.'' . number_format($booking->fix_amount, 2) . '' . number_format($paid, 2) . '' . number_format($outstanding, 2) . ''.$booking->company->reference.'' . $booking->created_at->format('d-m-Y') . '
'; + return (new UnfinishedPaymentOrders())->loadView($request); +}); +// web route to run the logic +Route::get('/run-batch-unfinished-payment-orders', function (Request $request) { + return (new UnfinishedPaymentOrders())->execute($request); }); \ No newline at end of file From eb9fcbdb238d3e7f7abdc16a8e88d80e70e6c570 Mon Sep 17 00:00:00 2001 From: Edmond Lang Date: Fri, 25 Jul 2025 19:14:03 +0800 Subject: [PATCH 05/18] add route preview-unfinished-payment-orders --- app/Http/Controllers/Reports/UnfinishedPaymentOrders.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php index c3bda14a..515f13a4 100644 --- a/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php +++ b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php @@ -13,7 +13,7 @@ class UnfinishedPaymentOrders extends Controller public function execute(Request $request) { $page = (int) $request->input('page', 1); - $perPage = 5000; + $perPage = 2500; $offset = ($page - 1) * $perPage; $cutOffDate = $request->cut_off_date ? Carbon::parse($request->cut_off_date) : null; From d2787964aa4b2df25a71e83c076292a832ac9db1 Mon Sep 17 00:00:00 2001 From: Edmond Lang Date: Fri, 25 Jul 2025 19:22:19 +0800 Subject: [PATCH 06/18] add route preview-unfinished-payment-orders --- app/Http/Controllers/Reports/UnfinishedPaymentOrders.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php index 515f13a4..6266b4a1 100644 --- a/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php +++ b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php @@ -126,9 +126,9 @@ function runNextBatch() { const row = document.createElement('tr'); row.innerHTML = ` \${item.order_ref} - RM \${item.booking_amount} - RM \${item.paid_amount} - RM \${item.outstanding_amount} + \${item.booking_amount} + \${item.paid_amount} + \${item.outstanding_amount} \${item.customer ?? '-'} \${item.created_at} `; From e6b73b2261a1a9c16f29af15e9d1c21322fcea2e Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 31 Jul 2025 22:23:35 +0800 Subject: [PATCH 07/18] E-Invoice - Automapping Issues, AR Credit Note Report (Export) --- .../BatchBookingsGenerateEInvoiceLogic.php | 7 +- .../Services/ExportsARCreditNoteReport.php | 124 ++++++++++++++++++ .../ControllersLogic/ImportExcelLogic.php | 13 +- .../CreateInvoiceDocumentProcessor.php | 5 +- .../Services/UpdatesTransactionStatus.php | 32 ++++- app/Classes/ValueObjects/Constants/KVPKey.php | 15 +++ .../Controllers/Exports/ExportController.php | 36 +++++ .../elements/DownloadUploadComponent.vue | 9 +- routes/export.php | 3 + 9 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php create mode 100644 app/Classes/ValueObjects/Constants/KVPKey.php diff --git a/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php index 1b467042..703fe348 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php @@ -8,6 +8,7 @@ use App\Classes\Jobs\Commands\V2\ProcessBookingForEInvoiceV2CommandJob; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Models\Booking; use App\Classes\Modules\Bookings\Processors\RegenerateInvoiceBookingProcessor; +use App\Classes\ValueObjects\Constants\KVPKey; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\JsonResponse; @@ -27,7 +28,7 @@ class BatchBookingsGenerateEInvoiceLogic extends AbstractControllerLogic return [ 'title' => 'Generate Bookings E-Invoices', 'message' => sprintf( - 'You have successfully submitted %d booking%s for E-Invoices.', + 'You have successfully submitted %d booking%s for E-Invoices processing.', $this->processedCount, $this->processedCount === 1 ? '' : 's' ), @@ -83,10 +84,10 @@ class BatchBookingsGenerateEInvoiceLogic extends AbstractControllerLogic $bookings = Booking::where('status', ApprovalStatus::COMPLETED) ->whereBetween('created_at', [$startDate, $endDate]) ->whereHas('attributesKVP', function (Builder $query) { - $query->where('key', 'AUTOCOUNT_DOCNO'); + $query->where('key', KVPKey::AUTOCOUNT_DOCNO); }) ->with(['attributesKVP' => function ($query) { - $query->where('key', 'AUTOCOUNT_DOCNO'); + $query->where('key', KVPKey::AUTOCOUNT_DOCNO); }]) ->get(); diff --git a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php new file mode 100644 index 00000000..c2bd406a --- /dev/null +++ b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php @@ -0,0 +1,124 @@ +startDate = $startDate ? Carbon::parse($startDate)->startOfDay() : Carbon::now()->subMonths(1); + $this->endDate = $endDate ? Carbon::parse($endDate)->endOfDay() : Carbon::now(); + } + + public function headings(): array + { + return [ + 'DocNo', + 'DocDate', + 'DebtorCode', + 'Ref', + 'Description', + 'Reason', + 'DeptNo', + 'Qty', + 'UnitPrice', + 'AccNo', + 'submiteinvoice', + 'ConsolidatedEinvoice', + 'KnockOffDocNo', + 'KnockOffAmt', + ]; + } + + /** + * @return \Illuminate\Support\Collection|mixed + */ + public function query() + { + $type = TransactionType::CREDIT_NOTE; + $query = Transaction::query(); + + $query->where('type', $type); + // $query->where('owner_type', Wallet::class); + $query->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + $query->whereBetween('created_at', [$this->startDate, $this->endDate]); + + return $query; + } + + /** + * @param Transaction $transaction + * @return array + */ + public function map($transaction): array + { + $booking = null; + $autoCountSalesInvoiceId = null; + $formattedDocumentDate = null; + + $company = $transaction->owner->owner; + $kvps = KeyValuePair::where('value', $transaction->id) + ->where('key', 'App\Models\Transaction') + ->orderByDesc('created_at') + ->get(); + + foreach ($kvps as $kvp) { + if ($kvp && $kvp->owner && $kvp->owner->owner && $kvp->owner->owner->type === 1) { + $booking = $kvp->owner->owner->booking; + if($booking){ + $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO)->first(); + if($metadata){ + $autoCountSalesInvoiceId = $metadata->value; + } + break; + } + } + } + + $documentDate = $transaction->created_at; + $metadata = $transaction->attributesKVP()->where('key', KVPKey::APPROVAL_DATE)->latest('created_at')->first(); + if($metadata){ + $documentDate = Carbon::parse($metadata->value); + } + if($company->e_invoice === 1){ + $documentDate = $documentDate->copy()->endOfMonth(); + } + $formattedDocumentDate = Carbon::parse($documentDate)->format('d/m/Y'); + + return [ + '<>', //DocNo + $formattedDocumentDate, //DocDate + $company->debtor, //DebtorCode + $booking ? $booking->marking : '', //Ref + $transaction->payment_reference, //Description + $transaction->payment_reference, //Reason + 'C', //DeptNo + '1', //Qty + number_format($transaction->amount, 2), //UnitPrice + '511-0000', //AccNo + 'F', //submiteinvoice + $company->e_invoice ? 'F' : 'T', //ConsolidatedEinvoice + $autoCountSalesInvoiceId ?? '', //KnockOffDocNo + number_format($transaction->amount, 2), //KnockOffAmt + ]; + } +} diff --git a/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php b/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php index a199f75d..c9b43488 100644 --- a/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php +++ b/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php @@ -10,6 +10,7 @@ use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair; use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject; use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject; use App\Classes\ValueObjects\Constants\ApprovalStatus; +use App\Classes\ValueObjects\Constants\KVPKey; use App\Models\Booking; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -127,11 +128,13 @@ class ImportExcelLogic extends AbstractControllerLogic ]); $booking = Booking::where('marking', $ref)->first(); - if($docNo != "<>"){ - $this->updateOrCreateKeyValuePair($booking, "AUTOCOUNT_DOCNO", $docNo); - } - if($eInvoiceValidationLink){ - $this->updateOrCreateKeyValuePair($booking, "AUTOCOUNT_EINVOICE_VALIDATION_LINK", $eInvoiceValidationLink); + if($booking){ + if($docNo != "" && $docNo != "<>"){ + $this->updateOrCreateKeyValuePair($booking, KVPKey::AUTOCOUNT_DOCNO, $docNo); + } + if($eInvoiceValidationLink){ + $this->updateOrCreateKeyValuePair($booking, KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK, $eInvoiceValidationLink); + } } } } diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php index 80f963ca..2267e51e 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php @@ -7,6 +7,7 @@ use App\Classes\Modules\Documents\Services\CreatesFiles; use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\DocumentType; +use App\Classes\ValueObjects\Constants\KVPKey; use App\Classes\ValueObjects\Constants\TransactionType; use App\Models\Booking; use App\Models\Document; @@ -68,11 +69,11 @@ class CreateInvoiceDocumentProcessor } if($document_type === DocumentType::EINVOICE){ - $metadata = $booking->attributesKVP()->where('key', 'AUTOCOUNT_DOCNO')->first(); + $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO)->first(); if($metadata){ $autoCountInvoiceId = $metadata->value; } - $metadata = $booking->attributesKVP()->where('key', 'AUTOCOUNT_EINVOICE_VALIDATION_LINK')->first(); + $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK)->first(); if($metadata){ $autoCountEInvoiceValidationLink = $metadata->value; } diff --git a/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php b/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php index d92e3e9a..43b77aaa 100644 --- a/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php +++ b/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php @@ -3,10 +3,14 @@ namespace App\Classes\Modules\Transactions\Services; use App\Classes\General\Eloquent\AbstractUpdateRecord; +use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject; +use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair; +use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair; use App\Classes\Modules\PerfexCRM\Processors\TransactionToPerfexCRMProcessorV2; use App\Models\Transaction; use App\Classes\Modules\Vouchers\Processors\Voucherify\TransactionToVoucherifyProcessor; use App\Classes\ValueObjects\Constants\ApprovalStatus; +use App\Classes\ValueObjects\Constants\KVPKey; use App\Classes\ValueObjects\Constants\TransactionType; class UpdatesTransactionStatus extends AbstractUpdateRecord @@ -17,15 +21,25 @@ class UpdatesTransactionStatus extends AbstractUpdateRecord /** @var TransactionToVoucherifyProcessor */ private $transactionToVoucherifyProcessor; + /** @var CreatesKeyValuePair */ + private $createsKeyValuePair; + + /** @var UpdatesKeyValuePair */ + private $updatesKeyValuePair; + /** * UpdatesTransactionStatus constructor. * @param TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor * @param TransactionToVoucherifyProcessor $transactionToVoucherifyProcessor + * @param CreatesKeyValuePair $createsKeyValuePair + * @param UpdatesKeyValuePair $updatesKeyValuePair */ - public function __construct(TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor, TransactionToVoucherifyProcessor $transactionToVoucherifyProcessor) + public function __construct(TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor, TransactionToVoucherifyProcessor $transactionToVoucherifyProcessor, CreatesKeyValuePair $createsKeyValuePair, UpdatesKeyValuePair $updatesKeyValuePair) { $this->transactionToPerfexCRMProcessor = $transactionToPerfexCRMProcessor; $this->transactionToVoucherifyProcessor = $transactionToVoucherifyProcessor; + $this->createsKeyValuePair = $createsKeyValuePair; + $this->updatesKeyValuePair = $updatesKeyValuePair; } @@ -48,6 +62,22 @@ class UpdatesTransactionStatus extends AbstractUpdateRecord $this->transactionToVoucherifyProcessor->execute($transaction, "PAID"); } + if($status == ApprovalStatus::APPROVED && ($transaction->type == TransactionType::REFUND)){ + $this->updateOrCreateKeyValuePair($transaction, KVPKey::APPROVAL_DATE, now()); + } + return $result; } + + private function updateOrCreateKeyValuePair($transaction, $key, $value) + { + $keyValuePairObject = new KeyValuePairObject($key, $value); + $metadata = $transaction->attributesKVP()->where('key', $key)->first(); + + if ($metadata) { + $this->updatesKeyValuePair->execute($metadata, $keyValuePairObject); + } else { + $this->createsKeyValuePair->execute($transaction, $keyValuePairObject); + } + } } diff --git a/app/Classes/ValueObjects/Constants/KVPKey.php b/app/Classes/ValueObjects/Constants/KVPKey.php new file mode 100644 index 00000000..d3526ba9 --- /dev/null +++ b/app/Classes/ValueObjects/Constants/KVPKey.php @@ -0,0 +1,15 @@ +validate([ + 'startDate' => 'nullable|date_format:d-m-Y', + 'endDate' => 'nullable|date_format:d-m-Y|after_or_equal:startDate', + ]); + + $startDate = null; + $endDate = null; + + if (isset($validated['startDate']) && $validated['startDate']) { + $startDate = Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay(); + } else { + $startDate = Carbon::now()->subMonths(1)->startOfDay(); + } + + if (isset($validated['endDate']) && $validated['endDate']) { + $endDate = Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay(); + } else { + $endDate = Carbon::now()->endOfDay(); + } + + $exportsTransactions = new ExportsARCreditNoteReport($startDate, $endDate); + + $exportFileName = 'Exchange - AR Credit Note Report.xls'; + $filesystemDriver = Storage::getDefaultDriver(); + if($filesystemDriver === 's3'){ + return response([ 'src' => AWSS3Helper::S3Exportable($exportFileName, $exportsTransactions) ]); + } + else{ + $response = $exportsTransactions->download($exportFileName, Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']); + ob_end_clean(); + } + return $response; + } } diff --git a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue index ab1524e8..1461d0c6 100644 --- a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue +++ b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue @@ -97,13 +97,6 @@ export default { generateEInvoicesUrl: null, } }, - mounted(){ - switch(this.section) { - case 'paymentsReportSection': - this.parameters.reportType = 'Sales Invoice Report' - break; - } - }, validations: { parameters: { startDate: { @@ -122,6 +115,7 @@ export default { return [ 'Sales Invoice Report', 'Customers Report', + 'AR Credit Note Report', ]; }, handleExportClick(){ @@ -132,6 +126,7 @@ export default { const routesMap = { 'Sales Invoice Report': route('api.export.bookings.sales-invoices'), 'Customers Report': route('api.export.companies.customers-data'), + 'AR Credit Note Report': route('api.export.transactions.ar-credit-note'), }; let url = `${routesMap[reportType]}?startDate=${this.parameters.startDate}&endDate=${this.parameters.endDate}`; diff --git a/routes/export.php b/routes/export.php index 2a766539..4801bc4e 100644 --- a/routes/export.php +++ b/routes/export.php @@ -12,6 +12,9 @@ Route::group(['prefix' => 'export', 'as' => 'export.', 'namespace' => 'Exports'] Route::group(['prefix' => 'companies', 'as' => 'companies.'], function () { Route::get('/customers-data', [ExportController::class, 'companies'])->name('customers-data'); }); + Route::group(['prefix' => 'transactions', 'as' => 'transactions.'], function () { + Route::get('/ar-credit-note', [ExportController::class, 'arCreditNote'])->name('ar-credit-note'); + }); }); Route::group(['prefix' => 'import', 'as' => 'import.', 'namespace' => 'Imports'], function () { From d5ad357496f361a038a66d24ac5efe8759a28a44 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 31 Jul 2025 23:23:11 +0800 Subject: [PATCH 08/18] E-Invoice - Automapping Issues, AR Credit Note Report (Export) --- .../Services/ExportsARCreditNoteReport.php | 4 +-- .../GenerateCreditNotePdfV2Logic.php | 16 +++++++--- .../Services/UpdatesTransactionStatus.php | 32 +------------------ .../Processors/CreditWalletProcessor.php | 31 ++++++++++++++++-- app/Classes/ValueObjects/Constants/KVPKey.php | 4 ++- 5 files changed, 46 insertions(+), 41 deletions(-) diff --git a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php index c2bd406a..240c9d9e 100644 --- a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php +++ b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php @@ -94,8 +94,8 @@ class ExportsARCreditNoteReport implements FromQuery, WithHeadings, WithHeadingR } } - $documentDate = $transaction->created_at; - $metadata = $transaction->attributesKVP()->where('key', KVPKey::APPROVAL_DATE)->latest('created_at')->first(); + $documentDate = $transaction->created_at; //NEW 2025: default in case there is no approval date + $metadata = $transaction->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_APPROVAL_DATE)->latest('created_at')->first(); if($metadata){ $documentDate = Carbon::parse($metadata->value); } diff --git a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php index 824d5528..71cf667c 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php @@ -10,6 +10,7 @@ use App\Classes\Modules\Transactions\Services\FetchesTransaction; use App\Classes\ValueObjects\Constants\TransactionType; use App\Classes\General\AWSS3Helper; use App\Classes\ValueObjects\Constants\DocumentType; +use App\Classes\ValueObjects\Constants\KVPKey; use App\Models\KeyValuePair; use App\Models\Transaction; use Carbon\Carbon; @@ -49,9 +50,9 @@ class GenerateCreditNotePdfV2Logic if($transaction->type === TransactionType::REFUND){ //Retrieve TransactionType::CREDIT_NOTE $booking = $transaction->owner->booking; - $kvp = $transaction->attributesKVP()->latest()->first(); + $kvp = $transaction->attributesKVP()->where('key', KVPKey::TRANSACTION_MODEL_CLASS)->latest()->first(); if($kvp){ - if($kvp->key === 'App\Models\Transaction'){ + if($kvp->key === KVPKey::TRANSACTION_MODEL_CLASS){ $transaction = $this->fetchesTransaction->execute(['id' => $kvp->value ]); } } @@ -62,7 +63,7 @@ class GenerateCreditNotePdfV2Logic $pdfTemplateName = 'pages.pdfs.credit_note'; //default //For New Cases with e-invoice: Retrieve the refund transaction for this credit note - $kvp = KeyValuePair::where('key', 'App\Models\Transaction')->where('value', $transaction->id)->first(); + $kvp = KeyValuePair::where('key', KVPKey::TRANSACTION_MODEL_CLASS)->where('value', $transaction->id)->first(); if($kvp){ $kvpOwner = $kvp->owner; if($kvpOwner && $kvpOwner instanceof Transaction && $kvpOwner->type === TransactionType::REFUND){ @@ -71,7 +72,12 @@ class GenerateCreditNotePdfV2Logic } } - $date = $transaction->created_at; + $date = $transaction->created_at; //NEW 2025: default in case there is no approval date + $metadata = $transaction->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_APPROVAL_DATE)->latest('created_at')->first(); + if($metadata){ + $date = Carbon::parse($metadata->value); + } + $supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]); $brn = $supplier->documents->where('document_type', DocumentType::SSM_REGISTRATION)->first(); @@ -88,7 +94,7 @@ class GenerateCreditNotePdfV2Logic { if($supplier->e_invoice === 1){ Log::info('Based on booking created date, E-Credit Note started and company wants e-invoice ' . json_encode($booking)); - $date = $booking->updated_at->copy()->endOfMonth(); + $date = $date->copy()->endOfMonth(); $pdfTemplateName = 'pages.pdfs.e_credit_note'; } else{ diff --git a/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php b/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php index 43b77aaa..d92e3e9a 100644 --- a/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php +++ b/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php @@ -3,14 +3,10 @@ namespace App\Classes\Modules\Transactions\Services; use App\Classes\General\Eloquent\AbstractUpdateRecord; -use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject; -use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair; -use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair; use App\Classes\Modules\PerfexCRM\Processors\TransactionToPerfexCRMProcessorV2; use App\Models\Transaction; use App\Classes\Modules\Vouchers\Processors\Voucherify\TransactionToVoucherifyProcessor; use App\Classes\ValueObjects\Constants\ApprovalStatus; -use App\Classes\ValueObjects\Constants\KVPKey; use App\Classes\ValueObjects\Constants\TransactionType; class UpdatesTransactionStatus extends AbstractUpdateRecord @@ -21,25 +17,15 @@ class UpdatesTransactionStatus extends AbstractUpdateRecord /** @var TransactionToVoucherifyProcessor */ private $transactionToVoucherifyProcessor; - /** @var CreatesKeyValuePair */ - private $createsKeyValuePair; - - /** @var UpdatesKeyValuePair */ - private $updatesKeyValuePair; - /** * UpdatesTransactionStatus constructor. * @param TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor * @param TransactionToVoucherifyProcessor $transactionToVoucherifyProcessor - * @param CreatesKeyValuePair $createsKeyValuePair - * @param UpdatesKeyValuePair $updatesKeyValuePair */ - public function __construct(TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor, TransactionToVoucherifyProcessor $transactionToVoucherifyProcessor, CreatesKeyValuePair $createsKeyValuePair, UpdatesKeyValuePair $updatesKeyValuePair) + public function __construct(TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor, TransactionToVoucherifyProcessor $transactionToVoucherifyProcessor) { $this->transactionToPerfexCRMProcessor = $transactionToPerfexCRMProcessor; $this->transactionToVoucherifyProcessor = $transactionToVoucherifyProcessor; - $this->createsKeyValuePair = $createsKeyValuePair; - $this->updatesKeyValuePair = $updatesKeyValuePair; } @@ -62,22 +48,6 @@ class UpdatesTransactionStatus extends AbstractUpdateRecord $this->transactionToVoucherifyProcessor->execute($transaction, "PAID"); } - if($status == ApprovalStatus::APPROVED && ($transaction->type == TransactionType::REFUND)){ - $this->updateOrCreateKeyValuePair($transaction, KVPKey::APPROVAL_DATE, now()); - } - return $result; } - - private function updateOrCreateKeyValuePair($transaction, $key, $value) - { - $keyValuePairObject = new KeyValuePairObject($key, $value); - $metadata = $transaction->attributesKVP()->where('key', $key)->first(); - - if ($metadata) { - $this->updatesKeyValuePair->execute($metadata, $keyValuePairObject); - } else { - $this->createsKeyValuePair->execute($transaction, $keyValuePairObject); - } - } } diff --git a/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php b/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php index b0fd0e1c..6e9b33fc 100644 --- a/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php +++ b/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php @@ -16,6 +16,8 @@ use App\Classes\Modules\Wallets\DataTransferObjects\WalletObject; use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject; use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber; use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair; +use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair; +use App\Classes\ValueObjects\Constants\KVPKey; use App\Models\Transaction; class CreditWalletProcessor @@ -38,6 +40,10 @@ class CreditWalletProcessor /** @var CreatesKeyValuePair */ private $createsKeyValuePair; + /** @var UpdatesKeyValuePair */ + private $updatesKeyValuePair; + + /** * CreateWalletLogic constructor. * @param GeneratesWalletCode $generatesWalletCode @@ -46,6 +52,7 @@ class CreditWalletProcessor * @param CreatesTransaction $createsTransaction * @param UpdatesWallet $updatesWallet * @param CreatesKeyValuePair $createsKeyValuePair + * @param UpdatesKeyValuePair $updatesKeyValuePair */ public function __construct( GeneratesWalletCode $generatesWalletCode, @@ -53,7 +60,8 @@ class CreditWalletProcessor GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, UpdatesWallet $updatesWallet, - CreatesKeyValuePair $createsKeyValuePair + CreatesKeyValuePair $createsKeyValuePair, + UpdatesKeyValuePair $updatesKeyValuePair ) { $this->generatesWalletCode = $generatesWalletCode; @@ -62,6 +70,7 @@ class CreditWalletProcessor $this->createsTransaction = $createsTransaction; $this->updatesWallet = $updatesWallet; $this->createsKeyValuePair = $createsKeyValuePair; + $this->updatesKeyValuePair = $updatesKeyValuePair; } @@ -86,9 +95,15 @@ class CreditWalletProcessor $billNumber = $this->generatesTransactionBillNumber->execute($transactionType === 2 ? 'DEBIT-NOTE-' : 'CREDIT-NOTE-'); - $transaction_object = new TransactionObject($billNumber, $transactionType === 2 ? TransactionType::DEBIT_NOTE : TransactionType::CREDIT_NOTE, 1, $wallet->owner->id, 1, PaymentMethodType::CASH, $amount, $amount, 1, 1, 1, 0, 0, null, ApprovalStatus::APPROVED, [], $reference); + $status = ApprovalStatus::APPROVED; + + $transaction_object = new TransactionObject($billNumber, $transactionType === 2 ? TransactionType::DEBIT_NOTE : TransactionType::CREDIT_NOTE, 1, $wallet->owner->id, 1, PaymentMethodType::CASH, $amount, $amount, 1, 1, 1, 0, 0, null, $status, [], $reference); $transaction = $this->createsTransaction->execute($wallet, $transaction_object); + if($status === ApprovalStatus::APPROVED){ + $this->updateOrCreateKeyValuePair($transaction, KVPKey::CREDIT_NOTE_APPROVAL_DATE, now()); + } + $updateWalletAmount = $transactionType === 2 ? ($wallet->amount - $transaction->amount) : ($wallet->amount + $transaction->amount); $walletObject = new WalletObject($wallet->owner->id, $wallet->currency_id, $wallet->code, $updateWalletAmount); @@ -107,4 +122,16 @@ class CreditWalletProcessor } return $wallet; } + + private function updateOrCreateKeyValuePair($transaction, $key, $value) + { + $keyValuePairObject = new KeyValuePairObject($key, $value); + $metadata = $transaction->attributesKVP()->where('key', $key)->first(); + + if ($metadata) { + $this->updatesKeyValuePair->execute($metadata, $keyValuePairObject); + } else { + $this->createsKeyValuePair->execute($transaction, $keyValuePairObject); + } + } } diff --git a/app/Classes/ValueObjects/Constants/KVPKey.php b/app/Classes/ValueObjects/Constants/KVPKey.php index d3526ba9..a15f7d91 100644 --- a/app/Classes/ValueObjects/Constants/KVPKey.php +++ b/app/Classes/ValueObjects/Constants/KVPKey.php @@ -10,6 +10,8 @@ class KVPKey public const AUTOCOUNT_EINVOICE_VALIDATION_LINK = 'AUTOCOUNT_EINVOICE_VALIDATION_LINK'; - public const APPROVAL_DATE = 'APPROVAL_DATE'; + public const CREDIT_NOTE_APPROVAL_DATE = 'CREDIT_NOTE_APPROVAL_DATE'; + + public const TRANSACTION_MODEL_CLASS = 'App\Models\Transaction'; } From b0fb5e4ac91b1d7a6e3754c4ce7839b01aa49d45 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 31 Jul 2025 23:56:37 +0800 Subject: [PATCH 09/18] E-Invoice - Automapping Issues, AR Credit Note Report (Export) --- .../Exports/Services/ExportsARCreditNoteReport.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php index 240c9d9e..2b7d41b9 100644 --- a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php +++ b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php @@ -74,6 +74,7 @@ class ExportsARCreditNoteReport implements FromQuery, WithHeadings, WithHeadingR $booking = null; $autoCountSalesInvoiceId = null; $formattedDocumentDate = null; + $refundRemark = null; $company = $transaction->owner->owner; $kvps = KeyValuePair::where('value', $transaction->id) @@ -83,7 +84,9 @@ class ExportsARCreditNoteReport implements FromQuery, WithHeadings, WithHeadingR foreach ($kvps as $kvp) { if ($kvp && $kvp->owner && $kvp->owner->owner && $kvp->owner->owner->type === 1) { - $booking = $kvp->owner->owner->booking; + $refundTransaction = $kvp->owner; + $refundRemark = $refundTransaction->remarks && $refundTransaction->remarks->first() ? $refundTransaction->remarks->first()->content : null; + $booking = $refundTransaction->owner->booking; if($booking){ $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO)->first(); if($metadata){ @@ -109,8 +112,8 @@ class ExportsARCreditNoteReport implements FromQuery, WithHeadings, WithHeadingR $formattedDocumentDate, //DocDate $company->debtor, //DebtorCode $booking ? $booking->marking : '', //Ref - $transaction->payment_reference, //Description - $transaction->payment_reference, //Reason + $refundRemark ?? '', //Description + $refundRemark ?? '', //Reason 'C', //DeptNo '1', //Qty number_format($transaction->amount, 2), //UnitPrice From ad4ef09efc477b3c0ffb81588bd7edb28234e46b Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Fri, 1 Aug 2025 10:36:36 +0800 Subject: [PATCH 10/18] E-Invoice - Automapping Issues, AR Credit Note Report (Export) --- .../Modules/Exports/Services/ExportsARCreditNoteReport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php index 2b7d41b9..9592cd93 100644 --- a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php +++ b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php @@ -105,7 +105,7 @@ class ExportsARCreditNoteReport implements FromQuery, WithHeadings, WithHeadingR if($company->e_invoice === 1){ $documentDate = $documentDate->copy()->endOfMonth(); } - $formattedDocumentDate = Carbon::parse($documentDate)->format('d/m/Y'); + $formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y'); return [ '<>', //DocNo From a6328fabd62bf786059ec38adde48a253bed6f30 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Fri, 1 Aug 2025 11:24:58 +0800 Subject: [PATCH 11/18] E-Invoice - Automapping Issues, AR Credit Note Report (Export) --- .../GenerateCreditNotePdfV2Logic.php | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php index 71cf667c..7168a175 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php @@ -88,7 +88,28 @@ class GenerateCreditNotePdfV2Logic if ($bookingCreatedDate->isAfter($eInvoiceStartDate)) { $eInvoiceStarted = true; } - // $eInvoiceStarted = false; //cief todo: 90 - for testing + + if($eInvoiceStarted) { + $eInvoiceStarted = false; //reset to re-evaluate second time + $autoCountInvoiceId = ''; + $autoCountEInvoiceValidationLink = ''; + + $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO)->first(); + if($metadata){ + $autoCountInvoiceId = $metadata->value; + } + $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK)->first(); + if($metadata){ + $autoCountEInvoiceValidationLink = $metadata->value; + } + + Log::info('autoCountInvoiceId: ' . $autoCountInvoiceId); + Log::info('autoCountEInvoiceValidationLink: ' . $autoCountEInvoiceValidationLink); + + if($autoCountInvoiceId && $autoCountEInvoiceValidationLink){ + $eInvoiceStarted = true; + } + } if($eInvoiceStarted) { @@ -103,7 +124,7 @@ class GenerateCreditNotePdfV2Logic } } else{ - Log::info('Based on booking created date, E-Credit Note not yet started'); + Log::info('Based on booking created date, E-Credit Note not yet started. / Not Yet Ready.'); } $pdf = LaravelMpdf::loadView($pdfTemplateName, ['transaction' => $transaction, 'booking' => $booking, 'supplier' => $supplier, 'date' => $date, 'brn' => $brn,]); From 0e51f4a2361417b2584a01da326d3a34da2d47df Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Mon, 4 Aug 2025 23:58:57 +0800 Subject: [PATCH 12/18] E-Invoice - One time processing to generate normal invoice for July 2025 --- ...eTimeBatchProcessEInvoicesV2CommandJob.php | 44 +++++++++++ .../RegenerateInvoiceBookingProcessor.php | 9 ++- .../CreateInvoiceDocumentProcessor.php | 8 +- .../CreateInvoiceTransactionProcessor.php | 12 ++- .../OneTimeBatchProcessEInvoicesV2Command.php | 78 +++++++++++++++++++ 5 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 app/Classes/Jobs/Commands/V2/OneTimeBatchProcessEInvoicesV2CommandJob.php create mode 100644 app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php diff --git a/app/Classes/Jobs/Commands/V2/OneTimeBatchProcessEInvoicesV2CommandJob.php b/app/Classes/Jobs/Commands/V2/OneTimeBatchProcessEInvoicesV2CommandJob.php new file mode 100644 index 00000000..26783cbe --- /dev/null +++ b/app/Classes/Jobs/Commands/V2/OneTimeBatchProcessEInvoicesV2CommandJob.php @@ -0,0 +1,44 @@ +booking = $booking; + } + + public function handle() + { + Log::info(Carbon::now() . ': Start job - Processing single booking for E-Invoices for July 2025.'); + $start = new Carbon(); + + $isAllowNormalInvoice = true; + (App()->make(RegenerateInvoiceBookingProcessor::class))->execute($this->booking, $isAllowNormalInvoice); + + $end = new Carbon(); + $elapsedTime = $start->diff($end)->format('%H:%I:%S'); + Log::info(Carbon::now() . ': End job - Processing single booking for E-Invoices for July 2025. ElapsedTime: ' . $elapsedTime . '.'); + } +} diff --git a/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php b/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php index 38d89f08..7b392d08 100644 --- a/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php +++ b/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php @@ -14,6 +14,7 @@ use App\Classes\ValueObjects\Constants\TransactionType; use App\Models\Booking; use App\Models\Transaction; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Log; class RegenerateInvoiceBookingProcessor { @@ -48,7 +49,7 @@ class RegenerateInvoiceBookingProcessor $this->createInvoiceTransactionProcessor = $createInvoiceTransactionProcessor; } - public function execute(Booking $booking) + public function execute(Booking $booking, bool $isAllowNormalInvoice = false) { $this->updatesBookingStatus->execute($booking, ApprovalStatus::APPROVED); @@ -58,6 +59,8 @@ class RegenerateInvoiceBookingProcessor ->orderBy('created_at', 'asc') ->first(); + Log::info('RegenerateInvoiceBookingProcessor booking: ' . json_encode($booking->marking)); + // get the first bill_no if($firstInvoice){ $firstBillNo = $firstInvoice->bill_no; @@ -90,10 +93,10 @@ class RegenerateInvoiceBookingProcessor $this->deletesDocument->execute($row); } - $this->createInvoiceTransactionProcessor->execute($booking, $firstBillNo, true); + $this->createInvoiceTransactionProcessor->execute($booking, $firstBillNo, true, $isAllowNormalInvoice); } else { - $this->createInvoiceTransactionProcessor->execute($booking); + $this->createInvoiceTransactionProcessor->execute($booking, "", false, $isAllowNormalInvoice); } } } diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php index 80f963ca..daf8acce 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php @@ -43,7 +43,7 @@ class CreateInvoiceDocumentProcessor * @return void * @throws \App\Classes\Exceptions\MalformedRequestException */ - public function execute($transaction, $purchaseOrder, $supplier, $document_type, $voucherRedemption = null) + public function execute($transaction, $purchaseOrder, $supplier, $document_type, $voucherRedemption = null, $isAllowNormalInvoice = false) { // calculate current Paid Amount $booking = $transaction->owner_type == Booking::class ? $transaction->owner : null; @@ -87,6 +87,12 @@ class CreateInvoiceDocumentProcessor $lowercaseDocumentType = strtolower($document_type); + if($document_type === DocumentType::EINVOICE){ //July 2025 workaround generate normal invoice instead of E-Invoice + if($isAllowNormalInvoice){ + $lowercaseDocumentType = strtolower(DocumentType::INVOICE); + } + } + $order_pdf = LaravelMpdf::loadView('pages.pdfs.' . $lowercaseDocumentType, [ 'transaction' => $transaction, diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php index e8c7ff21..44f0dbbf 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php @@ -21,6 +21,7 @@ use App\Classes\ValueObjects\Constants\DocumentType; use App\Models\Booking; use App\Models\SegmentConstant; use Carbon\Carbon; +use Illuminate\Support\Facades\Log; class CreateInvoiceTransactionProcessor { @@ -89,7 +90,7 @@ class CreateInvoiceTransactionProcessor * @return void * @throws MalformedRequestException */ - public function execute(Booking $booking, String $invoiceNo= "", bool $isAllowEInvoice = false) + public function execute(Booking $booking, String $invoiceNo= "", bool $isAllowEInvoice = false, bool $isAllowNormalInvoice = false) { if ($booking->status === ApprovalStatus::COMPLETED) { @@ -137,11 +138,18 @@ class CreateInvoiceTransactionProcessor } // $eInvoice = true; //cief todo: 90 - for testing + if($isAllowNormalInvoice){ + $invoiceNo = ""; //July 2025 workaround generate normal invoice instead of E-Invoice + } + if($invoiceNo){ $billNumber = $invoiceNo; } else{ $billNUmberPrefix = $eInvoice ? 'EINV-' : 'INV-'; + if($isAllowNormalInvoice){ + $billNUmberPrefix = 'INV-'; //July 2025 workaround generate normal invoice instead of E-Invoice + } $billNumber = $this->generatesTransactionBillNumber->execute($billNUmberPrefix); } @@ -189,7 +197,7 @@ class CreateInvoiceTransactionProcessor if ($eInvoice) { if($isAllowEInvoice){ - $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::EINVOICE, $voucherRedemption); + $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::EINVOICE, $voucherRedemption, $isAllowNormalInvoice); } } // invoice diff --git a/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php b/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php new file mode 100644 index 00000000..12c24ea7 --- /dev/null +++ b/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php @@ -0,0 +1,78 @@ +startOfDay(); + $endDate = Carbon::create(2025, 7, 31)->endOfDay(); + + $bookings = Booking::where('status', ApprovalStatus::COMPLETED) + ->whereBetween('created_at', [$startDate, $endDate]) + ->get(); + + $count = 0; + foreach ($bookings as $booking) { + $firstInvoice = $booking->transactions() + ->whereIn('type', [TransactionType::INVOICE]) + ->withTrashed() + ->whereBetween('created_at', [$startDate, $endDate]) + ->orderBy('created_at', 'asc') + ->first(); + + $normalInvoice = $booking->documents()->where('document_type', DocumentType::INVOICE)->first(); + $eInvoice = $booking->documents()->where('document_type', DocumentType::EINVOICE)->first(); + + if($firstInvoice && !$normalInvoice && !$eInvoice){ + OneTimeBatchProcessEInvoicesV2CommandJob::dispatch($booking); + $count++; + Log::info('Processed: ' . $count); + Log::info('Booking ID: ' . $booking->marking . ' | created_at: ' . $booking->created_at); + } + } + } +} From d8c6b1a33414d4ae59e3a2507179d9834eca77a4 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 5 Aug 2025 00:46:31 +0800 Subject: [PATCH 13/18] E-Invoice - One time processing to generate normal invoice for July 2025 --- .../Processors/CreateInvoiceDocumentProcessor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php index daf8acce..7ce160ac 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php @@ -62,9 +62,9 @@ class CreateInvoiceDocumentProcessor if ($bookingCreatedDate->isAfter($eInvoiceStartDate)) { $lastPaymentTransaction = $booking->transactions()->where('type', TransactionType::PAYMENT)->whereIn('status', [ApprovalStatus::COMPLETED, ApprovalStatus::APPROVED])->latest()->first(); $documentDate = $lastPaymentTransaction->created_at; - if(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){ - $documentDate = $booking->updated_at; - } + // if(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){ //cief todo: 90 - Batch generate E-Invoice date incorrect + // $documentDate = $booking->updated_at; + // } } if($document_type === DocumentType::EINVOICE){ From 249bfc09da9ce9f16838663ef7bec79b1a7e37c0 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 5 Aug 2025 14:38:36 +0800 Subject: [PATCH 14/18] E-Invoice - One time processing to generate normal invoice for July 2025 (prevent accidental rerun) --- .../Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php b/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php index 12c24ea7..f47ffd03 100644 --- a/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php +++ b/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php @@ -48,6 +48,8 @@ class OneTimeBatchProcessEInvoicesV2Command extends Command */ public function handle() { + return; + $startDate = Carbon::create(2025, 7, 1)->startOfDay(); $endDate = Carbon::create(2025, 7, 31)->endOfDay(); From 9f635f40bd0f7aedc21e7f2d807f987194c092c2 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 6 Aug 2025 15:17:34 +0800 Subject: [PATCH 15/18] E-Invoice - Emergency fix to allow admin to generate normal invoice instead of einvoice --- .../RegenerateInvoiceBookingLogic.php | 3 +- .../forms/RegenerateEInvoiceComponent.vue | 1 + ...generateNormalInvoiceEInvoiceComponent.vue | 42 +++++++++++++++++++ .../BookingDetailsSectionComponent.vue | 14 +++++-- 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 resources/assets/vue/components/bookings/forms/RegenerateNormalInvoiceEInvoiceComponent.vue diff --git a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php index 39db1708..bf88251f 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php @@ -69,8 +69,9 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic 'with_transactions' => true ] ); + $normalInvoice = $request->input('normal_invoice', false); - $this->regenerateInvoiceBookingProcessor->execute($booking); + $this->regenerateInvoiceBookingProcessor->execute($booking, $normalInvoice); return $this->resourceResponse(new BookingResource($booking)); } diff --git a/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue b/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue index 6d6d15f9..1d2dd9af 100644 --- a/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue +++ b/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue @@ -29,6 +29,7 @@ export default { methods: { submitForm() { + this.parameters.normal_invoice = false; this.submit(this.route('api.booking.einvoice.regenerate', this.data.id), 'post', this.section, true, true); }, successHandler(){ diff --git a/resources/assets/vue/components/bookings/forms/RegenerateNormalInvoiceEInvoiceComponent.vue b/resources/assets/vue/components/bookings/forms/RegenerateNormalInvoiceEInvoiceComponent.vue new file mode 100644 index 00000000..b3279d3e --- /dev/null +++ b/resources/assets/vue/components/bookings/forms/RegenerateNormalInvoiceEInvoiceComponent.vue @@ -0,0 +1,42 @@ + + diff --git a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue index 68dffd3a..c7717536 100644 --- a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue +++ b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue @@ -334,7 +334,7 @@ -
+
Regenerate E-Invoice
@@ -342,6 +342,14 @@
+
+
+
Regenerate Normal Inv
+
+ + + +
@@ -497,7 +505,7 @@
-
+
Change Booking Owner
@@ -614,7 +622,7 @@