diff --git a/app/Classes/General/Helper.php b/app/Classes/General/Helper.php index 8b5061ec..074eb26e 100644 --- a/app/Classes/General/Helper.php +++ b/app/Classes/General/Helper.php @@ -84,7 +84,7 @@ class Helper $ringgitWords = $numberTransformer->toWords((int)$ringgit); $centsWords = $numberTransformer->toWords((int)$cents); - return strtoupper('ringgit ' . $ringgitWords . ' and ' . $centsWords . ' cents only'); + return strtoupper('ringgit malaysia ' . $ringgitWords . ' and ' . $centsWords . ' cents only'); } public static function getLHDNStateCodeByName($name) diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreateBankingInvoiceTransactionLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreateBankingInvoiceTransactionLogic.php new file mode 100644 index 00000000..884614fe --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreateBankingInvoiceTransactionLogic.php @@ -0,0 +1,71 @@ + 'Create Banking Invoice Transaction', + 'message' => 'You have successfully create banking invoice transaction' + ]; + } + + /** @var CanFetchBooking */ + private $canFetchBooking; + + /** @var FetchesBooking */ + private $fetchesBooking; + + /** @var CreateBankingInvoiceTransactionProcessor */ + private $createBankingInvoiceTransactionProcessor; + + /** + * CreateBankingInvoiceTransactionLogic constructor. + * @param CanFetchBooking $canFetchBooking + * @param FetchesBooking $fetchesBooking + * @param CreateBankingInvoiceTransactionProcessor $createBankingInvoiceTransactionProcessor + */ + public function __construct( + CanFetchBooking $canFetchBooking, + FetchesBooking $fetchesBooking, + CreateBankingInvoiceTransactionProcessor $createBankingInvoiceTransactionProcessor + ) + { + $this->canFetchBooking = $canFetchBooking; + $this->fetchesBooking = $fetchesBooking; + $this->createBankingInvoiceTransactionProcessor = $createBankingInvoiceTransactionProcessor; + } + + + /** + * @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 + { + // $this->canFetchBooking->passes(); + + $booking = $this->fetchesBooking->execute(['id' => $request->route('id')]); + + $this->createBankingInvoiceTransactionProcessor->execute($booking); + + return $this->resourceResponse(new BookingResource($booking)); + } + +} diff --git a/app/Classes/Modules/Transactions/Processors/CreateBankingInvoiceTransactionProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateBankingInvoiceTransactionProcessor.php new file mode 100644 index 00000000..eb3c3819 --- /dev/null +++ b/app/Classes/Modules/Transactions/Processors/CreateBankingInvoiceTransactionProcessor.php @@ -0,0 +1,233 @@ +createsTransaction = $createsTransaction; + $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; + $this->fetchesCompany = $fetchesCompany; + $this->createsDocument = $createsDocument; + $this->createsFile = $createsFile; + $this->calculatesBookingOutstanding = $calculatesBookingOutstanding; + $this->fetchesBookingQuotation = $fetchesBookingQuotation; + $this->fetchesCompanyPaymentAttemptLimit = $fetchesCompanyPaymentAttemptLimit; + } + + + /** + * @param Booking $booking + * @return void + * @throws \App\Classes\Exceptions\MalformedRequestException + */ + public function execute(Booking $booking) + { + $po_order_transaction = $booking->transactions() + ->where('type', TransactionType::PURCHASE_ORDER) + ->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED]) + ->first(); + + $transaction = $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->whereNotIn('status', [ApprovalStatus::SUSPENDED]) + ->first(); + + if (!$transaction) { + $outstanding = $this->calculatesBookingOutstanding->execute($booking); + + $conversionObject = new CurrencyConversionObject(floatval(str_replace(',', '', $outstanding)), $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0 : 1, PaymentMethodType::CASH); + + $configurations = $this->fetchesBookingQuotation->execute($booking->company, $conversionObject); //cief todo: 76 + + $paymentAttemptLimit = $this->fetchesCompanyPaymentAttemptLimit->execute($booking->company); + + $billNumber = $this->generatesTransactionBillNumber->execute('PYMT-'); + + $object = new TransactionObject( + $billNumber, + TransactionType::PAYMENT, + 1, + $booking->company->id, + $configurations->getConfigurations()->getBankId(), + $configurations->getConversionObject()->getPaymentMethod(), + $configurations->getTotal(), + $configurations->getForeignTotal(), + 1, + $configurations->getConversionObject()->getCurrencyId(), + $configurations->getConfigurations()->getRate(), + $configurations->getTax(), + $configurations->getServiceCharge(), + Carbon::now()->addMinutes($paymentAttemptLimit), + ApprovalStatus::PENDING_SUBMISSION, + [], + isset($billPlzBill) ? $billPlzBill->id : NULL + ); + + $this->createsTransaction->execute($booking, $object); + } + + $billNumber = $this->generatesTransactionBillNumber->execute('BI-'); + + $payable_amount = $booking->transactions()->payments()->where(function ($query) { + return $query->where(function ($query) { + // return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString()); + return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->where('expires_on', '>=', Carbon::now()); + })->orWhere(function ($query) { + return $query->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + }); + })->sum('amount'); + $booking_amount = $booking->fix_amount; + + $transaction = $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->first(); + + $paymentAmount = $booking->transactions()->payments()->where(function ($query) { + return $query->where(function ($query) { + return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->where('expires_on', '>=', Carbon::now()); + })->orWhere(function ($query) { + return $query->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + }); + })->selectRaw('sum(amount - service_charge - tax) as sub_total')->get()->sum('sub_total'); + + $booking_currency_average_rate = $booking_amount / $paymentAmount; + + $total_service_charge = $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->whereNotIn('status', [ApprovalStatus::REJECTED, ApprovalStatus::SUSPENDED]) + ->sum('service_charge'); + + $total_tax = $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->whereIn('status', [ApprovalStatus::REJECTED, ApprovalStatus::SUSPENDED]) + ->sum('tax'); + + // delete prev banking transactions + $booking->transactions() + ->where('type', TransactionType::BANKING) + ->delete(); + + $transaction_object = new TransactionObject( + $billNumber, + TransactionType::BANKING, + $transaction->issuer, + $transaction->receiver, + $transaction->recipient_bank_account_id, + $transaction->payment_method, + $payable_amount, + $booking_amount, + $transaction->currency_id, + $transaction->original_currency_id, + $booking_currency_average_rate, + $total_tax, + $total_service_charge, + null, + ApprovalStatus::APPROVED + ); + + $banking_transaction = $this->createsTransaction->execute($po_order_transaction->booking, $transaction_object); + + $supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]); + $brn = $supplier->documents()->where('document_type', DocumentType::SSM_REGISTRATION)->latest()->first(); + + // PDF 1 - Banking Invoice + $purchase_order_pdf = LaravelMpdf::loadView('pages.pdfs.banking_invoice', + [ + 'transaction' => $banking_transaction, + 'po_order_transaction' => $po_order_transaction, + 'supplier' => $supplier, + 'brn' => $brn, + ]); + $document_object = new DocumentObject( + DocumentType::BANKING_INVOICE, + [chunk_split('data:application/pdf;base64,' . base64_encode($purchase_order_pdf->output()))], + '', + ApprovalStatus::COMPLETED, + 'banking_invoices' + ); + + /** @var Document $document */ + $document = $this->createsDocument->execute($po_order_transaction->booking, $document_object); + $this->createsFile->execute($document, $document_object); + + // PDF 2 - Banking Delivery Order + $purchase_order_pdf = LaravelMpdf::loadView('pages.pdfs.deliver_order_banking', + [ + 'transaction' => $banking_transaction, + 'po_order_transaction' => $po_order_transaction, + 'supplier' => $supplier, + 'brn' => $brn, + ]); + $document_object = new DocumentObject( + DocumentType::DELIVER_ORDER_BANKING, + [chunk_split('data:application/pdf;base64,' . base64_encode($purchase_order_pdf->output()))], + '', + ApprovalStatus::COMPLETED, + 'banking_invoices' + ); + + /** @var Document $document */ + $document = $this->createsDocument->execute($po_order_transaction->booking, $document_object); + $this->createsFile->execute($document, $document_object); + } +} diff --git a/app/Classes/ValueObjects/Constants/DocumentType.php b/app/Classes/ValueObjects/Constants/DocumentType.php index 91ff84bb..8da383d4 100644 --- a/app/Classes/ValueObjects/Constants/DocumentType.php +++ b/app/Classes/ValueObjects/Constants/DocumentType.php @@ -30,4 +30,7 @@ final class DocumentType { public const RECEIPT_VOUCHER = 'RECEIPT_VOUCHER'; public const EINVOICE = 'E_INVOICE'; + + public const BANKING_INVOICE = 'BANKING_INVOICE'; + public const DELIVER_ORDER_BANKING = 'DELIVER_ORDER_BANKING'; } diff --git a/app/Classes/ValueObjects/Constants/TransactionType.php b/app/Classes/ValueObjects/Constants/TransactionType.php index ee1d86b0..6f67adc5 100644 --- a/app/Classes/ValueObjects/Constants/TransactionType.php +++ b/app/Classes/ValueObjects/Constants/TransactionType.php @@ -40,6 +40,8 @@ final class TransactionType { public const RECEIPT_VOUCHER = 17; + public const BANKING = 18; + public const ID_TO_NAME = [ self::PAYMENT_ATTEMPT => "PAYMENT_ATTEMPT", self::PAYMENT => "PAYMENT", @@ -59,6 +61,7 @@ final class TransactionType { self::SUPPLIER_REFUND => "SUPPLIER_REFUND", self::BILL_REFUND => "BILL_REFUND", self::RECEIPT_VOUCHER => "RECEIPT_VOUCHER", + self::BANKING => "BANKING", ]; - } +} diff --git a/app/Http/Controllers/Bookings/CreateBankingInvoiceTransactionController.php b/app/Http/Controllers/Bookings/CreateBankingInvoiceTransactionController.php new file mode 100644 index 00000000..dc1636aa --- /dev/null +++ b/app/Http/Controllers/Bookings/CreateBankingInvoiceTransactionController.php @@ -0,0 +1,20 @@ +execute($request); + } + +} diff --git a/app/Http/Resources/BookingResource.php b/app/Http/Resources/BookingResource.php index 9c6edf96..fef78809 100644 --- a/app/Http/Resources/BookingResource.php +++ b/app/Http/Resources/BookingResource.php @@ -65,6 +65,8 @@ class BookingResource extends JsonResource 'supplier_delivery_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::SUPPLIER_DELIVER_ORDER)->first()), 'proforma_invoice' => new DocumentResource($this->documents()->where('document_type', DocumentType::PROFORMA_INVOICE)->whereNotIn('status', [ApprovalStatus::REJECTED, ApprovalStatus::EXPIRED])->orderByDesc('id')->first()), 'ecommerce_purchase_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::ECOMMERCE_PURCHASE_ORDER)->first()), + 'banking_invoice' => new DocumentResource($this->documents()->where('document_type', DocumentType::BANKING_INVOICE)->whereNotIn('status', [ApprovalStatus::REJECTED, ApprovalStatus::EXPIRED])->orderByDesc('id')->first()), + 'delivery_order_banking' => new DocumentResource($this->documents()->where('document_type', DocumentType::DELIVER_ORDER_BANKING)->orderByDesc('id')->first()), ], 'order_reference_no' => $this->modelAttributes()->where('name', BookingAttributeNames::ORDER_REFERENCE_NO)->get()->map(function ($attr) { return [ diff --git a/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue b/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue index 36432a62..1334615c 100644 --- a/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue @@ -72,7 +72,7 @@
-
+
@@ -81,6 +81,52 @@
+
+
+ +
+
@@ -96,6 +142,14 @@

The recipient can expect to receive the transfer within 3-5 working days. Explore our BANK TRANSFER (SAVER) option for a better rate!

Enjoy a better rate with this option! The recipient will receive the transfer after 5-7 working days.

You can request Pay-on-Behalf via Alipay for platforms like Taobao, 1688, Pinduoduo, or any Alipay-supported platform.
Please use Alipay account "2766384544@QQ.com" to apply for Daifu. This account may change, so always confirm the latest Alipay account before placing an order.

+

The customer may request an official invoice first for Bank submission (e.g. BA Trade Line).

+ The 2% service fee is already included in each product unit price, so it will not appear separately.
+ 🕓 Transfer will be processed once bank payment is received.
+

+

The customer may request an official invoice first for Bank submission (e.g. BA Trade Line).

+ Invoice will show a 2% service fee as a separate item.
+ 🕓 Transfer will be processed once bank payment is received.
+

Please ensure is a PERSONAL bank account details. Company bank account details only allow to use as E2E service.

Cancellation of E2E service are strictly NO refund on the 2% transfer fee charge.

@@ -188,7 +242,65 @@ recipientBanks: [], company: {} }, - parameters: {} + parameters: {}, + + innerDropdown: { main: false }, + innerDropdownHovered: { + service: null, + subservice: null + }, + + innerDropdownSelected: { + service: null, + subservice: null, + option: null + }, + hardcodeBankingInvoiceServices: { + services: [ + { + id: 1, + name: "BANK TRANSFER", + subservices: [ + { + id: 14, + name: "(IN) BANK TRANSFER (CNY/USD) - BA", + }, + { + id: 15, + name: "(EX) BANK TRANSFER (CNY/USD) - BA", + }, + ] + }, + { + id: 2, + name: "ALIPAY TRANSFER", + subservices: [ + { + id: 17, + name: "(IN) ALIPAY - BA", + }, + { + id: 16, + name: "(EX) ALIPAY - BA", + }, + ] + }, + { + id: 3, + name: "ENTERPRISE TO ENTERPRISE 公打公", + subservices: [ + { + id: 18, + name: "(IN) ENTERPRISE TO ENTERPRISE - BA", + }, + { + id: 19, + name: "(EX) ENTERPRISE TO ENTERPRISE - BA", + }, + ] + }, + ] + } } }, created(){ @@ -204,6 +316,28 @@ this.summary.recipientBanks = this.data.recipient_banks.accounts; }, + mounted() { + this.hardcodeBankingInvoiceServices.services.forEach(service => { + service.subservices = service.subservices.map(sub => { + const match = this.data.services.find(s => s.id === sub.id); + return match + ? { ...sub, name: match.name, currencies: match.currencies } + : sub; + }); + }); + }, + computed: { + displaySelection() { + var defaultName = "BANKING INVOICE"; + var service = this.innerDropdownSelected.service; + var sub = this.innerDropdownSelected.subservice; + var opt = this.innerDropdownSelected.option; + if (service && sub && opt) return defaultName + " → " + service.name + " → " + sub.name + " → " + opt.name; + if (service && sub) return defaultName + " → " + service.name + " → " + sub.name; + if (service) return defaultName + " → " + service.name; + return defaultName; + } + }, methods: { updateServiceType(service){ this.serviceType.id = service.id; @@ -222,9 +356,67 @@ recipientBanks: this.data.recipient_banks.accounts, company: this.data, } + this.innerDropdownSelected = { service: null, subservice: null }; + }, + selectService(service) { + this.innerDropdownSelected.service = service; + this.innerDropdownSelected.subservice = null; + this.innerDropdownSelected.option = null; + this.innerDropdownHovered.service = service; + this.innerDropdownHovered.subservice = null; + }, + selectSubservice(svc, sub) { + this.updateServiceType(sub); + this.innerDropdownSelected = { service: svc, subservice: sub }; + this.innerDropdownSelected.subservice = sub; + this.innerDropdownSelected.option = null; + this.innerDropdownHovered.subservice = sub; + + this.innerDropdown.main = false; + this.innerDropdownHovered.service = null; + }, + closeDropdown() { + this.innerDropdownHovered.service = null; + this.innerDropdownHovered.subservice = null; + this.innerDropdown.main = false; } }, mixins: [formHandler] } + diff --git a/resources/assets/vue/components/bookings/forms/CurrencyConverterComponent.vue b/resources/assets/vue/components/bookings/forms/CurrencyConverterComponent.vue index 8df81f86..317918c6 100644 --- a/resources/assets/vue/components/bookings/forms/CurrencyConverterComponent.vue +++ b/resources/assets/vue/components/bookings/forms/CurrencyConverterComponent.vue @@ -222,6 +222,10 @@ }) }, disabledMyr(){ + if (this.data.id === 14 || this.data.id === 15 || this.data.id === 16 || this.data.id === 17 || this.data.id === 18 || this.data.id === 19) { //default for service type manual / banking invoice + return false; + } + if (!this.currentSegmentNames.includes('enable enter MYR rate')) { return true; } diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue index 8e6959e0..a96af8fc 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -181,31 +181,18 @@
-
-
-
-
- - - - -
-
+ + +
+
-
-