From df193fd737f4004090b95ae03c04d0137893ae44 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 15 May 2025 23:46:36 +0800 Subject: [PATCH 1/5] E-Invoice - Do not allow customer to edit PO when customer already has made partial payment --- .../CreateBookingPaymentLogic.php | 14 ++-- .../DataTransferObjects/ConfirmBookingDTO.php | 2 +- .../UpdateCompanyEInvoiceInfoLogic.php | 6 +- .../UpdateCompanyEInvoiceRequestLogic.php | 2 +- .../DataTransferObjects/EInvoiceInfoDTO.php | 2 +- .../EInvoiceRequestDTO.php | 2 +- .../CheckEInvoiceRuleLogic.php | 8 +-- .../CheckPurchaseOrderRuleLogic.php | 8 +-- ...le.php => CanPassEInvoicePromptedRule.php} | 6 +- .../Standards/Rules/CanPassEditingPORule.php | 67 +++++++++++++++++++ ...rRule.php => CanPassPurchaseOrderRule.php} | 4 +- ...nProceedTINRule.php => CanPassTINRule.php} | 4 +- .../CreatePurchaseOrderTransactionLogic.php | 30 +++++++-- .../CreatePurchaseOrderDTO.php | 25 +++++++ .../forms/PurchaseOrderFormComponent.vue | 8 ++- 15 files changed, 152 insertions(+), 36 deletions(-) rename app/Classes/Modules/{Rules => Bookings}/DataTransferObjects/ConfirmBookingDTO.php (89%) rename app/Classes/Modules/{Rules => Companies}/DataTransferObjects/EInvoiceInfoDTO.php (95%) rename app/Classes/Modules/{Rules => Companies}/DataTransferObjects/EInvoiceRequestDTO.php (89%) rename app/Classes/Modules/Rules/Standards/Rules/{CanProceedEInvoicePromptedRule.php => CanPassEInvoicePromptedRule.php} (85%) create mode 100644 app/Classes/Modules/Rules/Standards/Rules/CanPassEditingPORule.php rename app/Classes/Modules/Rules/Standards/Rules/{CanProceedPurchaseOrderRule.php => CanPassPurchaseOrderRule.php} (96%) rename app/Classes/Modules/Rules/Standards/Rules/{CanProceedTINRule.php => CanPassTINRule.php} (93%) create mode 100644 app/Classes/Modules/Transactions/DataTransferObjects/CreatePurchaseOrderDTO.php diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php index 6b75dd49..ce8bec20 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php @@ -16,10 +16,10 @@ use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; use App\Classes\Modules\Wallets\Services\UpdatesWalletBalance; use App\Classes\Modules\Currencies\DataTransferObjects\CurrencyConversionObject; use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount; -use App\Classes\Modules\Rules\DataTransferObjects\ConfirmBookingDTO; -use App\Classes\Modules\Rules\Standards\Rules\CanProceedEInvoicePromptedRule; -use App\Classes\Modules\Rules\Standards\Rules\CanProceedPurchaseOrderRule; -use App\Classes\Modules\Rules\Standards\Rules\CanProceedTINRule; +use App\Classes\Modules\Bookings\DataTransferObjects\ConfirmBookingDTO; +use App\Classes\Modules\Rules\Standards\Rules\CanPassEInvoicePromptedRule; +use App\Classes\Modules\Rules\Standards\Rules\CanPassPurchaseOrderRule; +use App\Classes\Modules\Rules\Standards\Rules\CanPassTINRule; use App\Classes\Modules\Transactions\Processors\CreateCashBackTransactionProcessor; use App\Classes\Modules\Vouchers\Processors\Voucherify\BookingToVoucherifyProcessor; use App\Classes\Modules\Wallets\Services\RecalculatesWalletBalance; @@ -131,9 +131,9 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic { $dto = new ConfirmBookingDTO($request->all()); $result = $this->ruleEvaluator->evaluate([ - App()->make(CanProceedEInvoicePromptedRule::class), - App()->make(CanProceedTINRule::class), - App()->make(CanProceedPurchaseOrderRule::class), + App()->make(CanPassEInvoicePromptedRule::class), + App()->make(CanPassTINRule::class), + App()->make(CanPassPurchaseOrderRule::class), ], $dto); if ($result->failed()) { diff --git a/app/Classes/Modules/Rules/DataTransferObjects/ConfirmBookingDTO.php b/app/Classes/Modules/Bookings/DataTransferObjects/ConfirmBookingDTO.php similarity index 89% rename from app/Classes/Modules/Rules/DataTransferObjects/ConfirmBookingDTO.php rename to app/Classes/Modules/Bookings/DataTransferObjects/ConfirmBookingDTO.php index 2c154833..49f7e727 100644 --- a/app/Classes/Modules/Rules/DataTransferObjects/ConfirmBookingDTO.php +++ b/app/Classes/Modules/Bookings/DataTransferObjects/ConfirmBookingDTO.php @@ -1,6 +1,6 @@ all()); $result = $this->ruleEvaluator->evaluate([ - App()->make(CanProceedEInvoicePromptedRule::class), + App()->make(CanPassEInvoicePromptedRule::class), ], $dto); if ($result->failed()) { diff --git a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceRequestLogic.php b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceRequestLogic.php index 86051447..09700f5d 100644 --- a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceRequestLogic.php +++ b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceRequestLogic.php @@ -6,7 +6,7 @@ namespace App\Classes\Modules\Companies\ControllersLogic; use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Companies\Services\UpdatesCompanyEInvoiceRequest; use App\Classes\Modules\Companies\Services\FetchesCompany; -use App\Classes\Modules\Rules\DataTransferObjects\EInvoiceRequestDTO; +use App\Classes\Modules\Companies\DataTransferObjects\EInvoiceRequestDTO; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; diff --git a/app/Classes/Modules/Rules/DataTransferObjects/EInvoiceInfoDTO.php b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php similarity index 95% rename from app/Classes/Modules/Rules/DataTransferObjects/EInvoiceInfoDTO.php rename to app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php index f768b2c1..2717bf7c 100644 --- a/app/Classes/Modules/Rules/DataTransferObjects/EInvoiceInfoDTO.php +++ b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php @@ -1,6 +1,6 @@ all()); $result = $this->ruleEvaluator->evaluate([ - App()->make(CanProceedEInvoicePromptedRule::class), - App()->make(CanProceedTINRule::class), + App()->make(CanPassEInvoicePromptedRule::class), + App()->make(CanPassTINRule::class), ], $dto); if ($result->failed()) { diff --git a/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php b/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php index 577e4618..41f24c22 100644 --- a/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php +++ b/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php @@ -7,8 +7,8 @@ use App\Classes\Exceptions\CriteriaNotFulfilledException; use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Rules\DataTransferObjects\CheckPurchaseOrderRuleDTO; use App\Classes\Modules\Rules\Services\RuleEvaluator; -use App\Classes\Modules\Rules\Standards\Rules\CanProceedEInvoicePromptedRule; -use App\Classes\Modules\Rules\Standards\Rules\CanProceedPurchaseOrderRule; +use App\Classes\Modules\Rules\Standards\Rules\CanPassEInvoicePromptedRule; +use App\Classes\Modules\Rules\Standards\Rules\CanPassPurchaseOrderRule; use App\Http\Resources\RuleResource; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -49,8 +49,8 @@ class CheckPurchaseOrderRuleLogic extends AbstractControllerLogic $dto = new CheckPurchaseOrderRuleDTO($request->all()); $result = $this->ruleEvaluator->evaluate([ - App()->make(CanProceedEInvoicePromptedRule::class), - App()->make(CanProceedPurchaseOrderRule::class), + App()->make(CanPassEInvoicePromptedRule::class), + App()->make(CanPassPurchaseOrderRule::class), ], $dto); if ($result->failed()) { diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanProceedEInvoicePromptedRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassEInvoicePromptedRule.php similarity index 85% rename from app/Classes/Modules/Rules/Standards/Rules/CanProceedEInvoicePromptedRule.php rename to app/Classes/Modules/Rules/Standards/Rules/CanPassEInvoicePromptedRule.php index dcddbcdd..5ab57fdf 100644 --- a/app/Classes/Modules/Rules/Standards/Rules/CanProceedEInvoicePromptedRule.php +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassEInvoicePromptedRule.php @@ -6,14 +6,14 @@ use App\Classes\Exceptions\CriteriaNotFulfilledException; use App\Classes\General\Abstracts\AbstractRule; use App\Classes\Modules\Companies\Services\FetchesCompany; -class CanProceedEInvoicePromptedRule extends AbstractRule +class CanPassEInvoicePromptedRule extends AbstractRule { /** @var FetchesCompany */ private $fetchesCompany; /** - * CanProceedEInvoicePromptedRule constructor. + * CanPassEInvoicePromptedRule constructor. * @param FetchesCompany $fetchesCompany */ public function __construct(FetchesCompany $fetchesCompany) @@ -48,7 +48,7 @@ class CanProceedEInvoicePromptedRule extends AbstractRule //Check if account requires E-Invoice $company = $this->fetchesCompany->execute(['id' => $object->companyId]); if($company->e_invoice === null){ - throw new CriteriaNotFulfilledException("Please refresh page to answer question related to E-Invoice."); + throw new CriteriaNotFulfilledException("Please refresh page and click 'Make Payment' first to answer question related to E-Invoice."); } return true; } diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanPassEditingPORule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassEditingPORule.php new file mode 100644 index 00000000..62293b6d --- /dev/null +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassEditingPORule.php @@ -0,0 +1,67 @@ +fetchesBooking = $fetchesBooking; + $this->fetchesCompany = $fetchesCompany; + } + + /** + * @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 user is allow to edit purchase order + $booking = $this->fetchesBooking->execute(['id' => $object->bookingId]); + + $paidAmount = floatval((App()->make(CalculatesBookingPayableAmount::class))->execute($booking, $booking->fix_currency_id)) - floatval((App()->make(CalculatesBookingRefundAmount::class))->execute($booking, $booking->fix_currency_id)); + + if($paidAmount > 0){ + throw new CriteriaNotFulfilledException("Purchase order form is no longer allow to be edited."); + } + return true; + } +} diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanProceedPurchaseOrderRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassPurchaseOrderRule.php similarity index 96% rename from app/Classes/Modules/Rules/Standards/Rules/CanProceedPurchaseOrderRule.php rename to app/Classes/Modules/Rules/Standards/Rules/CanPassPurchaseOrderRule.php index f9483835..c6782cf5 100644 --- a/app/Classes/Modules/Rules/Standards/Rules/CanProceedPurchaseOrderRule.php +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassPurchaseOrderRule.php @@ -13,7 +13,7 @@ use App\Classes\ValueObjects\Constants\TransactionType; use App\Models\ServiceType; use Illuminate\Support\Facades\Log; -class CanProceedPurchaseOrderRule extends AbstractRule +class CanPassPurchaseOrderRule extends AbstractRule { /** @var FetchesBooking */ @@ -23,7 +23,7 @@ class CanProceedPurchaseOrderRule extends AbstractRule private $fetchesCompany; /** - * CanProceedPurchaseOrderRule constructor. + * CanPassPurchaseOrderRule constructor. * @param FetchesBooking $fetchesBooking * @param FetchesCompany $fetchesCompany */ diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanProceedTINRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassTINRule.php similarity index 93% rename from app/Classes/Modules/Rules/Standards/Rules/CanProceedTINRule.php rename to app/Classes/Modules/Rules/Standards/Rules/CanPassTINRule.php index a4adc674..3f767912 100644 --- a/app/Classes/Modules/Rules/Standards/Rules/CanProceedTINRule.php +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassTINRule.php @@ -7,14 +7,14 @@ use App\Classes\Exceptions\CriteriaNotFulfilledException; use App\Classes\General\Abstracts\AbstractRule; use App\Classes\Modules\Companies\Services\FetchesCompany; -class CanProceedTINRule extends AbstractRule +class CanPassTINRule extends AbstractRule { /** @var FetchesCompany */ private $fetchesCompany; /** - * CanProceedTINRule constructor. + * CanPassTINRule constructor. * @param FetchesCompany $fetchesCompany */ public function __construct(FetchesCompany $fetchesCompany) diff --git a/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php index 9bab107f..862945f0 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php @@ -3,9 +3,11 @@ namespace App\Classes\Modules\Transactions\ControllersLogic; +use App\Classes\Exceptions\CriteriaNotFulfilledException; use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Bookings\Services\FetchesBooking; use App\Classes\Modules\Companies\Services\FetchesCompany; +use App\Classes\Modules\Transactions\DataTransferObjects\CreatePurchaseOrderDTO; use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject; use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor; use App\Classes\Modules\Transactions\Services\CreatesTransaction; @@ -15,6 +17,8 @@ use App\Classes\Modules\Transactions\Services\FetchesTransaction; use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber; use App\Classes\Modules\Transactions\Services\UpdatesTransaction; use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; +use App\Classes\Modules\Rules\Services\RuleEvaluator; +use App\Classes\Modules\Rules\Standards\Rules\CanPassEditingPORule; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\PaymentMethodType; use App\Classes\ValueObjects\Constants\TransactionType; @@ -46,17 +50,27 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic /** @var CreatePurchaseOrderTransactionProcessor */ private $createPurchaseOrderTransactionProcessor; + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** @var CanPassEditingPORule */ + private $canPassEditingPORule; + /** * CreatePurchaseOrderTransactionLogic constructor. * @param FetchesBooking $fetchesBooking * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber * @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor + * @param RuleEvaluator $ruleEvaluator + * @param CanPassEditingPORule $canPassEditingPORule */ - public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor) + public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, RuleEvaluator $ruleEvaluator, CanPassEditingPORule $canPassEditingPORule) { $this->fetchesBooking = $fetchesBooking; $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; $this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor; + $this->ruleEvaluator = $ruleEvaluator; + $this->canPassEditingPORule = $canPassEditingPORule; } /** @@ -64,9 +78,20 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic * @param string $id * @return JsonResponse * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\CriteriaNotFulfilledException */ public function logic(Request $request, $id = '') : JsonResponse { + + $dto = new CreatePurchaseOrderDTO($request->all()); + $result = $this->ruleEvaluator->evaluate([ + $this->canPassEditingPORule + ], $dto); + + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } + /** @var Booking $booking */ $booking = $this->fetchesBooking->execute(['id' => $request->route('id') ?? $id]); @@ -87,7 +112,4 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic return $this->resourceResponse(new TransactionResource($transaction)); } - - - } diff --git a/app/Classes/Modules/Transactions/DataTransferObjects/CreatePurchaseOrderDTO.php b/app/Classes/Modules/Transactions/DataTransferObjects/CreatePurchaseOrderDTO.php new file mode 100644 index 00000000..074640ad --- /dev/null +++ b/app/Classes/Modules/Transactions/DataTransferObjects/CreatePurchaseOrderDTO.php @@ -0,0 +1,25 @@ +bookingId = $data['booking_id']; + $this->companyId = $data['company_id']; + } + + public function toArray(): array + { + return [ + 'booking_id' => $this->bookingId, + 'company_id' => $this->companyId, + ]; + } +} diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue index de4e496b..4636db2a 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -188,12 +188,12 @@
@@ -281,7 +281,9 @@ submitForm(){ this.uploadFiles = false; this.parameters = { - products: this.products + products: this.products, + booking_id: this.data.id, + company_id: this.data.company.id, }; this.submit(route('api.transaction.po.create', this.data.id), 'post', this.section, true, true); From f524bc37de2b764686e7e7a6ed7cf96e3a147f8a Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 15 May 2025 23:54:16 +0800 Subject: [PATCH 2/5] E-Invoice - Do not allow customer to edit PO when customer already has made partial payment --- .../CreatePurchaseOrderTransactionLogic.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php index 862945f0..59757fa1 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php @@ -21,12 +21,14 @@ use App\Classes\Modules\Rules\Services\RuleEvaluator; use App\Classes\Modules\Rules\Standards\Rules\CanPassEditingPORule; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\PaymentMethodType; +use App\Classes\ValueObjects\Constants\RoleTypes; use App\Classes\ValueObjects\Constants\TransactionType; use App\Http\Resources\TransactionResource; use App\Models\Booking; use App\Models\Transaction; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic { @@ -82,14 +84,15 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic */ public function logic(Request $request, $id = '') : JsonResponse { + if(!in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)){ + $dto = new CreatePurchaseOrderDTO($request->all()); + $result = $this->ruleEvaluator->evaluate([ + $this->canPassEditingPORule + ], $dto); - $dto = new CreatePurchaseOrderDTO($request->all()); - $result = $this->ruleEvaluator->evaluate([ - $this->canPassEditingPORule - ], $dto); - - if ($result->failed()) { - throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } } /** @var Booking $booking */ From d3de6f8997f829ff0dd5ff73b0ffb5b691ceab68 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Sun, 18 May 2025 21:45:32 +0800 Subject: [PATCH 3/5] E-Invoice - New business rule, transfer now has duration limit (Payment attempt duration limit) --- .../CreateBookingPaymentLogic.php | 2 + .../CheckPurchaseOrderRuleLogic.php | 2 + .../CheckTransferRuleLogic.php | 60 ++++++++++ .../CheckTransferRuleDTO.php | 25 ++++ .../Rules/CanPassOrderDurationLimitRule.php | 113 ++++++++++++++++++ .../Controllers/Rules/CheckRuleController.php | 10 ++ app/Http/Resources/V2/BookingV2Resource.php | 10 +- .../BookingPaymentQuotationV2Component.vue | 17 ++- .../forms/PurchaseOrderFormComponent.vue | 1 + routes/rule.php | 1 + 10 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 app/Classes/Modules/Rules/ControllersLogic/CheckTransferRuleLogic.php create mode 100644 app/Classes/Modules/Rules/DataTransferObjects/CheckTransferRuleDTO.php create mode 100644 app/Classes/Modules/Rules/Standards/Rules/CanPassOrderDurationLimitRule.php diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php index ce8bec20..aa9f9670 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php @@ -24,6 +24,7 @@ use App\Classes\Modules\Transactions\Processors\CreateCashBackTransactionProcess use App\Classes\Modules\Vouchers\Processors\Voucherify\BookingToVoucherifyProcessor; use App\Classes\Modules\Wallets\Services\RecalculatesWalletBalance; use App\Classes\Modules\Rules\Services\RuleEvaluator; +use App\Classes\Modules\Rules\Standards\Rules\CanPassOrderDurationLimitRule; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\PaymentMethodType; use App\Classes\ValueObjects\Constants\TransactionType; @@ -131,6 +132,7 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic { $dto = new ConfirmBookingDTO($request->all()); $result = $this->ruleEvaluator->evaluate([ + App()->make(CanPassOrderDurationLimitRule::class), App()->make(CanPassEInvoicePromptedRule::class), App()->make(CanPassTINRule::class), App()->make(CanPassPurchaseOrderRule::class), diff --git a/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php b/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php index 41f24c22..a1c74aee 100644 --- a/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php +++ b/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php @@ -8,6 +8,7 @@ use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Rules\DataTransferObjects\CheckPurchaseOrderRuleDTO; use App\Classes\Modules\Rules\Services\RuleEvaluator; use App\Classes\Modules\Rules\Standards\Rules\CanPassEInvoicePromptedRule; +use App\Classes\Modules\Rules\Standards\Rules\CanPassOrderDurationLimitRule; use App\Classes\Modules\Rules\Standards\Rules\CanPassPurchaseOrderRule; use App\Http\Resources\RuleResource; use Illuminate\Http\JsonResponse; @@ -49,6 +50,7 @@ class CheckPurchaseOrderRuleLogic extends AbstractControllerLogic $dto = new CheckPurchaseOrderRuleDTO($request->all()); $result = $this->ruleEvaluator->evaluate([ + App()->make(CanPassOrderDurationLimitRule::class), App()->make(CanPassEInvoicePromptedRule::class), App()->make(CanPassPurchaseOrderRule::class), ], $dto); diff --git a/app/Classes/Modules/Rules/ControllersLogic/CheckTransferRuleLogic.php b/app/Classes/Modules/Rules/ControllersLogic/CheckTransferRuleLogic.php new file mode 100644 index 00000000..b17d8ed0 --- /dev/null +++ b/app/Classes/Modules/Rules/ControllersLogic/CheckTransferRuleLogic.php @@ -0,0 +1,60 @@ + 'Rule Check Transfer', + 'message' => 'You have successfully passed all rules evaluated' + ]; + } + + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** + * CheckTransferRuleLogic constructor. + */ + public function __construct(RuleEvaluator $ruleEvaluator) + { + $this->ruleEvaluator = $ruleEvaluator; + } + + /** + * @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 CheckTransferRuleDTO($request->all()); + + $result = $this->ruleEvaluator->evaluate([ + App()->make(CanPassOrderDurationLimitRule::class), + ], $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/CheckTransferRuleDTO.php b/app/Classes/Modules/Rules/DataTransferObjects/CheckTransferRuleDTO.php new file mode 100644 index 00000000..35f630f2 --- /dev/null +++ b/app/Classes/Modules/Rules/DataTransferObjects/CheckTransferRuleDTO.php @@ -0,0 +1,25 @@ +bookingId = $data['booking_id']; + $this->companyId = $data['company_id']; + } + + public function toArray(): array + { + return [ + 'booking_id' => $this->bookingId, + 'company_id' => $this->companyId, + ]; + } +} diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanPassOrderDurationLimitRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassOrderDurationLimitRule.php new file mode 100644 index 00000000..46739d2b --- /dev/null +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassOrderDurationLimitRule.php @@ -0,0 +1,113 @@ +fetchesBooking = $fetchesBooking; + $this->fetchesCompanyPaymentAttemptLimit = $fetchesCompanyPaymentAttemptLimit; + } + + /** + * @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 order is still valid (within duration limit, reused PAYMENT_ATTEMPT_DURATION_LIMIT) + + $isExpired = false; + $booking = $this->fetchesBooking->execute(['id' => $object->bookingId]); + $paymentAttemptLimit = $this->fetchesCompanyPaymentAttemptLimit->execute($booking->company); + // $paymentAttemptLimit = 5; //Manual testing must pay in minutes + + $createdAt = Carbon::parse($booking->created_at); + $bookingExpiresAt = $createdAt->addMinutes($paymentAttemptLimit); + $now = Carbon::now(); + + if ($now->greaterThan($bookingExpiresAt)) { + $isExpired = true; + } + + $allPayments = $booking->transactions() + ->payments() + ->get(); + + if ($isExpired) { + $filteredPayments = $allPayments->filter(function ($payment) use ($bookingExpiresAt) { + return Carbon::parse($payment->created_at)->lessThanOrEqualTo($bookingExpiresAt); + }); + + if ($filteredPayments->isNotEmpty()) { + // Use the earlier payment to recalculate bookingExpiresAt + $earliestPayment = $filteredPayments->sortBy('created_at')->first(); + $newBookingExpiresAt = Carbon::parse($earliestPayment->created_at)->addMinutes($paymentAttemptLimit); + + $logDetails = [ + 'booking_id' => $booking->id, + 'initial_created_at' => $booking->created_at, + 'original_expiry' => $bookingExpiresAt->toDateTimeString(), + 'new_expiry' => $newBookingExpiresAt->toDateTimeString(), + 'valid_payments' => [] + ]; + foreach ($filteredPayments as $payment) { + $logDetails['valid_payments'][] = [ + 'payment_id' => $payment->id, + 'created_at' => $payment->created_at, + 'amount' => $payment->amount, + ]; + } + Log::info("Booking initially expired, but found valid pending payment(s).", $logDetails); + + $bookingExpiresAt = $newBookingExpiresAt; + $isExpired = Carbon::now()->greaterThan($bookingExpiresAt); + } else { + Log::info("Booking expired and no valid pending payments for booking ID: {$booking->id}"); + } + } + + if($isExpired){ + throw new CriteriaNotFulfilledException("Transfer has already expired."); + } + return true; + } +} diff --git a/app/Http/Controllers/Rules/CheckRuleController.php b/app/Http/Controllers/Rules/CheckRuleController.php index 881c6abe..59b24cd0 100644 --- a/app/Http/Controllers/Rules/CheckRuleController.php +++ b/app/Http/Controllers/Rules/CheckRuleController.php @@ -4,6 +4,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 Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -26,4 +27,13 @@ class CheckRuleController public function checkPurchaseOrderRule(Request $request, CheckPurchaseOrderRuleLogic $logic): JsonResponse { return $logic->execute($request); } + + /** + * @param Request $request + * @param CheckTransferRuleLogic $logic + * @return JsonResponse + */ + public function checkTransferRule(Request $request, CheckTransferRuleLogic $logic): JsonResponse { + return $logic->execute($request); + } } diff --git a/app/Http/Resources/V2/BookingV2Resource.php b/app/Http/Resources/V2/BookingV2Resource.php index a41d7e35..e1735081 100644 --- a/app/Http/Resources/V2/BookingV2Resource.php +++ b/app/Http/Resources/V2/BookingV2Resource.php @@ -53,15 +53,19 @@ class BookingV2Resource extends JsonResource 'created_at' => Carbon::parse($this->created_at)->format('d-m-Y'), 'created_at_with_time' => Carbon::parse($this->created_at)->format('d-m-Y h:i:s A'), $this->mergeWhen($this->relationLoaded('transactions'), [ - 'purchase_order' => new V2\TransactionV2Resource($this->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first()), + 'purchase_order' => new V2\TransactionV2Resource( + $this->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first()), 'payment_attempts' => V2\TransactionV2Resource::collection( $this->transactions() ->payments()->where('status', ApprovalStatus::PENDING_SUBMISSION) ->whereDate('expires_on', '>=', Carbon::now()) ->get() ), - 'expired_payment_attempts' => V2\TransactionV2Resource::collection($this->transactions()->payments()->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString())->get()), - 'payment_history' => V2\TransactionV2Resource::collection($this->transactions()->where(function($query){ + 'expired_payment_attempts' => V2\TransactionV2Resource::collection( + $this->transactions()->payments()->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString())->get() + ), + 'payment_history' => V2\TransactionV2Resource::collection( + $this->transactions()->where(function($query){ $query->where(function($query){ $query->payments()->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::COMPLETED, ApprovalStatus::REJECTED]); })->orWhere(function($query){ diff --git a/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue index b5858ee3..f9267240 100644 --- a/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue +++ b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue @@ -708,7 +708,10 @@ this.calculation = null; }, successHandler(response, section){ //E-Invoice - if(section === this.section + 'CheckEInvoiceRule'){ + if(section === this.section + 'CheckTransferRule'){ + this.checkEInvoiceRule(); + } + else if(section === this.section + 'CheckEInvoiceRule'){ if(response.payload.data.isPassed){ this.amount = (Math.round((this.data.outstanding_amount + Number.EPSILON) * 100) / 100).toFixed(2); this.expandPayment = true; @@ -751,7 +754,8 @@ $('#modal-einvoice-request').modal('show'); } else{ - this.checkEInvoiceRule(); + this.checkTransferRule(); + // this.checkEInvoiceRule(); } }, cancelQuotation(){ @@ -799,6 +803,14 @@ } }, //E-Invoice - Starts + checkTransferRule(){ + this.error = ''; + this.parameters = { + booking_id: this.data.id, + company_id: this.data.company.id, + }; + this.submit(route('api.rule.check.transfer'), 'post', this.section + 'CheckTransferRule', false, true); + }, checkEInvoiceRule(){ this.error = ''; this.parameters = { @@ -807,7 +819,6 @@ this.submit(route('api.rule.check.einvoice'), 'post', this.section + 'CheckEInvoiceRule', false, true); }, checkPurchaseOrderRule(){ - this.error = ''; this.error = ''; this.parameters = { booking_id: this.data.id, diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue index 4636db2a..942a30e4 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -194,6 +194,7 @@
+ diff --git a/routes/rule.php b/routes/rule.php index b2ea057b..d5330176 100644 --- a/routes/rule.php +++ b/routes/rule.php @@ -8,4 +8,5 @@ Route::prefix('rule') ->group(function () { Route::post('/check/eInvoice', [CheckRuleController::class, 'checkEInvoiceRule'])->name('check.einvoice'); Route::post('/check/purchase-order', [CheckRuleController::class, 'checkPurchaseOrderRule'])->name('check.purchase.order'); + Route::post('/check/tranfer', [CheckRuleController::class, 'checkTransferRule'])->name('check.transfer'); }); From 0643b28329669a359e774e28239b40122ecccadf Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Mon, 19 May 2025 02:54:00 +0800 Subject: [PATCH 4/5] E-Invoice - New state field when filling E-invoice info, validation of tin field and msic code field, etc... --- .../ControllersLogic/ListStatesLogic.php | 51 ++++++++ .../Addresses/Services/ListsStates.php | 33 ++++++ .../UpdateCompanyEInvoiceInfoLogic.php | 2 +- .../DataTransferObjects/EInvoiceInfoDTO.php | 15 ++- .../Addresses/ListStatesController.php | 19 +++ app/Http/Resources/StateResource.php | 22 ++++ .../forms/EInvoiceInfoFormComponent.vue | 111 ++++++++++++------ .../elements/EInvoiceInfoComponent.vue | 2 +- .../BookingPaymentQuotationV2Component.vue | 3 +- .../forms/ValidationErrorComponent.vue | 13 +- routes/crud.php | 3 + 11 files changed, 230 insertions(+), 44 deletions(-) create mode 100644 app/Classes/Modules/Addresses/ControllersLogic/ListStatesLogic.php create mode 100644 app/Classes/Modules/Addresses/Services/ListsStates.php create mode 100644 app/Http/Controllers/Addresses/ListStatesController.php create mode 100644 app/Http/Resources/StateResource.php diff --git a/app/Classes/Modules/Addresses/ControllersLogic/ListStatesLogic.php b/app/Classes/Modules/Addresses/ControllersLogic/ListStatesLogic.php new file mode 100644 index 00000000..60ecd262 --- /dev/null +++ b/app/Classes/Modules/Addresses/ControllersLogic/ListStatesLogic.php @@ -0,0 +1,51 @@ + 'Retrieved States', + 'message' => 'You have successfully retrieved a list of States' + ]; + } + + /** @var ListsStates */ + private $listsStates; + + /** + * ListStatesLogic constructor. + * @param ListsStates $listsStates + */ + public function __construct(ListsStates $listsStates) + { + $this->listsStates = $listsStates; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws \App\Classes\Exceptions\MalformedRequestException + */ + public function logic(Request $request) : JsonResponse + { + $query = $this->listsStates->execute($this->listsStates->deserializeFilters($request->input('filters'))); + + return $this->collectionResponse(StateResource::collection($query)); + + } + +} diff --git a/app/Classes/Modules/Addresses/Services/ListsStates.php b/app/Classes/Modules/Addresses/Services/ListsStates.php new file mode 100644 index 00000000..c7ac955f --- /dev/null +++ b/app/Classes/Modules/Addresses/Services/ListsStates.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceInfoLogic.php b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceInfoLogic.php index 1b050809..3b7006ae 100644 --- a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceInfoLogic.php +++ b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceInfoLogic.php @@ -93,7 +93,7 @@ class UpdateCompanyEInvoiceInfoLogic extends AbstractControllerLogic } $district = $this->fetchesDistrict->execute(['id' => $dto->districtId]); - $object = new AddressObject($dto->streetOne, $dto->streetTwo, $district->country_id, $district->state_id, $district->id, $dto->postCode); + $object = new AddressObject($dto->streetOne, $dto->streetTwo, $district->country_id, $dto->stateId, $district->id, $dto->postCode); //Update Address $this->canCreateAddress->passes($object); diff --git a/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php index 2717bf7c..893f20c7 100644 --- a/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php +++ b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php @@ -6,9 +6,10 @@ use App\Classes\General\Interfaces\DataTransferObject; class EInvoiceInfoDTO implements DataTransferObject { - public int $tin; + public string $tin; public int $msicCode; public int $districtId; + public int $stateId; public int $companyId; public string $streetOne; public string $streetTwo; @@ -16,13 +17,14 @@ class EInvoiceInfoDTO implements DataTransferObject public function __construct(array $data) { - $this->tin = (int) ($data['tin'] ?? ''); - $this->msicCode = (int) ($data['msic_code'] ?? ''); - $this->districtId = (int) ($data['district_id'] ?? ''); - $this->companyId = (int) ($data['company_id'] ?? ''); + $this->tin = (string) ($data['tin'] ?? ''); + $this->msicCode = (int) ($data['msic_code'] ?? 0); + $this->districtId = (int) ($data['district_id'] ?? 0); + $this->stateId = (int) ($data['state_id'] ?? 0); + $this->companyId = (int) ($data['company_id'] ?? 0); $this->streetOne = (string) ($data['street_one'] ?? ''); $this->streetTwo = (string) ($data['street_two'] ?? ''); - $this->postCode = (int) ($data['post_code'] ?? ''); + $this->postCode = (int) ($data['post_code'] ?? 0); } public function toArray(): array @@ -31,6 +33,7 @@ class EInvoiceInfoDTO implements DataTransferObject 'tin' => $this->tin, 'msic_code' => $this->msicCode, 'district_id' => $this->districtId, + 'state_id' => $this->stateId, 'company_id' => $this->companyId, 'street_one' => $this->streetOne, 'street_two' => $this->streetTwo, diff --git a/app/Http/Controllers/Addresses/ListStatesController.php b/app/Http/Controllers/Addresses/ListStatesController.php new file mode 100644 index 00000000..a881ad6c --- /dev/null +++ b/app/Http/Controllers/Addresses/ListStatesController.php @@ -0,0 +1,19 @@ +execute($request); + } +} diff --git a/app/Http/Resources/StateResource.php b/app/Http/Resources/StateResource.php new file mode 100644 index 00000000..d73c455c --- /dev/null +++ b/app/Http/Resources/StateResource.php @@ -0,0 +1,22 @@ + $this->id, + 'state' => $this->name, + ]; + } +} diff --git a/resources/assets/vue/components/address/forms/EInvoiceInfoFormComponent.vue b/resources/assets/vue/components/address/forms/EInvoiceInfoFormComponent.vue index 5478b2f3..aad9ba94 100644 --- a/resources/assets/vue/components/address/forms/EInvoiceInfoFormComponent.vue +++ b/resources/assets/vue/components/address/forms/EInvoiceInfoFormComponent.vue @@ -32,7 +32,7 @@
- +
@@ -40,7 +40,7 @@
- +
@@ -59,6 +59,14 @@
+
+
+ + + + +
+
@@ -103,7 +111,7 @@

Billing Address

-

{{parameters.street_one}} {{parameters.street_two}}, {{districts[parseFloat(parameters.district_id) - 1].city}} {{parameters.post_code}} {{districts[parseFloat(parameters.district_id) - 1].state.name}}, {{districts[parseFloat(parameters.district_id) - 1].country.name}}

+

{{parameters.street_one}} {{parameters.street_two}}, {{districts[parseFloat(parameters.district_id) - 1].city}} {{parameters.post_code}} {{states[parseFloat(parameters.state_id) - 1].state}}, {{districts[parseFloat(parameters.district_id) - 1].country.name}}

@@ -128,68 +136,105 @@