From d0f88f5848903876663f684a8c6fe8999e9d47be Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Sat, 26 Jul 2025 16:46:55 +0800 Subject: [PATCH] E-Invoice - Automapping Issues, Sales Invoice Report (Import) with batch processing for E-Invoices --- .../BatchBookingsGenerateEInvoiceLogic.php | 95 ++++++++++++++++++ .../RegenerateInvoiceBookingLogic.php | 81 ++------------- .../RegenerateInvoiceBookingProcessor.php | 99 +++++++++++++++++++ .../RegenerateBookingEInvoiceController.php | 11 ++- .../elements/DownloadUploadComponent.vue | 71 ++++++++++--- .../bookings/elements/PaymentComponent.vue | 2 +- routes/booking.php | 3 + 7 files changed, 271 insertions(+), 91 deletions(-) create mode 100644 app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php create mode 100644 app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php diff --git a/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php new file mode 100644 index 00000000..d674c3c6 --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php @@ -0,0 +1,95 @@ + 'Generate Bookings E-Invoices', + 'message' => 'You have successfully process bookings for E-Invoices' + ]; + } + + /** @var RegenerateInvoiceBookingProcessor */ + private $regenerateInvoiceBookingProcessor; + + /** + * BatchBookingsGenerateEInvoiceLogic constructor. + * @param RegenerateInvoiceBookingProcessor $regenerateInvoiceBookingProcessor + */ + public function __construct( + RegenerateInvoiceBookingProcessor $regenerateInvoiceBookingProcessor + ) { + $this->regenerateInvoiceBookingProcessor = $regenerateInvoiceBookingProcessor; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws \App\Classes\Exceptions\AccessForbiddenException + * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\RequestValidationException + */ + public function logic(Request $request): JsonResponse + { + $validated = $request->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(); + } + + $startDate = $startDate ? Carbon::parse($startDate)->startOfDay() : Carbon::now()->subMonths(1); + $endDate = $endDate ? Carbon::parse($endDate)->endOfDay() : Carbon::now(); + + $bookings = Booking::where('status', ApprovalStatus::COMPLETED) + ->whereBetween('created_at', [$startDate, $endDate]) + ->whereHas('attributesKVP', function (Builder $query) { + $query->where('key', 'AUTOCOUNT_DOCNO'); + }) + ->with(['attributesKVP' => function ($query) { + $query->where('key', 'AUTOCOUNT_DOCNO'); + }]) + ->get(); + + foreach ($bookings as $booking) { + // $autocountValue = optional($booking->attributesKVP->first())->value; + //Log::info('Booking ID: ' . $booking->marking . ' | AUTOCOUNT_DOCNO: ' . $autocountValue); + $this->regenerateInvoiceBookingProcessor->execute($booking); + } + + return $this->resourceResponse(JsonResource::collection(collect([]))); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php index 4a63eca4..39db1708 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php @@ -5,19 +5,11 @@ namespace App\Classes\Modules\Bookings\ControllersLogic; use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Bookings\Services\FetchesBooking; use App\Classes\Modules\Bookings\Standards\Rules\CanFetchBooking; -use App\Classes\Modules\Bookings\Services\UpdatesBookingStatus; -use App\Classes\Modules\Transactions\Services\DeletesTransaction; -use App\Classes\Modules\Documents\Services\DeletesDocument; -use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionProcessor; -use Illuminate\Support\Str; -use App\Classes\ValueObjects\Constants\DocumentType; +use App\Classes\Modules\Bookings\Processors\RegenerateInvoiceBookingProcessor; use App\Http\Resources\BookingResource; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use App\Classes\ValueObjects\Constants\ApprovalStatus; -use App\Classes\ValueObjects\Constants\TransactionType; -use App\Models\Transaction; -use Illuminate\Support\Carbon; class RegenerateInvoiceBookingLogic extends AbstractControllerLogic { @@ -39,41 +31,23 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic /** @var FetchesBooking */ private $fetchesBooking; - /** @var DeletesTransaction */ - private $deletesTransaction; - - /** @var UpdatesBookingStatus */ - private $updatesBookingStatus; - - /** @var DeletesDocument */ - private $deletesDocument; - - /** @var CreateInvoiceTransactionProcessor */ - private $createInvoiceTransactionProcessor; + /** @var RegenerateInvoiceBookingProcessor */ + private $regenerateInvoiceBookingProcessor; /** - * FetchBookingLogic constructor. + * RegenerateInvoiceBookingLogic constructor. * @param CanFetchBooking $canFetchBooking * @param FetchesBooking $fetchesBooking - * @param DeletesTransaction $deletesTransaction - * @param UpdatesBookingStatus $updatesBookingStatus - * @param DeletesDocument $deletesDocument - * @param CreateInvoiceTransactionProcessor $createInvoiceTransactionProcessor + * @param RegenerateInvoiceBookingProcessor $regenerateInvoiceBookingProcessor */ public function __construct( CanFetchBooking $canFetchBooking, FetchesBooking $fetchesBooking, - DeletesTransaction $deletesTransaction, - UpdatesBookingStatus $updatesBookingStatus, - DeletesDocument $deletesDocument, - CreateInvoiceTransactionProcessor $createInvoiceTransactionProcessor + RegenerateInvoiceBookingProcessor $regenerateInvoiceBookingProcessor ) { $this->canFetchBooking = $canFetchBooking; $this->fetchesBooking = $fetchesBooking; - $this->deletesTransaction = $deletesTransaction; - $this->updatesBookingStatus = $updatesBookingStatus; - $this->deletesDocument = $deletesDocument; - $this->createInvoiceTransactionProcessor = $createInvoiceTransactionProcessor; + $this->regenerateInvoiceBookingProcessor = $regenerateInvoiceBookingProcessor; } @@ -96,46 +70,7 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic ] ); - $this->updatesBookingStatus->execute($booking, ApprovalStatus::APPROVED); - - $firstInvoice = $booking->transactions() - ->whereIn('type', [TransactionType::INVOICE]) - ->withTrashed() - ->orderBy('created_at', 'asc') - ->first(); - - // get the first bill_no - $firstBillNo = $firstInvoice->bill_no; - if (strpos($firstBillNo, '-deleted') !== false) { - $firstBillNo = substr($firstBillNo, 0, strpos($firstBillNo, '-deleted')); - } - - // update currentInvoice bill_no to '-deleted-' - $currentInvoice = $booking->transactions()->where('type', TransactionType::INVOICE)->first(); - if($currentInvoice){ - $currentInvoice->bill_no = $currentInvoice->bill_no ."-deleted-" . (string)(Carbon::now()->timestamp); - $currentInvoice->save(); - } - - $transactionWithSameBillNo = Transaction::where('bill_no', $firstBillNo)->withTrashed()->get(); - if ($transactionWithSameBillNo) { - foreach ($transactionWithSameBillNo as $transaction) { - $transaction->bill_no = $transaction->bill_no . "-deleted-" . Str::random(10); - $transaction->save(); - } - } - - $transaction = $booking->transactions()->whereIn('type', [TransactionType::INVOICE, TransactionType::SUPPLIER_DELIVER])->get(); - foreach ($transaction as $key => $row) { - $this->deletesTransaction->execute($row); - } - - $document = $booking->documents()->whereIn('document_type', [DocumentType::PURCHASE_ORDER, DocumentType::INVOICE, DocumentType::DELIVER_ORDER, DocumentType::SUPPLIER_DELIVER_ORDER])->get(); - foreach ($document as $key => $row) { - $this->deletesDocument->execute($row); - } - - $this->createInvoiceTransactionProcessor->execute($booking, $firstBillNo, true); + $this->regenerateInvoiceBookingProcessor->execute($booking); return $this->resourceResponse(new BookingResource($booking)); } diff --git a/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php b/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php new file mode 100644 index 00000000..38d89f08 --- /dev/null +++ b/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php @@ -0,0 +1,99 @@ +deletesTransaction = $deletesTransaction; + $this->updatesBookingStatus = $updatesBookingStatus; + $this->deletesDocument = $deletesDocument; + $this->createInvoiceTransactionProcessor = $createInvoiceTransactionProcessor; + } + + public function execute(Booking $booking) + { + $this->updatesBookingStatus->execute($booking, ApprovalStatus::APPROVED); + + $firstInvoice = $booking->transactions() + ->whereIn('type', [TransactionType::INVOICE]) + ->withTrashed() + ->orderBy('created_at', 'asc') + ->first(); + + // get the first bill_no + if($firstInvoice){ + $firstBillNo = $firstInvoice->bill_no; + if (strpos($firstBillNo, '-deleted') !== false) { + $firstBillNo = substr($firstBillNo, 0, strpos($firstBillNo, '-deleted')); + } + + // update currentInvoice bill_no to '-deleted-' + $currentInvoice = $booking->transactions()->where('type', TransactionType::INVOICE)->first(); + if($currentInvoice){ + $currentInvoice->bill_no = $currentInvoice->bill_no ."-deleted-" . (string)(Carbon::now()->timestamp); + $currentInvoice->save(); + } + + $transactionWithSameBillNo = Transaction::where('bill_no', $firstBillNo)->withTrashed()->get(); + if ($transactionWithSameBillNo) { + foreach ($transactionWithSameBillNo as $transaction) { + $transaction->bill_no = $transaction->bill_no . "-deleted-" . Str::random(10); + $transaction->save(); + } + } + + $transaction = $booking->transactions()->whereIn('type', [TransactionType::INVOICE, TransactionType::SUPPLIER_DELIVER])->get(); + foreach ($transaction as $key => $row) { + $this->deletesTransaction->execute($row); + } + + $document = $booking->documents()->whereIn('document_type', [DocumentType::PURCHASE_ORDER, DocumentType::INVOICE, DocumentType::DELIVER_ORDER, DocumentType::SUPPLIER_DELIVER_ORDER])->get(); + foreach ($document as $key => $row) { + $this->deletesDocument->execute($row); + } + + $this->createInvoiceTransactionProcessor->execute($booking, $firstBillNo, true); + } + else { + $this->createInvoiceTransactionProcessor->execute($booking); + } + } +} diff --git a/app/Http/Controllers/Bookings/RegenerateBookingEInvoiceController.php b/app/Http/Controllers/Bookings/RegenerateBookingEInvoiceController.php index 0b07d938..baec723f 100644 --- a/app/Http/Controllers/Bookings/RegenerateBookingEInvoiceController.php +++ b/app/Http/Controllers/Bookings/RegenerateBookingEInvoiceController.php @@ -3,10 +3,10 @@ namespace App\Http\Controllers\Bookings; use App\Classes\Modules\Bookings\ControllersLogic\RegenerateInvoiceBookingLogic; +use App\Classes\Modules\Bookings\ControllersLogic\BatchBookingsGenerateEInvoiceLogic; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; - class RegenerateBookingEInvoiceController { /** @@ -17,4 +17,13 @@ class RegenerateBookingEInvoiceController public function regenerate(Request $request, RegenerateInvoiceBookingLogic $logic): JsonResponse { return $logic->execute($request); } + + /** + * @param Request $request + * @param BatchBookingsGenerateEInvoiceLogic $logic + * @return JsonResponse + */ + public function batchProcess(Request $request, BatchBookingsGenerateEInvoiceLogic $logic): JsonResponse { + return $logic->execute($request); + } } diff --git a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue index cabd9f8b..4bacfb71 100644 --- a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue +++ b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue @@ -22,7 +22,7 @@ -
+
Export @@ -30,21 +30,41 @@
-
-
- - Import - -
-
- - Import - -
+
+ + + + +
+
+ + + + +
- - -
@@ -71,6 +91,7 @@ export default { reportType: '', }, isDownloading: false, + generateEInvoicesUrl: null, } }, mounted(){ @@ -116,10 +137,28 @@ export default { this.submit(url, 'get', this.section, false, false); } else{ - window.open(this.url, '_blank'); + window.open(url, '_blank'); this.isDownloading = false; } }, + handleGenerateEInvoiceClick(){ + if (!this.validate()) return; + + const reportType = this.parameters.reportType; + const routesMap = { + 'Sales Invoice Report': route('api.booking.batch.process'), + }; + + const selectedRoute = routesMap[reportType]; + + if (!selectedRoute) { + throw new Error(`No route mapping found for report type: ${reportType}`); + } + + this.generateEInvoicesUrl = `${selectedRoute}?startDate=${this.parameters.startDate}&endDate=${this.parameters.endDate}`; + + $('#modal-generate-einvoice').modal('show'); + }, successHandler(response) { if(window.LARAVEL_VAPOR_ENABLED){ if (response.payload !== undefined && response.payload.src) { diff --git a/resources/assets/vue/components/bookings/elements/PaymentComponent.vue b/resources/assets/vue/components/bookings/elements/PaymentComponent.vue index d85e8dbd..02f9a953 100644 --- a/resources/assets/vue/components/bookings/elements/PaymentComponent.vue +++ b/resources/assets/vue/components/bookings/elements/PaymentComponent.vue @@ -2,7 +2,7 @@
-
+
Export Report (New)
diff --git a/routes/booking.php b/routes/booking.php index e305276b..50e9911a 100644 --- a/routes/booking.php +++ b/routes/booking.php @@ -50,4 +50,7 @@ Route::group(['prefix' => 'booking', 'as' => 'booking.', 'namespace' => 'Booking Route::group(['prefix' => '{id}/einvoice', 'as' => 'einvoice.'], function () { Route::post('/', [RegenerateBookingEInvoiceController::class, 'regenerate'])->name('regenerate'); }); + Route::group(['prefix' => 'batch/einvoice', 'as' => 'batch.'], function () { + Route::get('/process', [RegenerateBookingEInvoiceController::class, 'batchProcess'])->name('process'); + }); });