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/Bookings/Services/CalculatesBookingCurrencyAverageRate.php b/app/Classes/Modules/Bookings/Services/CalculatesBookingCurrencyAverageRate.php index aaa3e569..9f5241f2 100644 --- a/app/Classes/Modules/Bookings/Services/CalculatesBookingCurrencyAverageRate.php +++ b/app/Classes/Modules/Bookings/Services/CalculatesBookingCurrencyAverageRate.php @@ -6,6 +6,7 @@ use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\TransactionType; use App\Models\Booking; use Carbon\Carbon; +use Illuminate\Support\Facades\Log; class CalculatesBookingCurrencyAverageRate { @@ -37,13 +38,13 @@ class CalculatesBookingCurrencyAverageRate } if ($type == TransactionType::PAYMENT) { - if($generateEInvoiceRefund){ + + $totalPayment = $booking->fix_currency_id === 1 ? $booking->transactions()->payments()->complete()->sum('original_amount') : + $booking->transactions()->payments()->complete()->selectRaw('sum(amount - service_charge - tax) as sub_total')->get()->sum('sub_total'); + + if($totalPayment === 0 || $generateEInvoiceRefund){ $totalPayment = $booking->fix_currency_id === 1 ? $booking->transactions()->payments()->where('status', ApprovalStatus::REFUNDED)->sum('original_amount') : - $booking->transactions()->payments()->where('status', ApprovalStatus::REFUNDED)->selectRaw('sum(amount - service_charge - tax) as sub_total')->get()->sum('sub_total'); - } - else{ - $totalPayment = $booking->fix_currency_id === 1 ? $booking->transactions()->payments()->complete()->sum('original_amount') : - $booking->transactions()->payments()->complete()->selectRaw('sum(amount - service_charge - tax) as sub_total')->get()->sum('sub_total'); + $booking->transactions()->payments()->where('status', ApprovalStatus::REFUNDED)->selectRaw('sum(amount - service_charge - tax) as sub_total')->get()->sum('sub_total'); } return $this->calculatesBookingPayableAmount->execute($booking, $booking->fix_currency_id, $generateEInvoiceRefund) / ($totalPayment + $discount); diff --git a/app/Classes/Modules/Rules/ControllersLogic/CheckEInvoiceAmountLimitLogic.php b/app/Classes/Modules/Rules/ControllersLogic/CheckEInvoiceAmountLimitLogic.php new file mode 100644 index 00000000..bc9f18ae --- /dev/null +++ b/app/Classes/Modules/Rules/ControllersLogic/CheckEInvoiceAmountLimitLogic.php @@ -0,0 +1,67 @@ + 'Rule Check E-Invoice Amount Limit', + 'message' => 'You have successfully passed all rules evaluated' + ]; + } + + /** @var RuleEvaluator */ + private $ruleEvaluator; + + + /** @var CanPassMustEInvoiceAmountLimitRule */ + private $canPassMustEInvoiceAmountLimitRule; + + /** + * CheckEInvoiceAmountLimitLogic constructor. + * @param RuleEvaluator $ruleEvaluator + * @param CanPassMustEInvoiceAmountLimitRule $canPassMustEInvoiceAmountLimitRule + */ + public function __construct(RuleEvaluator $ruleEvaluator, CanPassMustEInvoiceAmountLimitRule $canPassMustEInvoiceAmountLimitRule) + { + $this->ruleEvaluator = $ruleEvaluator; + $this->canPassMustEInvoiceAmountLimitRule = $canPassMustEInvoiceAmountLimitRule; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws \App\Classes\Exceptions\AccessForbiddenException + * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\RequestValidationException + * @throws \App\Classes\Exceptions\CriteriaNotFulfilledException + */ + public function logic(Request $request) : JsonResponse + { + $dto = new CheckEInvoiceAmountLimitRuleDTO($request->all()); + + $result = $this->ruleEvaluator->evaluate([ + $this->canPassMustEInvoiceAmountLimitRule + ], $dto); + + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } + + return $this->resourceResponse(new RuleResource((object)$result)); + } +} diff --git a/app/Classes/Modules/Rules/DataTransferObjects/CheckEInvoiceAmountLimitRuleDTO.php b/app/Classes/Modules/Rules/DataTransferObjects/CheckEInvoiceAmountLimitRuleDTO.php new file mode 100644 index 00000000..b28e78f1 --- /dev/null +++ b/app/Classes/Modules/Rules/DataTransferObjects/CheckEInvoiceAmountLimitRuleDTO.php @@ -0,0 +1,36 @@ +bookingId = $data['booking_id']; + $this->companyId = $data['company_id']; + + $this->amount = $data['amount'] ?? null; + $this->paymentMethod = $data['payment_method'] ?? null; + $this->voucherCode = $data['voucherCode'] ?? null; + } + + public function toArray(): array + { + return [ + 'booking_id' => $this->bookingId, + 'company_id' => $this->companyId, + 'amount' => $this->amount, + 'payment_method' => $this->paymentMethod, + 'voucher_code' => $this->voucherCode, + ]; + } +} diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanPassMustEInvoiceAmountLimitRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassMustEInvoiceAmountLimitRule.php new file mode 100644 index 00000000..d077aab7 --- /dev/null +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassMustEInvoiceAmountLimitRule.php @@ -0,0 +1,102 @@ +fetchesBooking = $fetchesBooking; + $this->fetchesBookingQuotation = $fetchesBookingQuotation; + $this->calculatesBookingOutstanding = $calculatesBookingOutstanding; + } + + /** + * @return bool + */ + protected function authorized($object): bool + { + return true; + + } + + /** + * @return bool + */ + protected function validators($object): bool + { + return true; + + } + + + /** + * @return bool + */ + protected function criteria($object): bool + { + //Check if booking amount is RM10,000 or more, than must opt-in E-Invoice + $booking = $this->fetchesBooking->execute(['id' => $object->bookingId]); + $company = $booking->company; + + // $totalPayments = 0; + + // if(isset($object->amount)){ + // $totalPayments = $booking->transactions()->payments()->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->sum('amount'); + + // $conversionObject = new CurrencyConversionObject(floatval(str_replace(',', '', floatval(str_replace(',', '', $object->amount)))), $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0:1, PaymentMethodType::PAYMENT_METHODS[$object->paymentMethod]); + // $voucherCode = $object->voucherCode ?? null; + // $configurations = $this->fetchesBookingQuotation->execute($booking->company, $conversionObject, $voucherCode, null, $booking); + // } + // else{ + // $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); + // } + + // $bookingAmountInMYR = $configurations->getTotal(); + // $bookingAmountInMYR = $bookingAmountInMYR + $totalPayments; + + // if($bookingAmountInMYR >= 10000 && (!$company->e_invoice || !$company->tin) ){ + // throw new CriteriaNotFulfilledException("RM10,000 and above must opt-in for E-Invoice."); + // } + + //1 MYR, 2 CNY, 3 USD + if((($booking->fix_amount >= 9000 && $booking->fix_currency_id === 1) || + ($booking->fix_amount >= 12600 && $booking->fix_currency_id === 2) || + ($booking->fix_amount >= 1920 && $booking->fix_currency_id === 3)) + && (!$company->e_invoice || !$company->tin) ){ + throw new CriteriaNotFulfilledException("Booking amount above limit, must opt-in for E-Invoice."); + } + + return true; + } +} \ No newline at end of file diff --git a/app/Classes/Modules/Transactions/ControllersLogic/DeleteGroupLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/DeleteGroupLogic.php index d7b3418a..fcebd93b 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/DeleteGroupLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/DeleteGroupLogic.php @@ -6,6 +6,7 @@ use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Transactions\Services\FetchesGroup; use App\Classes\Modules\Transactions\Services\DeletesTransaction; use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; +use App\Classes\Modules\Transactions\Processors\UpdateRefundTransactionStatusProcessor; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\TransactionType; use App\Http\Resources\GroupResource; @@ -35,17 +36,22 @@ class DeleteGroupLogic extends AbstractControllerLogic /** @var DeletesTransaction */ private $deletesTransaction; + /** @var UpdateRefundTransactionStatusProcessor */ + private $updateRefundTransactionStatusProcessor; + /** * DeleteGroupLogic constructor. * @param updatesTransactionStatus $updatesTransactionStatus * @param FetchesGroup $fetchesGroup * @param DeletesTransaction $deletesTransaction + * @param UpdateRefundTransactionStatusProcessor $updateRefundTransactionStatusProcessor */ - public function __construct(updatesTransactionStatus $updatesTransactionStatus, FetchesGroup $fetchesGroup, DeletesTransaction $deletesTransaction) + public function __construct(updatesTransactionStatus $updatesTransactionStatus, FetchesGroup $fetchesGroup, DeletesTransaction $deletesTransaction, UpdateRefundTransactionStatusProcessor $updateRefundTransactionStatusProcessor) { $this->updatesTransactionStatus = $updatesTransactionStatus; $this->fetchesGroup = $fetchesGroup; $this->deletesTransaction = $deletesTransaction; + $this->updateRefundTransactionStatusProcessor = $updateRefundTransactionStatusProcessor; } /** @@ -69,6 +75,7 @@ class DeleteGroupLogic extends AbstractControllerLogic $this->updatesTransactionStatus->execute($payment, ApprovalStatus::APPROVED); } $this->deletesTransaction->execute($bill); + $this->checkForRefund($payment); } $group->delete(); @@ -76,4 +83,11 @@ class DeleteGroupLogic extends AbstractControllerLogic return $this->resourceResponse(new GroupResource($group)); } + //Check for refund if it is still pending, auto reject immediately + private function checkForRefund($payment){ + $refunds = $payment->transactions()->refunds()->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION])->get(); + foreach($refunds as $refund) { + $this->updateRefundTransactionStatusProcessor->execute($refund->id, ApprovalStatus::REJECTED); + } + } } diff --git a/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php index 0a29023d..e4e4577c 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php @@ -2,22 +2,11 @@ namespace App\Classes\Modules\Transactions\ControllersLogic; + use App\Classes\General\Abstracts\AbstractControllerLogic; -use App\Classes\Modules\Bookings\ControllersLogic\UpdateBookingAmountLogic; -use App\Classes\Modules\Companies\Services\FetchesCompany; -use App\Classes\Modules\Transactions\Services\FetchesTransaction; -use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; -use App\Classes\ValueObjects\Constants\ApprovalStatus; -use App\Classes\Modules\Documents\Services\DeletesDocument; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use App\Classes\Modules\Wallets\Processors\CreditWalletProcessor; -use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount; -use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount; -use App\Classes\Modules\Transactions\Standards\Rules\CanUpdateRefundTransactionStatus; -use App\Classes\ValueObjects\Constants\RemarkRefundReason; -use App\Classes\ValueObjects\Constants\TransactionType; -use Illuminate\Support\Facades\Log; +use App\Classes\Modules\Transactions\Processors\UpdateRefundTransactionStatusProcessor; class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic { @@ -27,61 +16,21 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic */ protected function notification():array { return [ - 'title' => 'Updated Transaction', - 'message' => 'You have successfully updated a transaction' + 'title' => 'Updated Refund Transaction', + 'message' => 'You have successfully updated a refund transaction' ]; } - /** @var FetchesCompany */ - private $fetchesCompany; - - /** @var FetchesTransaction */ - private $fetchesTransaction; - - /** @var UpdatesTransactionStatus */ - private $updatesTransactionStatus; - - /** @var DeletesDocument */ - private $deletesDocument; - - /** @var CreditWalletProcessor */ - private $creditWalletProcessor; - - /** @var CalculatesBookingPayableAmount */ - private $calculatesBookingPayableAmount; - - /** @var CalculatesBookingRefundAmount */ - private $calculatesBookingRefundAmount; - - /** @var UpdateBookingAmountLogic */ - private $updateBookingAmountLogic; - - /** @var CanUpdateRefundTransactionStatus */ - private $canUpdateRefundTransactionStatus; + /** @var UpdateRefundTransactionStatusProcessor */ + private $updateRefundTransactionStatusProcessor; /** - * CreatePaymentVerificationDocumentLogic constructor. - * @param FetchesCompany $fetchesCompany - * @param FetchesTransaction $fetchesTransaction - * @param UpdatesTransactionStatus $updatesTransactionStatus - * @param DeletesDocument $deletesDocument - * @param CreditWalletProcessor $creditWalletProcessor - * @param CalculatesBookingPayableAmount $calculatesBookingPayableAmount - * @param CalculatesBookingRefundAmount $calculatesBookingRefundAmount - * @param UpdateBookingAmountLogic $updateBookingAmountLogic - * @param CanUpdateRefundTransactionStatus $canUpdateRefundTransactionStatus + * UpdateRefundTransactionStatusLogic constructor. + * @param UpdateRefundTransactionStatusProcessor $updateRefundTransactionStatusProcessor */ - public function __construct(FetchesCompany $fetchesCompany, FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, DeletesDocument $deletesDocument, CreditWalletProcessor $creditWalletProcessor, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingRefundAmount $calculatesBookingRefundAmount, UpdateBookingAmountLogic $updateBookingAmountLogic, CanUpdateRefundTransactionStatus $canUpdateRefundTransactionStatus) + public function __construct(UpdateRefundTransactionStatusProcessor $updateRefundTransactionStatusProcessor) { - $this->fetchesCompany = $fetchesCompany; - $this->fetchesTransaction = $fetchesTransaction; - $this->updatesTransactionStatus = $updatesTransactionStatus; - $this->deletesDocument = $deletesDocument; - $this->creditWalletProcessor = $creditWalletProcessor; - $this->calculatesBookingPayableAmount = $calculatesBookingPayableAmount; - $this->calculatesBookingRefundAmount = $calculatesBookingRefundAmount; - $this->updateBookingAmountLogic = $updateBookingAmountLogic; - $this->canUpdateRefundTransactionStatus = $canUpdateRefundTransactionStatus; + $this->updateRefundTransactionStatusProcessor = $updateRefundTransactionStatusProcessor; } /** @@ -93,52 +42,7 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic { // $this->canUpdateRefundTransactionStatus->passes(); - $refundTransaction = $this->fetchesTransaction->execute(['id' => $request->route('id')]); - - $refundTransaction = $this->updatesTransactionStatus->execute($refundTransaction, $request->route('status')); - - $paymentTransaction = $refundTransaction->owner; - - $supplierRefundTransaction = $paymentTransaction->transactions()->supplierRefunds()->where('status', [ApprovalStatus::PENDING_VERIFICATION])->first(); - - $booking = $paymentTransaction->owner; - - // $reference = $paymentTransaction->amount - $refundTransaction->amount < 0.01 ? 'Fully Refund for Ref. ' . $booking->marking : 'Partially Refund for Ref. ' . $booking->marking; - if ($paymentTransaction->amount - $refundTransaction->amount < 0.01) { - $reference = 'Return Inward for Ref. ' . $booking->marking; - } else { - $refundRemark = $refundTransaction->remarks && $refundTransaction->remarks->first() ? $refundTransaction->remarks->first()->content : $request->input('refundRemark') ; - $remarkGroup = RemarkRefundReason::REFUND_REASONS[$refundRemark] ?? null; - $reference = $remarkGroup ? $remarkGroup . ' for Ref. ' . $booking->marking : $refundRemark . ' for Ref. ' . $booking->marking; - } - - $refundAmount = $this->calculatesBookingRefundAmount->calculateRefundAmount($paymentTransaction, $booking->fix_currency_id); - - $paidAmount = $paymentTransaction->original_amount - $refundAmount; - - if ($refundTransaction->status == ApprovalStatus::APPROVED) { - $this->creditWalletProcessor->execute($booking->company, $refundTransaction->type, $refundTransaction->amount, $reference, $refundTransaction); - - $po_transaction = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first(); - - if ($po_transaction) { - $this->updatesTransactionStatus->execute($po_transaction, (float) number_format($po_transaction->amount, 2, '.', '') === (float) number_format((float)$booking->fix_amount - $refundTransaction->original_amount, 2, '.', '') ? ApprovalStatus::PENDING_VERIFICATION : ApprovalStatus::PENDING_SUBMISSION); - } - - // cief todo: 90 - REVERTED STARTS (commented before revert) - // $request['fix_amount'] = $booking->fix_amount - $refundTransaction->original_amount; - // $request->route()->setParameter('id', $booking->id); - // $this->updateBookingAmountLogic->execute($request); - // cief todo: 90 - REVERTED ENDS (commented before revert) - } - - if ($supplierRefundTransaction) { - $this->updatesTransactionStatus->execute($supplierRefundTransaction, $request->route('status')); - } - - if (!$paidAmount > 0) { - $this->updatesTransactionStatus->execute($paymentTransaction, ApprovalStatus::REFUNDED); - } + $this->updateRefundTransactionStatusProcessor->execute($request->route('id'), $request->route('status'), $request->input('refundRemark') ?? ""); return $this->response([]); } 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/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php index b9361152..bb77f79a 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionV2Processor.php @@ -5,6 +5,7 @@ namespace App\Classes\Modules\Transactions\Processors; use App\Classes\Exceptions\MalformedRequestException; use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount; use App\Classes\Modules\Bookings\Services\CalculatesBookingTransferredAmount; +use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount; use App\Classes\Modules\ServiceTypes\Services\FetchesServiceConfigurations; use App\Classes\Modules\Transactions\Services\ListsTransactions; use App\Classes\Modules\Transactions\Services\CreatesTransaction; @@ -55,6 +56,9 @@ class CreateInvoiceTransactionV2Processor /** @var CreateInvoiceDocumentProcessor */ private $invoiceDocumentProcessor; + /** @var CalculatesBookingRefundAmount */ + private $calculatesBookingRefundAmount; + /** * CreateInvoiceTransactionV2Processor constructor. @@ -69,8 +73,9 @@ class CreateInvoiceTransactionV2Processor * @param FetchesCompany $fetchesCompany * @param UpdatesBookingStatus $updatesBookingStatus * @param CreateInvoiceDocumentProcessor $invoiceDocumentProcessor + * @param CalculatesBookingRefundAmount $calculatesBookingRefundAmount */ - public function __construct(ListsTransactions $listsTransactions, CreatesTransaction $createsTransaction, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CalculatesBookingPaidAmount $calculatesBookingPaidAmount, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingTransferredAmount $calculatesBookingTransferredAmount, FetchesServiceConfigurations $fetchesServiceConfigurations, CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate, FetchesCompany $fetchesCompany, UpdatesBookingStatus $updatesBookingStatus, CreateInvoiceDocumentProcessor $invoiceDocumentProcessor) + public function __construct(ListsTransactions $listsTransactions, CreatesTransaction $createsTransaction, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CalculatesBookingPaidAmount $calculatesBookingPaidAmount, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingTransferredAmount $calculatesBookingTransferredAmount, FetchesServiceConfigurations $fetchesServiceConfigurations, CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate, FetchesCompany $fetchesCompany, UpdatesBookingStatus $updatesBookingStatus, CreateInvoiceDocumentProcessor $invoiceDocumentProcessor, CalculatesBookingRefundAmount $calculatesBookingRefundAmount) { $this->createsTransaction = $createsTransaction; $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; @@ -81,6 +86,7 @@ class CreateInvoiceTransactionV2Processor $this->fetchesCompany = $fetchesCompany; $this->updatesBookingStatus = $updatesBookingStatus; $this->invoiceDocumentProcessor = $invoiceDocumentProcessor; + $this->calculatesBookingRefundAmount = $calculatesBookingRefundAmount; } @@ -115,8 +121,13 @@ class CreateInvoiceTransactionV2Processor } $payable_amount = $this->calculatesBookingPayableAmount->execute($booking, $booking->fix_currency_id, $generateEInvoiceRefund); + $refund_amount = $this->calculatesBookingRefundAmount->execute($booking, $booking->fix_currency_id); $booking_amount = $booking->fix_amount; + if ((float) $booking_amount === (float) $refund_amount && !$generateEInvoice) { + return; + } + // confirm that booking amount has been fully paid if ((float) $booking_amount > (float) $payable_amount) { return; diff --git a/app/Classes/Modules/Transactions/Processors/UpdateRefundTransactionStatusProcessor.php b/app/Classes/Modules/Transactions/Processors/UpdateRefundTransactionStatusProcessor.php new file mode 100644 index 00000000..15e7c0a5 --- /dev/null +++ b/app/Classes/Modules/Transactions/Processors/UpdateRefundTransactionStatusProcessor.php @@ -0,0 +1,129 @@ +fetchesCompany = $fetchesCompany; + $this->fetchesTransaction = $fetchesTransaction; + $this->updatesTransactionStatus = $updatesTransactionStatus; + $this->deletesDocument = $deletesDocument; + $this->creditWalletProcessor = $creditWalletProcessor; + $this->calculatesBookingPayableAmount = $calculatesBookingPayableAmount; + $this->calculatesBookingRefundAmount = $calculatesBookingRefundAmount; + $this->updateBookingAmountLogic = $updateBookingAmountLogic; + $this->canUpdateRefundTransactionStatus = $canUpdateRefundTransactionStatus; + } + + + /** + * @param Booking $booking + * @return void + * @throws \App\Classes\Exceptions\MalformedRequestException + */ + public function execute(int $transactionId, int $transactionStatus, string $refundRemark = "") + { + $refundTransaction = $this->fetchesTransaction->execute(['id' => $transactionId]); + + $refundTransaction = $this->updatesTransactionStatus->execute($refundTransaction, $transactionStatus); + + $paymentTransaction = $refundTransaction->owner; + + $supplierRefundTransaction = $paymentTransaction->transactions()->supplierRefunds()->where('status', [ApprovalStatus::PENDING_VERIFICATION])->first(); + + $booking = $paymentTransaction->owner; + + // $reference = $paymentTransaction->amount - $refundTransaction->amount < 0.01 ? 'Fully Refund for Ref. ' . $booking->marking : 'Partially Refund for Ref. ' . $booking->marking; + if ($paymentTransaction->amount - $refundTransaction->amount < 0.01) { + $reference = 'Return Inward for Ref. ' . $booking->marking; + } else { + $refundRemark = $refundTransaction->remarks && $refundTransaction->remarks->first() ? $refundTransaction->remarks->first()->content : $refundRemark; + $remarkGroup = RemarkRefundReason::REFUND_REASONS[$refundRemark] ?? null; + $reference = $remarkGroup ? $remarkGroup . ' for Ref. ' . $booking->marking : $refundRemark . ' for Ref. ' . $booking->marking; + } + + $refundAmount = $this->calculatesBookingRefundAmount->calculateRefundAmount($paymentTransaction, $booking->fix_currency_id); + + $paidAmount = $paymentTransaction->original_amount - $refundAmount; + + if ($refundTransaction->status == ApprovalStatus::APPROVED) { + $this->creditWalletProcessor->execute($booking->company, $refundTransaction->type, $refundTransaction->amount, $reference, $refundTransaction); + + $po_transaction = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first(); + + if ($po_transaction) { + $this->updatesTransactionStatus->execute($po_transaction, (float) number_format($po_transaction->amount, 2, '.', '') === (float) number_format((float)$booking->fix_amount - $refundTransaction->original_amount, 2, '.', '') ? ApprovalStatus::PENDING_VERIFICATION : ApprovalStatus::PENDING_SUBMISSION); + } + + // cief todo: 90 - REVERTED STARTS (commented before revert) + // $request['fix_amount'] = $booking->fix_amount - $refundTransaction->original_amount; + // $request->route()->setParameter('id', $booking->id); + // $this->updateBookingAmountLogic->execute($request); + // cief todo: 90 - REVERTED ENDS (commented before revert) + } + + if ($supplierRefundTransaction) { + $this->updatesTransactionStatus->execute($supplierRefundTransaction, $transactionStatus); + } + + if (!$paidAmount > 0) { + $this->updatesTransactionStatus->execute($paymentTransaction, ApprovalStatus::REFUNDED); + } + } +} 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 efb11f5c..e02ec0ad 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", @@ -57,6 +59,7 @@ final class TransactionType { self::CASH_BACK => "CASH_BACK", self::SUPPLIER_PAYMENT => "SUPPLIER_PAYMENT", self::SUPPLIER_REFUND => "SUPPLIER_REFUND", + 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/Controllers/Rules/CheckRuleController.php b/app/Http/Controllers/Rules/CheckRuleController.php index 59b24cd0..b0a175c1 100644 --- a/app/Http/Controllers/Rules/CheckRuleController.php +++ b/app/Http/Controllers/Rules/CheckRuleController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Rules; use App\Classes\Modules\Rules\ControllersLogic\CheckEInvoiceRuleLogic; use App\Classes\Modules\Rules\ControllersLogic\CheckPurchaseOrderRuleLogic; use App\Classes\Modules\Rules\ControllersLogic\CheckTransferRuleLogic; +use App\Classes\Modules\Rules\ControllersLogic\CheckEInvoiceAmountLimitLogic; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -36,4 +37,13 @@ class CheckRuleController public function checkTransferRule(Request $request, CheckTransferRuleLogic $logic): JsonResponse { return $logic->execute($request); } + + /** + * @param Request $request + * @param CheckEInvoiceAmountLimitLogic $logic + * @return JsonResponse + */ + public function checkEInvoiceAmountLimitRule(Request $request, CheckEInvoiceAmountLimitLogic $logic): JsonResponse { + return $logic->execute($request); + } } diff --git a/database/seeds/DistrictsTableSeeder.php b/database/seeds/DistrictsTableSeeder.php index 7d62de42..c915e6c3 100644 --- a/database/seeds/DistrictsTableSeeder.php +++ b/database/seeds/DistrictsTableSeeder.php @@ -454,7 +454,11 @@ class DistrictsTableSeeder extends Seeder [439, 1, 15, 'Sungai Pelek', json_encode(['43950']), 1, '2020-05-06 02:09:07', '2020-05-06 02:09:07'], [440, 1, 15, 'Tanjong Karang', json_encode(['45500']), 1, '2020-05-06 02:09:07', '2020-05-06 02:09:07'], [441, 1, 15, 'Tanjong Sepat', json_encode(['42800']), 1, '2020-05-06 02:09:07', '2020-05-06 02:09:07'], - [442, 1, 15, 'Telok Panglima Garang', json_encode(['42425','42500','42507','42509']), 1, '2020-05-06 02:09:07', '2020-05-06 02:09:07'] + [442, 1, 15, 'Telok Panglima Garang', json_encode(['42425','42500','42507','42509']), 1, '2020-05-06 02:09:07', '2020-05-06 02:09:07'], + [443, 1, 16, 'Paka', json_encode(['23100']), 1, '2025-11-27 14:09:07', '2025-11-27 14:09:07'], + [444, 1, 16, 'Kemaman', json_encode(['24000']), 1, '2025-11-27 14:09:07', '2025-11-27 14:09:07'], + [445, 1, 16, 'Cukai', json_encode(['24000']), 1, '2025-11-27 14:09:07', '2025-11-27 14:09:07'], + [446, 1, 16, 'Kuala Terengganu', json_encode(['21080']), 1, '2025-11-27 14:09:07', '2025-11-27 14:09:07'], ]; diff --git a/resources/assets/vue/components/accounts/forms/RegistrationFormComponent.vue b/resources/assets/vue/components/accounts/forms/RegistrationFormComponent.vue index d6f05b1b..a046a7eb 100644 --- a/resources/assets/vue/components/accounts/forms/RegistrationFormComponent.vue +++ b/resources/assets/vue/components/accounts/forms/RegistrationFormComponent.vue @@ -169,6 +169,7 @@ + diff --git a/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue index 7dd9adc0..363a6d1f 100644 --- a/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue +++ b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue @@ -162,29 +162,6 @@ - - - - - - - @@ -622,7 +599,7 @@ + @click.prevent="checkEInvoiceAmountLimitRule">Lock Booking @@ -630,6 +607,31 @@ + + + + + + + + + @@ -740,6 +742,9 @@ $('#confirm-booking-modal').modal('show'); } } + else if(section === this.section + 'CheckEInvoiceAmountLimitRule'){ + this.checkPurchaseOrderRule(); + } else if(section === this.section + 'ChangeOfMind'){ this.$store.dispatch('reloadList', {'name': "bookingDetailSection"}); } @@ -767,6 +772,14 @@ if(section === this.section + 'CheckEInvoiceRule' && statusCode === 422){ $('#modal-einvoice-info').modal('show'); } + else if(section === this.section + 'CheckEInvoiceAmountLimitRule' && statusCode === 422){ + if(!this.data.company.e_invoice){ + $('#modal-einvoice-request').modal('show'); + } + else if(!this.data.company.tin){ + $('#modal-einvoice-info').modal('show'); + } + } this.error = error.message; }, makePayment(){ //E-Invoice @@ -837,6 +850,17 @@ }; this.submit(route('api.rule.check.einvoice'), 'post', this.section + 'CheckEInvoiceRule', false, true); }, + checkEInvoiceAmountLimitRule(){ + this.error = ''; + this.parameters = { + booking_id: this.data.id, + company_id: this.data.company.id, + payment_method: this.paymentMethod.id, + voucherCode: this.voucherCode, + amount: this.amount + }; + this.submit(route('api.rule.check.einvoice.amount-limit'), 'post', this.section + 'CheckEInvoiceAmountLimitRule', false, true); + }, checkPurchaseOrderRule(){ this.error = ''; this.parameters = { 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 05112123..e3b5ded3 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -181,23 +181,10 @@ -
-
-
-
- - - - -
-
+ + +
+
@@ -217,8 +204,8 @@
-
-