Merge branch 'vapor/production' of https://gitlab.com/CIEFWorldwideSdnBhd/exchange-2.0 into vapor/development

This commit is contained in:
Edmond Lang
2025-08-06 21:34:50 +08:00
11 changed files with 213 additions and 18 deletions
@@ -0,0 +1,44 @@
<?php
namespace App\Classes\Jobs\Commands\V2;
use App\Classes\Modules\Bookings\Processors\RegenerateInvoiceBookingProcessor;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Booking;
use Illuminate\Support\Facades\Log;
class OneTimeBatchProcessEInvoicesV2CommandJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var Booking */
private $booking;
/**
* OneTimeBatchProcessEInvoicesV2CommandJob constructor.
* @param Booking $booking
*/
public function __construct(Booking $booking)
{
$this->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 . '.');
}
}
@@ -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));
}
@@ -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);
}
}
}
@@ -71,6 +71,8 @@ class ExportsNullDebtors implements FromQuery, WithHeadings, WithHeadingRow, Wit
*/
public function map($company): array
{
$employee = $company->employees()->orderBy('id', 'DESC')->first();
return [
'<<New>>', // Code
'300-0000', // DebtorControlAcc
@@ -89,7 +91,7 @@ class ExportsNullDebtors implements FromQuery, WithHeadings, WithHeadingRow, Wit
'', // DeliverAddr2
'', // DeliverAddr3
'', // DeliverPostCode
'', // EmailAddress
$employee->email, // EmailAddress
'', // Attention
'', // Phone1
'', // Phone2
@@ -44,7 +44,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;
@@ -63,9 +63,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){
@@ -88,6 +88,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,
@@ -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
@@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands\V2;
use App\Classes\Jobs\Commands\V2\OneTimeBatchProcessEInvoicesV2CommandJob;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\TransactionType;
use Illuminate\Console\Command;
use App\Models\Booking;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Log;
class OneTimeBatchProcessEInvoicesV2Command extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'one-time-batch-process-einvoices-command';
/**
* The console command description.
*
* @var string
*/
protected $description = 'One time batch process E-Invoices for July 2025';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
return;
$startDate = Carbon::create(2025, 7, 1)->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);
}
}
}
}
@@ -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;
@@ -126,9 +126,9 @@ function runNextBatch() {
const row = document.createElement('tr');
row.innerHTML = `
<td><a href="\${bookingUrlTemplate.replace('__ORDER_REF__', item.order_ref)}" target="_blank">\${item.order_ref}</a></td>
<td>RM \${item.booking_amount}</td>
<td>RM \${item.paid_amount}</td>
<td>RM \${item.outstanding_amount}</td>
<td>\${item.booking_amount}</td>
<td>\${item.paid_amount}</td>
<td>\${item.outstanding_amount}</td>
<td><a href="\${customerUrlTemplate.replace('__ORDER_REF__', item.customer)}" target="_blank">\${item.customer ?? '-'}</a></td>
<td>\${item.created_at}</td>
`;
@@ -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(){
@@ -0,0 +1,42 @@
<template>
<div class="row">
<div class="col bg-white padding-40 b-rad-lg">
<loading-component style="height: 300px; top: 0;" key="1" color="success" v-show="isLoading" ></loading-component>
<div class="row justify-content-center" v-show="!isLoading">
<div class="col">
<div class="row m-b-20">
<div class="col">
<h3 class="all-caps">Are you Sure?</h3>
<div class="fs-11">Are you sure you want to regenerate normal invoice instead of e-invoice for this payment?</div>
</div>
</div>
<div class="row">
<div class="col p-r-5">
<div class="btn btn-sm btn-success btn-block b-rad-none" data-dismiss="modal">Cancel</div>
</div>
<div class="col p-l-5">
<div class="btn btn-sm btn-danger btn-block b-rad-none" @click="submitForm()">Confirm</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
export default {
methods: {
submitForm() {
this.parameters.normal_invoice = true;
this.submit(this.route('api.booking.einvoice.regenerate', this.data.id), 'post', this.section, true, true);
},
successHandler(){
this.closeModal();
this.$store.dispatch('reloadList', {'name': "bookingDetailSection"});
}
},
mixins: [componentHandler, ModalFormHandler]
}
</script>
@@ -334,7 +334,7 @@
</general-confirmation-form-component>
</modal-component>
</div>
<div class="row m-t-15" v-if="booking.status === 3 && ($store.getters.isSuperAdmin || ($store.getters.isCustomer && $store.getters.getCompanyId === 199)) && booking.einvoice">
<div class="row m-t-15" v-if="booking.status === 3 && ($store.getters.isSuperAdmin || ($store.getters.isCustomer && $store.getters.getCompanyId === 199)) && booking.einvoice">
<div class="col-sm col-md-auto">
<div class="btn btn-sm btn block all-caps b-rad-none btn-danger pointer requestModal equal-width-button" data-type="regenerateEInvoice">Regenerate E-Invoice</div>
</div>
@@ -342,6 +342,14 @@
<regenerate-e-invoice-component :data="booking" :section="section" class="text-center"></regenerate-e-invoice-component>
</modal-component>
</div>
<div class="row m-t-15" v-if="booking.status === 3 && ($store.getters.isSuperAdmin || ($store.getters.isCustomer && $store.getters.getCompanyId === 199)) && booking.einvoice">
<div class="col-sm col-md-auto">
<div class="btn btn-sm btn block all-caps b-rad-none btn-danger pointer requestModal equal-width-button" data-type="regenerateNormalInvoiceEInvoice">Regenerate Normal Inv</div>
</div>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="regenerateNormalInvoiceEInvoice">
<regenerate-normal-invoice-e-invoice-component :data="booking" :section="section" class="text-center"></regenerate-normal-invoice-e-invoice-component>
</modal-component>
</div>
</div>
<div class="col-12 col-sm-12 col-md-5 mt-3 mt-sm-0">
<!-- <booking-payment-quotation-component :data="booking" :section="section"></booking-payment-quotation-component> -->
@@ -497,7 +505,7 @@
</div>
</div>
</div>
<div class="row m-t-5" v-if="$store.getters.isAdmin">
<div class="row m-t-15" v-if="$store.getters.isAdmin">
<div class="col">
<div class="btn btn-xs all-caps b-rad-none btn-warning pointer requestModal equal-width-button" data-type="changeBookingOwner">Change Booking Owner</div>
<modal-component type="changeBookingOwner">
@@ -614,7 +622,7 @@
</script>
<style scoped>
.equal-width-button {
min-width: 180px;
min-width: 200px;
display: inline-block;
text-align: center;
}