diff --git a/.env.example b/.env.example index 05b97dee..a3ebeffe 100644 --- a/.env.example +++ b/.env.example @@ -75,3 +75,8 @@ LARAVEL_VAPOR_ENABLED=false COMMANDS_V2_ENABLED=false SENDING_EMAIL_ENABLED=false SENDING_EMAIL_WELCOME_VOUCHER_ENABLED=false + + +E_INVOICE_START_DATE="2025-07-01 00:00:00" +MAINTENANCE_MESSAGE_TITLE="We'll be back online on 00:00 1/7/2025" +MAINTENANCE_MESSAGE="Sorry for the inconvenience but we're performing some maintenance at the moment." diff --git a/app/Classes/Exceptions/CriteriaNotFulfilledException.php b/app/Classes/Exceptions/CriteriaNotFulfilledException.php new file mode 100644 index 00000000..da105690 --- /dev/null +++ b/app/Classes/Exceptions/CriteriaNotFulfilledException.php @@ -0,0 +1,14 @@ +getMessage()); } catch(\Exception $exception){ throw new RequestValidationException($exception->getMessage()); } } - } diff --git a/app/Classes/General/Eloquent/Filters/IsEInvoice.php b/app/Classes/General/Eloquent/Filters/IsEInvoice.php new file mode 100644 index 00000000..001b5f3e --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/IsEInvoice.php @@ -0,0 +1,19 @@ +where('e_invoice', $value); + } +} diff --git a/app/Classes/General/Helper.php b/app/Classes/General/Helper.php index 45c72f53..cb77d936 100644 --- a/app/Classes/General/Helper.php +++ b/app/Classes/General/Helper.php @@ -4,8 +4,8 @@ namespace App\Classes\General; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Support\Facades\Log; - use Illuminate\Support\Str; +use NumberToWords\NumberToWords; class Helper { @@ -66,4 +66,25 @@ class Helper return json_decode($collection->response()->getContent(), true); } + /** + * Convert a given number to words based on the specified locale. + * + * @param int|float $number + * @param string $locale The locale to use for conversion (default is 'en'). + * @return string + */ + static function convert($number, $locale = 'en') + { + $numberToWords = new NumberToWords(); + $numberTransformer = $numberToWords->getNumberTransformer($locale); + + $number = number_format((float)$number, 2, '.', ''); + [$ringgit, $cents] = explode('.', $number); + + $ringgitWords = $numberTransformer->toWords((int)$ringgit); + $centsWords = $numberTransformer->toWords((int)$cents); + + return strtoupper('ringgit ' . $ringgitWords . ' and ' . $centsWords . ' cents only'); + } + } diff --git a/app/Classes/Modules/Addresses/ControllersLogic/ListAddressesLogic.php b/app/Classes/Modules/Addresses/ControllersLogic/ListAddressesLogic.php index 57e3a2f0..c75f0623 100644 --- a/app/Classes/Modules/Addresses/ControllersLogic/ListAddressesLogic.php +++ b/app/Classes/Modules/Addresses/ControllersLogic/ListAddressesLogic.php @@ -50,10 +50,10 @@ class ListAddressesLogic extends AbstractControllerLogic public function logic(Request $request) : JsonResponse { try { - $this->canListAddresses->passes(); - $query = $this->listsAddresses->execute($this->listsAddresses->deserializeFilters($request->input('filters'))); + $query = $this->listsAddresses->execute( + array_merge($this->listsAddresses->deserializeFilters($request->input('filters')), ['is_e_invoice' => false])); //exclude all addresses meant for e_invoice return $this->collectionResponse(AddressResource::collection($query)); @@ -63,4 +63,4 @@ class ListAddressesLogic extends AbstractControllerLogic } -} \ No newline at end of file +} 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/Addresses/Services/UpdatesAddressMetadata.php b/app/Classes/Modules/Addresses/Services/UpdatesAddressMetadata.php new file mode 100644 index 00000000..cfecb739 --- /dev/null +++ b/app/Classes/Modules/Addresses/Services/UpdatesAddressMetadata.php @@ -0,0 +1,25 @@ +billing = $billing; + $model->e_invoice = $eInvoice; + + return $this->handler($model); + } +} diff --git a/app/Classes/Modules/Addresses/Services/UpsertsAddress.php b/app/Classes/Modules/Addresses/Services/UpsertsAddress.php new file mode 100644 index 00000000..ec130189 --- /dev/null +++ b/app/Classes/Modules/Addresses/Services/UpsertsAddress.php @@ -0,0 +1,36 @@ +addresses()->where('id', $id)->first() ?? new Address(); + + $model->street_one = $object->getStreetOne(); + $model->street_two = $object->getStreetTwo(); + $model->country_id = $object->getCountryId(); + $model->state_id = $object->getStateId(); + $model->district_id = $object->getDistrictId(); + $model->postcode = $object->getPostCode(); + + return $this->handler($company->addresses(), $model); + } +} diff --git a/app/Classes/Modules/Billplzs/ControllersLogic/CallbackBillplzLogic.php b/app/Classes/Modules/Billplzs/ControllersLogic/CallbackBillplzLogic.php index 186ef3b6..b6537420 100644 --- a/app/Classes/Modules/Billplzs/ControllersLogic/CallbackBillplzLogic.php +++ b/app/Classes/Modules/Billplzs/ControllersLogic/CallbackBillplzLogic.php @@ -5,6 +5,7 @@ namespace App\Classes\Modules\Billplzs\ControllersLogic; use App\Classes\Modules\Wallets\DataTransferObjects\WalletObject; use App\Classes\Modules\Wallets\Services\UpdatesWallet; use App\Classes\Modules\Transactions\Processors\CreateCashBackTransactionProcessor; +use App\Classes\Modules\Transactions\Processors\CreateReceiptVoucherTransactionProcessor; use App\Classes\Exceptions\ResourceNotFoundException; use App\Classes\Modules\Wallets\Services\UpdatesWalletBalance; @@ -49,6 +50,9 @@ class CallbackBillplzLogic /** @var RecalculatesWalletBalance */ private $recalculatesWalletBalance; + /** @var CreateReceiptVoucherTransactionProcessor */ + private $createReceiptVoucherTransactionProcessor; + /** * CallbackBillplzLogic constructor. * @param GetBillplzBill $getBillplzBill @@ -56,8 +60,9 @@ class CallbackBillplzLogic * @param UpdatesTransactionStatus $updatesTransactionStatus * @param UpdatesWalletBalance $updatesWalletBalance * @param RecalculatesWalletBalance $recalculatesWalletBalance + * @param CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor */ - public function __construct(GetBillplzBill $getBillplzBill, FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, UpdatesWalletBalance $updatesWalletBalance, CreateCashBackTransactionProcessor $createCashBackTransactionProcessor, RecalculatesWalletBalance $recalculatesWalletBalance) + public function __construct(GetBillplzBill $getBillplzBill, FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, UpdatesWalletBalance $updatesWalletBalance, CreateCashBackTransactionProcessor $createCashBackTransactionProcessor, RecalculatesWalletBalance $recalculatesWalletBalance, CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor) { $this->getBillplzBill = $getBillplzBill; $this->fetchesTransaction = $fetchesTransaction; @@ -65,6 +70,7 @@ class CallbackBillplzLogic $this->updatesWalletBalance = $updatesWalletBalance; $this->createCashBackTransactionProcessor = $createCashBackTransactionProcessor; $this->recalculatesWalletBalance = $recalculatesWalletBalance; + $this->createReceiptVoucherTransactionProcessor = $createReceiptVoucherTransactionProcessor; } @@ -105,6 +111,12 @@ class CallbackBillplzLogic $this->updatesWalletBalance->execute($transaction->owner, $transaction->amount); } + $booking = $transaction->owner instanceof Booking ? $transaction->booking : (count($transaction->owner->owner->bookings()->get())? $transaction->owner->owner->bookings()->orderBy('id', 'DESC')->first(): null); + + if($transaction->owner instanceof Booking && $booking && $transaction){ + $this->createReceiptVoucherTransactionProcessor->execute($booking, $transaction); + } + // if ($transaction->type == TransactionType::PAYMENT) { // $cash_back_transaction = $this->createCashBackTransactionProcessor->execute($transaction); // } @@ -117,4 +129,4 @@ class CallbackBillplzLogic return $request->method() === 'POST' ? true : view('pages.payments_redirect', ['marking' => $marking ?? null, 'transaction' => $transaction, 'status' => $status]); } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Billplzs/Services/DeletesBillplzBill.php b/app/Classes/Modules/Billplzs/Services/DeletesBillplzBill.php new file mode 100644 index 00000000..40321642 --- /dev/null +++ b/app/Classes/Modules/Billplzs/Services/DeletesBillplzBill.php @@ -0,0 +1,33 @@ +delete(config('billplz.base_url').'/api/v3/bills/'.$billID); + Log::info('DeletesBillplzBill bill with id '. $billID . ' response: '.json_encode($response)); + + if($response->successful()){ + $data = $response->json(); + + // $data['url'] = $data['url'].'?auto_submit=true'; + return (object) $data; + }else{ + return null; + } + }catch(\Exception $exception){ + throw new MalformedRequestException('Unable to get correct response from billplz server: ' . $exception->getMessage()); + } + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ApprovePaymentVerificationLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ApprovePaymentVerificationLogic.php index c20a44d2..7d32671e 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/ApprovePaymentVerificationLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/ApprovePaymentVerificationLogic.php @@ -11,42 +11,15 @@ use App\Classes\Modules\Transactions\Services\FetchesTransaction; use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; use App\Classes\Modules\Transactions\Processors\CreateCashBackTransactionProcessor; +use App\Classes\Modules\Transactions\Processors\CreateReceiptVoucherTransactionProcessor; use App\Classes\ValueObjects\Constants\ApprovalStatus; - +use App\Models\Booking; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class ApprovePaymentVerificationLogic extends AbstractControllerLogic { - /** - * ApprovePaymentVerificationLogic constructor. - * @param FetchesTransaction $fetchesTransaction - * @param UpdatesTransactionStatus $updatesTransactionStatus - * @param ApprovesDocument $approvesDocument - * @param RejectsDocument $rejectsDocument - * @param FetchesDocument $fetchesDocument - * @param CreateCashBackTransactionProcessor $createCashBackTransactionProcessor - */ - public function __construct(FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, ApprovesDocument $approvesDocument, RejectsDocument $rejectsDocument, FetchesDocument $fetchesDocument, CreateCashBackTransactionProcessor $createCashBackTransactionProcessor) - { - $this->fetchesTransaction = $fetchesTransaction; - $this->updatesTransactionStatus = $updatesTransactionStatus; - $this->approvesDocument = $approvesDocument; - $this->rejectsDocument = $rejectsDocument; - $this->createCashBackTransactionProcessor = $createCashBackTransactionProcessor; - } - - /** - * @return array - */ - protected function notification():array { - return [ - 'title' => 'Payment Status', - 'message' => 'You have successfully updated the payment status' - ]; - } - /** @var FetchesTransaction */ private $fetchesTransaction; @@ -62,6 +35,39 @@ class ApprovePaymentVerificationLogic extends AbstractControllerLogic /** @var CreateCashBackTransactionProcessor */ private $createCashBackTransactionProcessor; + /** @var CreateReceiptVoucherTransactionProcessor */ + private $createReceiptVoucherTransactionProcessor; + + /** + * ApprovePaymentVerificationLogic constructor. + * @param FetchesTransaction $fetchesTransaction + * @param UpdatesTransactionStatus $updatesTransactionStatus + * @param ApprovesDocument $approvesDocument + * @param RejectsDocument $rejectsDocument + * @param FetchesDocument $fetchesDocument + * @param CreateCashBackTransactionProcessor $createCashBackTransactionProcessor + * @param CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor + */ + public function __construct(FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, ApprovesDocument $approvesDocument, RejectsDocument $rejectsDocument, FetchesDocument $fetchesDocument, CreateCashBackTransactionProcessor $createCashBackTransactionProcessor, CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor) + { + $this->fetchesTransaction = $fetchesTransaction; + $this->updatesTransactionStatus = $updatesTransactionStatus; + $this->approvesDocument = $approvesDocument; + $this->rejectsDocument = $rejectsDocument; + $this->createCashBackTransactionProcessor = $createCashBackTransactionProcessor; + $this->createReceiptVoucherTransactionProcessor = $createReceiptVoucherTransactionProcessor; + } + + /** + * @return array + */ + protected function notification():array { + return [ + 'title' => 'Payment Status', + 'message' => 'You have successfully updated the payment status' + ]; + } + /** * @param Request $request * @return JsonResponse @@ -77,9 +83,14 @@ class ApprovePaymentVerificationLogic extends AbstractControllerLogic $this->updatesTransactionStatus->execute($transaction, $status === 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED); + $booking = $transaction->owner instanceof Booking ? $transaction->booking : null; + + if($booking && $transaction){ + $this->createReceiptVoucherTransactionProcessor->execute($booking, $transaction); + } // $this->createCashBackTransactionProcessor->execute($transaction); return $this->response([]); } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php index 478c71a4..f67fa669 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php @@ -2,7 +2,7 @@ namespace App\Classes\Modules\Bookings\ControllersLogic; - +use App\Classes\Exceptions\CriteriaNotFulfilledException; use App\Classes\Exceptions\MalformedRequestException; use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Bookings\Services\CalculatesBookingOutstanding; @@ -16,9 +16,17 @@ 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\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; +use App\Classes\Modules\Rules\Services\RuleEvaluator; +use App\Classes\Modules\Rules\Standards\Rules\CanPassOrderDurationLimitRule; +use App\Classes\Modules\Transactions\Processors\CreateReceiptVoucherTransactionProcessor; +use App\Classes\Modules\Transactions\Services\CalculatesTransactionExpiryDateTime; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\PaymentMethodType; use App\Classes\ValueObjects\Constants\TransactionType; @@ -80,6 +88,27 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic /** @var CalculatesBookingRefundAmount */ private $calculatesBookingRefundAmount; + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** @var CreateReceiptVoucherTransactionProcessor */ + private $createReceiptVoucherTransactionProcessor; + + /** @var CanPassOrderDurationLimitRule */ + protected $canPassOrderDurationLimitRule; + + /** @var CanPassEInvoicePromptedRule */ + protected $canPassEInvoicePromptedRule; + + /** @var CanPassTINRule */ + protected $canPassTINRule; + + /** @var CanPassPurchaseOrderRule */ + protected $canPassPurchaseOrderRule; + + /** @var CalculatesTransactionExpiryDateTime */ + protected $calculatesTransactionExpiryDateTime; + /** * CreateBookingPaymentLogic constructor. * @param FetchesBookingQuotation $fetchBookingQuotation @@ -94,8 +123,15 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic * @param RecalculatesWalletBalance $recalculatesWalletBalance * @param BookingToVoucherifyProcessor $bookingToVoucherifyProcessor * @param CalculatesBookingRefundAmount $calculatesBookingRefundAmount + * @param RuleEvaluator $ruleEvaluator + * @param CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor + * @param CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule + * @param CanPassEInvoicePromptedRule $canPassEInvoicePromptedRule + * @param CanPassTINRule $canPassTINRule + * @param CanPassPurchaseOrderRule $canPassPurchaseOrderRule + * @param CalculatesTransactionExpiryDateTime $calculatesTransactionExpiryDateTime */ - public function __construct(FetchesBookingQuotation $fetchBookingQuotation, FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, CalculatesBookingOutstanding $calculatesBookingOutstanding, CreatesBillplzBill $createsBillplzBill, UpdatesWalletBalance $updatesWalletBalance, UpdatesTransactionStatus $updatesTransactionStatus, CreateCashBackTransactionProcessor $createCashBackTransactionProcessor, RecalculatesWalletBalance $recalculatesWalletBalance, BookingToVoucherifyProcessor $bookingToVoucherifyProcessor, CalculatesBookingRefundAmount $calculatesBookingRefundAmount) + public function __construct(FetchesBookingQuotation $fetchBookingQuotation, FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, CalculatesBookingOutstanding $calculatesBookingOutstanding, CreatesBillplzBill $createsBillplzBill, UpdatesWalletBalance $updatesWalletBalance, UpdatesTransactionStatus $updatesTransactionStatus, CreateCashBackTransactionProcessor $createCashBackTransactionProcessor, RecalculatesWalletBalance $recalculatesWalletBalance, BookingToVoucherifyProcessor $bookingToVoucherifyProcessor, CalculatesBookingRefundAmount $calculatesBookingRefundAmount, RuleEvaluator $ruleEvaluator, CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor, CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule, CanPassEInvoicePromptedRule $canPassEInvoicePromptedRule, CanPassTINRule $canPassTINRule, CanPassPurchaseOrderRule $canPassPurchaseOrderRule, CalculatesTransactionExpiryDateTime $calculatesTransactionExpiryDateTime) { $this->fetchBookingQuotation = $fetchBookingQuotation; $this->fetchesCompanyPaymentAttemptLimit = $fetchesCompanyPaymentAttemptLimit; @@ -109,15 +145,35 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic $this->recalculatesWalletBalance = $recalculatesWalletBalance; $this->bookingToVoucherifyProcessor = $bookingToVoucherifyProcessor; $this->calculatesBookingRefundAmount = $calculatesBookingRefundAmount; + $this->ruleEvaluator = $ruleEvaluator; + $this->createReceiptVoucherTransactionProcessor = $createReceiptVoucherTransactionProcessor; + $this->canPassOrderDurationLimitRule = $canPassOrderDurationLimitRule; + $this->canPassEInvoicePromptedRule = $canPassEInvoicePromptedRule; + $this->canPassTINRule = $canPassTINRule; + $this->canPassPurchaseOrderRule = $canPassPurchaseOrderRule; + $this->calculatesTransactionExpiryDateTime = $calculatesTransactionExpiryDateTime; } /** * @param Request $request * @return JsonResponse * @throws MalformedRequestException + * @throws CriteriaNotFulfilledException */ public function logic(Request $request) : JsonResponse { + $dto = new ConfirmBookingDTO($request->all()); + $result = $this->ruleEvaluator->evaluate([ + $this->canPassOrderDurationLimitRule, + $this->canPassEInvoicePromptedRule, + $this->canPassTINRule, + $this->canPassPurchaseOrderRule, + ], $dto); + + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } + $voucherCode = $request->input('voucher_code'); $booking = Booking::find($request->route('id')); @@ -162,11 +218,14 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic $billNumber = $this->generatesTransactionBillNumber->execute('PYMT-'); + // $expiresOn = Carbon::now()->addMinutes($paymentAttemptLimit); + $expiresOn = $this->calculatesTransactionExpiryDateTime->execute($booking->id); + $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, [], $paymentReference); + $configurations->getTax(), $configurations->getServiceCharge(), $expiresOn, ApprovalStatus::PENDING_SUBMISSION, [], $paymentReference); /** @var Transaction $transaction */ $transaction = $this->createsTransaction->execute($booking, $object); @@ -176,6 +235,7 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic if(PaymentMethodType::PAYMENT_METHODS[$request->input('payment_method')] == PaymentMethodType::WALLET){ $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::APPROVED); + $this->createReceiptVoucherTransactionProcessor->execute($booking, $transaction); } return $this->resourceResponse(new TransactionResource($transaction)); diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingRefundLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingRefundLogic.php index 49541b35..16d047a8 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingRefundLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingRefundLogic.php @@ -93,9 +93,10 @@ class CreateBookingRefundLogic extends AbstractControllerLogic $invoice = $booking->transactions()->where('type', TransactionType::INVOICE)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->first(); - if(auth()->user()->type === 3) { - throw new MalformedRequestException('You do not have the permission to refund the order.'); - } + //cief todo: 90 - move this into rules + // if(auth()->user()->type === 3) { + // throw new MalformedRequestException('You do not have the permission to refund the order.'); + // } $billNumber = $this->generatesTransactionBillNumber->execute('RFD-'); @@ -117,7 +118,7 @@ class CreateBookingRefundLogic extends AbstractControllerLogic $refundAmount = $transaction->original_amount / $transaction->currency_rate; $service_charges_to_refund = $transaction->service_charge; } else { - // partial refund + // partial refund $refundAmount = bcdiv($request->input('amount'), $transaction->currency_rate, 7); $bookingAmountBeforeCurrentRefund = $booking->fix_amount - $refundInPending; @@ -159,7 +160,7 @@ class CreateBookingRefundLogic extends AbstractControllerLogic $refund_transaction = $this->createsTransaction->execute($transaction, $object); - $bookingInWhiteForm = $transaction->transactions()->bills()->first(); + $bookingInWhiteForm = $transaction->transactions()->bills()->first(); // if no white form created yet can approve right away // create supplier refund if ($bookingInWhiteForm) { diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreatePaymentVerificationDocumentLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreatePaymentVerificationDocumentLogic.php index bfd0229d..cd39e81a 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/CreatePaymentVerificationDocumentLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreatePaymentVerificationDocumentLogic.php @@ -2,8 +2,9 @@ namespace App\Classes\Modules\Bookings\ControllersLogic; - +use App\Classes\Exceptions\CriteriaNotFulfilledException; use App\Classes\General\Abstracts\AbstractControllerLogic; +use App\Classes\Modules\Bookings\DataTransferObjects\CreatePaymentVerificationDocumentDTO; use App\Classes\Modules\Companies\Services\FetchesCompany; use App\Classes\Modules\Companies\Services\UpdatesCompanyStatus; use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject; @@ -11,6 +12,8 @@ use App\Classes\Modules\Documents\Services\CreatesDocument; use App\Classes\Modules\Documents\Services\CreatesFiles; use App\Classes\Modules\Transactions\Services\FetchesTransaction; use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; +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\CompanyType; use App\Classes\ValueObjects\Constants\DocumentType; @@ -44,19 +47,29 @@ class CreatePaymentVerificationDocumentLogic extends AbstractControllerLogic /** @var UpdatesTransactionStatus */ private $updatesTransactionStatus; + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** @var CanPassOrderDurationLimitRule */ + private $canPassOrderDurationLimitRule; + /** * CreatePaymentVerificationDocumentLogic constructor. * @param FetchesTransaction $fetchesTransaction * @param CreatesDocument $createsDocument * @param CreatesFiles $createsFile * @param UpdatesTransactionStatus $updatesTransactionStatus + * @param RuleEvaluator $ruleEvaluator + * @param CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule */ - public function __construct(FetchesTransaction $fetchesTransaction, CreatesDocument $createsDocument, CreatesFiles $createsFile, UpdatesTransactionStatus $updatesTransactionStatus) + public function __construct(FetchesTransaction $fetchesTransaction, CreatesDocument $createsDocument, CreatesFiles $createsFile, UpdatesTransactionStatus $updatesTransactionStatus, RuleEvaluator $ruleEvaluator, CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule) { $this->fetchesTransaction = $fetchesTransaction; $this->createsDocument = $createsDocument; $this->createsFile = $createsFile; $this->updatesTransactionStatus = $updatesTransactionStatus; + $this->ruleEvaluator = $ruleEvaluator; + $this->canPassOrderDurationLimitRule = $canPassOrderDurationLimitRule; } /** @@ -66,6 +79,15 @@ class CreatePaymentVerificationDocumentLogic extends AbstractControllerLogic */ public function logic(Request $request) : JsonResponse { + $dto = new CreatePaymentVerificationDocumentDTO($request->all()); + + $result = $this->ruleEvaluator->evaluate([ + $this->canPassOrderDurationLimitRule, + ], $dto); + + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } $transaction = $this->fetchesTransaction->execute(['id' => $request->route('payment_id')]); @@ -80,4 +102,4 @@ class CreatePaymentVerificationDocumentLogic extends AbstractControllerLogic return $this->response([]); } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateBookingPaymentRVLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateBookingPaymentRVLogic.php new file mode 100644 index 00000000..8106eb8f --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateBookingPaymentRVLogic.php @@ -0,0 +1,81 @@ + 'Regenerate Booking Payment Receipt Voucher', + 'message' => 'You have successfully regenerate booking payment receipt voucher' + ]; + } + + /** @var CanFetchBooking */ + private $canFetchBooking; + + /** @var FetchesBooking */ + private $fetchesBooking; + + /** @var FetchesTransaction */ + private $fetchesTransaction; + + /** @var CreateReceiptVoucherTransactionProcessor */ + private $createReceiptVoucherTransactionProcessor; + + /** + * RegenerateBookingPaymentRVLogic constructor. + * @param CanFetchBooking $canFetchBooking + * @param FetchesBooking $fetchesBooking + * @param FetchesTransaction $fetchesTransaction + * @param CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor + */ + public function __construct( + CanFetchBooking $canFetchBooking, + FetchesBooking $fetchesBooking, + FetchesTransaction $fetchesTransaction, + CreateReceiptVoucherTransactionProcessor $createReceiptVoucherTransactionProcessor + ) { + $this->canFetchBooking = $canFetchBooking; + $this->fetchesBooking = $fetchesBooking; + $this->fetchesTransaction = $fetchesTransaction; + $this->createReceiptVoucherTransactionProcessor = $createReceiptVoucherTransactionProcessor; + } + + + /** + * @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')]); + $transaction = $this->fetchesTransaction->execute(['id' => $request->input('paymentId')]); + + if($booking && $transaction){ + $this->createReceiptVoucherTransactionProcessor->execute($booking, $transaction, true); + } + + return $this->resourceResponse(new BookingResource($booking)); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php index 5d067fd5..4a63eca4 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php @@ -9,7 +9,6 @@ use App\Classes\Modules\Bookings\Services\UpdatesBookingStatus; use App\Classes\Modules\Transactions\Services\DeletesTransaction; use App\Classes\Modules\Documents\Services\DeletesDocument; use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionProcessor; -use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionWithInvoiceNoProcessor; use Illuminate\Support\Str; use App\Classes\ValueObjects\Constants\DocumentType; use App\Http\Resources\BookingResource; @@ -52,9 +51,6 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic /** @var CreateInvoiceTransactionProcessor */ private $createInvoiceTransactionProcessor; - /** @var CreateInvoiceTransactionWithInvoiceNoProcessor */ - private $createInvoiceTransactionWithInvoiceNoProcessor; - /** * FetchBookingLogic constructor. * @param CanFetchBooking $canFetchBooking @@ -63,7 +59,6 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic * @param UpdatesBookingStatus $updatesBookingStatus * @param DeletesDocument $deletesDocument * @param CreateInvoiceTransactionProcessor $createInvoiceTransactionProcessor - * @param CreateInvoiceTransactionWithInvoiceNoProcessor $createInvoiceTransactionWithInvoiceNoProcessor */ public function __construct( CanFetchBooking $canFetchBooking, @@ -71,8 +66,7 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic DeletesTransaction $deletesTransaction, UpdatesBookingStatus $updatesBookingStatus, DeletesDocument $deletesDocument, - CreateInvoiceTransactionProcessor $createInvoiceTransactionProcessor, - CreateInvoiceTransactionWithInvoiceNoProcessor $createInvoiceTransactionWithInvoiceNoProcessor + CreateInvoiceTransactionProcessor $createInvoiceTransactionProcessor ) { $this->canFetchBooking = $canFetchBooking; $this->fetchesBooking = $fetchesBooking; @@ -80,7 +74,6 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic $this->updatesBookingStatus = $updatesBookingStatus; $this->deletesDocument = $deletesDocument; $this->createInvoiceTransactionProcessor = $createInvoiceTransactionProcessor; - $this->createInvoiceTransactionWithInvoiceNoProcessor = $createInvoiceTransactionWithInvoiceNoProcessor; } @@ -119,8 +112,10 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic // update currentInvoice bill_no to '-deleted-' $currentInvoice = $booking->transactions()->where('type', TransactionType::INVOICE)->first(); - $currentInvoice->bill_no = $currentInvoice->bill_no ."-deleted-" . (string)(Carbon::now()->timestamp); - $currentInvoice->save(); + if($currentInvoice){ + $currentInvoice->bill_no = $currentInvoice->bill_no ."-deleted-" . (string)(Carbon::now()->timestamp); + $currentInvoice->save(); + } $transactionWithSameBillNo = Transaction::where('bill_no', $firstBillNo)->withTrashed()->get(); if ($transactionWithSameBillNo) { @@ -140,7 +135,7 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic $this->deletesDocument->execute($row); } - $this->createInvoiceTransactionWithInvoiceNoProcessor->execute($booking, $firstBillNo); + $this->createInvoiceTransactionProcessor->execute($booking, $firstBillNo, true); return $this->resourceResponse(new BookingResource($booking)); } diff --git a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountOnHoldLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountOnHoldLogic.php new file mode 100644 index 00000000..39dc6674 --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountOnHoldLogic.php @@ -0,0 +1,115 @@ + 'Recorded Booking Amount', + 'message' => 'You have successfully recorded the Booking Amount to be updated' + ]; + } + + /** @var CanUpdateBooking */ + private $canUpdateBooking; + + /** @var UpdatesBookingFixedAmount */ + private $updatesBookingFixedAmount; + + /** @var FetchesBooking */ + private $fetchesBooking; + + /** @var CalculatesBookingOutstanding */ + private $calculatesBookingOutstanding; + + /** @var UpdatesTransactionStatus */ + private $updatesTransactionStatus; + + /** @var CreatesKeyValuePair */ + private $createsKeyValuePair; + + /** @var UpdatesKeyValuePair */ + private $updatesKeyValuePair; + + /** + * UpdateBookingAmountLogic constructor. + * @param CanUpdateBooking $canUpdateBooking + * @param UpdatesBookingFixedAmount $updatesBookingFixedAmount + * @param FetchesBooking $fetchesBooking + * @param CalculatesBookingOutstanding $calculatesBookingOutstanding + * @param UpdatesTransactionStatus $updatesTransactionStatus + * @param CreatesKeyValuePair $createsKeyValuePair + * @param UpdatesKeyValuePair $updatesKeyValuePair + */ + public function __construct(CanUpdateBooking $canUpdateBooking, UpdatesBookingFixedAmount $updatesBookingFixedAmount, FetchesBooking $fetchesBooking, CalculatesBookingOutstanding $calculatesBookingOutstanding, UpdatesTransactionStatus $updatesTransactionStatus, CreatesKeyValuePair $createsKeyValuePair, UpdatesKeyValuePair $updatesKeyValuePair) + { + $this->canUpdateBooking = $canUpdateBooking; + $this->updatesBookingFixedAmount = $updatesBookingFixedAmount; + $this->fetchesBooking = $fetchesBooking; + $this->calculatesBookingOutstanding = $calculatesBookingOutstanding; + $this->updatesTransactionStatus = $updatesTransactionStatus; + $this->createsKeyValuePair = $createsKeyValuePair; + $this->updatesKeyValuePair = $updatesKeyValuePair; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws MalformedRequestException + */ + public function logic(Request $request) : JsonResponse + { + $booking = $this->fetchesBooking->execute(['id' => $request->route('id')]); + + $input_amount = number_format( floatval(str_replace(',', '', $request->input('amount_to_edit', $booking->fix_amount))), 5, '.', ''); + + $minimum_amount = $booking->fix_amount - $this->calculatesBookingOutstanding->execute($booking); + + if (((float)$input_amount + 0.01) < (float)$minimum_amount) { + throw new MalformedRequestException('Booking Amount cannot be less than '. $minimum_amount .'.'); + } + + $key = "BOOKING_AMOUNT_UPDATE"; + $keyValuePairObject = new KeyValuePairObject($key, $input_amount); + $metadata = $booking->attributesKVP()->where('key', $key)->first(); + if($metadata){ + $this->updatesKeyValuePair->execute($metadata, $keyValuePairObject); + } + else{ + $this->createsKeyValuePair->execute($booking, $keyValuePairObject); + } + + $poTransaction = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first(); + // $booking->transactions()->where('type', TransactionType::PROFORMA)->delete(); + + if($poTransaction) { + $this->updatesTransactionStatus->execute($poTransaction, ApprovalStatus::PENDING_SUBMISSION); + } + + // $booking = $this->updatesBookingFixedAmount->execute($booking, $input_amount); + + return $this->resourceResponse(new BookingResource($booking)); + } + +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountWithPOLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountWithPOLogic.php new file mode 100644 index 00000000..5a5c5001 --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountWithPOLogic.php @@ -0,0 +1,103 @@ + 'Update Purchase Order With Booking Amount Update', + 'message' => 'You have successfully updated booking amount' + ]; + } + + /** @var FetchesBooking */ + private $fetchesBooking; + + /** @var UpdatesBookingFixedAmount */ + private $updatesBookingFixedAmount; + + /** @var CalculatesBookingOutstanding */ + private $calculatesBookingOutstanding; + + /** @var CalculatesBookingPayableAmount */ + private $calculatesBookingPayableAmount; + + /** @var CalculatesBookingRefundAmount */ + private $calculatesBookingRefundAmount; + + /** + * UpdateBookingAmountWithPOLogic constructor. + * @param FetchesBooking $fetchesBooking + * @param UpdatesBookingFixedAmount $updatesBookingFixedAmount + * @param CalculatesBookingOutstanding $calculatesBookingOutstanding + * @param CalculatesBookingPayableAmount $calculatesBookingPayableAmount + * @param CalculatesBookingRefundAmount $calculatesBookingRefundAmount + */ + public function __construct(FetchesBooking $fetchesBooking, UpdatesBookingFixedAmount $updatesBookingFixedAmount, CalculatesBookingOutstanding $calculatesBookingOutstanding, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingRefundAmount $calculatesBookingRefundAmount) + { + $this->fetchesBooking = $fetchesBooking; + $this->updatesBookingFixedAmount = $updatesBookingFixedAmount; + $this->calculatesBookingOutstanding = $calculatesBookingOutstanding; + $this->calculatesBookingPayableAmount = $calculatesBookingPayableAmount; + $this->calculatesBookingRefundAmount = $calculatesBookingRefundAmount; + } + + /** + * @param Request $request + * @param string $id + * @return JsonResponse + * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\CriteriaNotFulfilledException + */ + public function logic(Request $request, $id = '') : JsonResponse + { + /** @var Booking $booking */ + $booking = $this->fetchesBooking->execute(['id' => $request->route('id') ?? $id]); + $outstandingAmount = $this->calculatesBookingOutstanding->execute($booking); + $bookingAttribute = $booking->attributesKVP()->where('key', "BOOKING_AMOUNT_UPDATE")->first(); + $paidAmount = floatval($this->calculatesBookingPayableAmount->execute($booking, $booking->fix_currency_id)) - floatval($this->calculatesBookingRefundAmount->execute($booking, $booking->fix_currency_id)); + + if($paidAmount > 0 && $outstandingAmount > 0 && !$bookingAttribute){ + throw new MalformedRequestException('Purchase Order at this point can only be edited after editing the booking amount'); + } + + if($bookingAttribute){ + $total = collect($request->input('products'))->sum(function($product){ + return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price'])); + }); + $bookingAmountUpdate = (float)$bookingAttribute->value; + $isTally = $total === $bookingAmountUpdate ? true : false; + if(!$isTally){ + throw new MalformedRequestException('Purchase Order total not tally with updated booking amount of ' . $bookingAmountUpdate); + } + $booking->transactions()->where('type', TransactionType::PROFORMA)->delete(); + $booking = $this->updatesBookingFixedAmount->execute($booking, $bookingAmountUpdate); + + $request->merge(['is_privilleged_update' => true]); + + $bookingAttribute->delete(); + } + + return $this->resourceResponse(new BookingResource($booking)); + } +} diff --git a/app/Classes/Modules/Bookings/DataTransferObjects/ConfirmBookingDTO.php b/app/Classes/Modules/Bookings/DataTransferObjects/ConfirmBookingDTO.php new file mode 100644 index 00000000..49f7e727 --- /dev/null +++ b/app/Classes/Modules/Bookings/DataTransferObjects/ConfirmBookingDTO.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/Bookings/DataTransferObjects/CreatePaymentVerificationDocumentDTO.php b/app/Classes/Modules/Bookings/DataTransferObjects/CreatePaymentVerificationDocumentDTO.php new file mode 100644 index 00000000..c1140bbe --- /dev/null +++ b/app/Classes/Modules/Bookings/DataTransferObjects/CreatePaymentVerificationDocumentDTO.php @@ -0,0 +1,28 @@ +bookingId = $data['booking_id']; + $this->companyId = $data['company_id']; + $this->paymentId = $data['payment_id']; + } + + public function toArray(): array + { + return [ + 'booking_id' => $this->bookingId, + 'company_id' => $this->companyId, + 'payment_id' => $this->paymentId, + ]; + } +} diff --git a/app/Classes/Modules/Companies/ControllersLogic/FetchCompanyEInvoiceInfoLogic.php b/app/Classes/Modules/Companies/ControllersLogic/FetchCompanyEInvoiceInfoLogic.php new file mode 100644 index 00000000..ebb2d275 --- /dev/null +++ b/app/Classes/Modules/Companies/ControllersLogic/FetchCompanyEInvoiceInfoLogic.php @@ -0,0 +1,68 @@ + 'Retrieved Company E-Invoice Info', + 'message' => 'You have successfully retrieved a Company E-Invoice Info' + ]; + } + + /** @var CanFetchCompany */ + private $canFetchCompany; + + /** @var FetchesCompany */ + private $fetchesCompany; + + /** + * FetchCompanyEInvoiceInfoLogic constructor. + * @param CanFetchCompany $canFetchCompany + * @param FetchesCompany $fetchesCompany + */ + public function __construct(CanFetchCompany $canFetchCompany, FetchesCompany $fetchesCompany) + { + $this->canFetchCompany = $canFetchCompany; + $this->fetchesCompany = $fetchesCompany; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws \App\Classes\Exceptions\AccessForbiddenException + * @throws \App\Classes\Exceptions\RequestValidationException + */ + public function logic(Request $request) : JsonResponse + { + $this->canFetchCompany->passes(); + + $company = $this->fetchesCompany->execute(['id' => $request->route('id')]); + $eInvoiceInfo = $company->addresses()->where('billing', '=', false)->where('e_invoice', '=', true)->latest()->first(); + + if($eInvoiceInfo){ + $eInvoiceInfo->tin = $company->tin; + $eInvoiceInfo->msic_code = $company->msic_code; + $eInvoiceInfo->e_invoice = $company->e_invoice; + } + else{ + return $this->response([]); + } + + return $this->resourceResponse(new EInvoiceInfoResource($eInvoiceInfo)); + } +} diff --git a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyDetailsLogic.php b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyDetailsLogic.php new file mode 100644 index 00000000..6b08aad5 --- /dev/null +++ b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyDetailsLogic.php @@ -0,0 +1,132 @@ + 'Update Company Details', + 'message' => 'You have successfully updated the Company Details' + ]; + } + /** @var CanUpdateCompany */ + private $canUpdateCompany; + + /** @var UpdatesCompany */ + private $updatesCompany; + + /** @var FetchesCompany */ + private $fetchesCompany; + + /** @var UpdatesCompanyDebtor */ + private $updatesCompanyDebtor; + + /** @var CanCreateAddress */ + private $canCreateAddress; + + /** @var FetchesDistrict */ + private $fetchesDistrict; + + /** @var UpsertsAddress */ + private $upsertsAddress; + + /** @var UpdatesCompanyEInvoiceInfo */ + private $updatesCompanyEInvoiceInfo; + + /** @var UpdatesAddressMetadata */ + private $updatesAddressMetadata; + + + /** + * UpdateCompanyDetailsLogic constructor. + * @param CanUpdateCompany $canUpdateCompany + * @param UpdatesCompany $updatesCompany + * @param FetchesCompany $fetchesCompany + * @param UpdatesCompanyDebtor $updatesCompanyDebtor + * @param CanCreateAddress $canCreateAddress + * @param FetchesDistrict $fetchesDistrict + * @param FetchesCompany $fetchesCompany + * @param UpsertsAddress $upsertsAddress + * @param UpdatesCompanyEInvoiceInfo $updatesCompanyEInvoiceInfo; + * @param UpdatesAddressMetadata $updatesAddressMetadata + */ + public function __construct( + CanUpdateCompany $canUpdateCompany, + UpdatesCompany $updatesCompany, + FetchesCompany $fetchesCompany, + UpdatesCompanyDebtor $updatesCompanyDebtor, + CanCreateAddress $canCreateAddress, + FetchesDistrict $fetchesDistrict, + UpsertsAddress $upsertsAddress, + UpdatesCompanyEInvoiceInfo $updatesCompanyEInvoiceInfo, + UpdatesAddressMetadata $updatesAddressMetadata + ) + { + $this->canUpdateCompany = $canUpdateCompany; + $this->updatesCompany = $updatesCompany; + $this->fetchesCompany = $fetchesCompany; + $this->updatesCompanyDebtor = $updatesCompanyDebtor; + $this->canCreateAddress = $canCreateAddress; + $this->fetchesDistrict = $fetchesDistrict; + $this->fetchesCompany = $fetchesCompany; + $this->upsertsAddress = $upsertsAddress; + $this->updatesCompanyEInvoiceInfo = $updatesCompanyEInvoiceInfo; + $this->updatesAddressMetadata = $updatesAddressMetadata; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws ErrorException + */ + public function logic(Request $request) : JsonResponse + { + $dto = new UpdateCompanyDetailsDTO($request->all()); + + $company = $this->fetchesCompany->execute(['id' => $request->route('id')]); + $object = new CompanyObject($dto->name, $dto->reference, $company->business_type, $dto->type); + + $this->canUpdateCompany->passes($object); + + //Update Name and Debtor + $company = $this->updatesCompany->execute($company, $object); + if ($dto->debtor|| $company->first()->debtor !== null) { + $this->updatesCompanyDebtor->execute($company, $dto->debtor); + } + + //Update EInvoice Related Info + if($company->e_invoice){ + $district = $this->fetchesDistrict->execute(['id' => $dto->districtId]); + $addObj = new AddressObject($dto->streetOne, $dto->streetTwo, $district->country_id, $dto->stateId, $district->id, $dto->postCode); + $this->canCreateAddress->passes($addObj); + + $address = $this->upsertsAddress->execute($company, $addObj, $dto->addressId); + $query = $this->updatesAddressMetadata->execute($address, false, true); + $this->updatesCompanyEInvoiceInfo->execute($company, $dto->tin, $dto->msicCode); + } + + return $this->resourceResponse(new CompanyResource($company)); + } +} + diff --git a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceInfoLogic.php b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceInfoLogic.php new file mode 100644 index 00000000..ee91153a --- /dev/null +++ b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceInfoLogic.php @@ -0,0 +1,114 @@ + 'EInvoice Info Update', + 'message' => 'You have successfully updated company E-Invoice information' + ]; + } + + /** @var CanCreateAddress */ + private $canCreateAddress; + + /** @var FetchesDistrict */ + private $fetchesDistrict; + + /** @var FetchesCompany */ + private $fetchesCompany; + + /** @var CreatesAddress */ + private $createsAddress; + + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** @var UpdatesCompanyEInvoiceInfo */ + private $updatesCompanyEInvoiceInfo; + + /** @var UpdatesAddressMetadata */ + private $updatesAddressMetadata; + + /** @var CanPassEInvoicePromptedRule */ + private $canPassEInvoicePromptedRule; + + /** + * UpdateCompanyEInvoiceInfoLogic constructor. + * @param CanCreateAddress $canCreateAddress + * @param FetchesDistrict $fetchesDistrict + * @param FetchesCompany $fetchesCompany + * @param CreatesAddress $createsAddress + * @param RuleEvaluator $ruleEvaluator; + * @param UpdatesCompanyEInvoiceInfo $updatesCompanyEInvoiceInfo; + * @param UpdatesAddressMetadata $updatesAddressMetadata + * @param CanPassEInvoicePromptedRule $canPassEInvoicePromptedRule + */ + public function __construct(CanCreateAddress $canCreateAddress, FetchesDistrict $fetchesDistrict, FetchesCompany $fetchesCompany, CreatesAddress $createsAddress, RuleEvaluator $ruleEvaluator, UpdatesCompanyEInvoiceInfo $updatesCompanyEInvoiceInfo, UpdatesAddressMetadata $updatesAddressMetadata, CanPassEInvoicePromptedRule $canPassEInvoicePromptedRule) + { + $this->canCreateAddress = $canCreateAddress; + $this->fetchesDistrict = $fetchesDistrict; + $this->fetchesCompany = $fetchesCompany; + $this->createsAddress = $createsAddress; + $this->ruleEvaluator = $ruleEvaluator; + $this->updatesCompanyEInvoiceInfo = $updatesCompanyEInvoiceInfo; + $this->updatesAddressMetadata = $updatesAddressMetadata; + $this->canPassEInvoicePromptedRule = $canPassEInvoicePromptedRule; + } + + /** + * @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 EInvoiceInfoDTO($request->all()); + $result = $this->ruleEvaluator->evaluate([ + $this->canPassEInvoicePromptedRule, + ], $dto); + + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } + + $district = $this->fetchesDistrict->execute(['id' => $dto->districtId]); + $object = new AddressObject($dto->streetOne, $dto->streetTwo, $district->country_id, $dto->stateId, $district->id, $dto->postCode); + + //Update Address + $this->canCreateAddress->passes($object); + $company = $this->fetchesCompany->execute(['id' => $dto->companyId]); + $address = $this->createsAddress->execute($company, $object); + $query = $this->updatesAddressMetadata->execute($address, false, true); + + //Update tin, msic code + $this->updatesCompanyEInvoiceInfo->execute($company, $dto->tin, $dto->msicCode); + + return $this->response([]); + } +} diff --git a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceRequestLogic.php b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceRequestLogic.php new file mode 100644 index 00000000..09700f5d --- /dev/null +++ b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyEInvoiceRequestLogic.php @@ -0,0 +1,59 @@ + 'Updated EInvoice Request', + 'message' => 'You have successfully updated company E-Invoice request' + ]; + } + + /** @var FetchesCompany */ + private $fetchesCompany; + + /** @var UpdatesCompanyEInvoiceRequest */ + private $updatesCompanyEInvoiceRequest; + + + /** + * UpdateCompanyEInvoiceRequestLogic constructor. + * @param FetchesCompany $fetchesCompany + * @param UpdatesCompanyEInvoiceRequest $updatesCompanyEInvoiceRequest + */ + public function __construct(FetchesCompany $fetchesCompany, UpdatesCompanyEInvoiceRequest $updatesCompanyEInvoiceRequest) + { + $this->fetchesCompany = $fetchesCompany; + $this->updatesCompanyEInvoiceRequest = $updatesCompanyEInvoiceRequest; + } + + /** + * @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 + { + $dto = new EInvoiceRequestDTO($request->all()); + $company = $this->fetchesCompany->execute(['id' => $dto->companyId]); + $this->updatesCompanyEInvoiceRequest->execute($company, $dto->eInvoiceRequest); + + return $this->response([]); + } +} diff --git a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyNameAndDebtorLogic.php b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyNameAndDebtorLogic.php index 05fb0746..5a110d85 100644 --- a/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyNameAndDebtorLogic.php +++ b/app/Classes/Modules/Companies/ControllersLogic/UpdateCompanyNameAndDebtorLogic.php @@ -20,8 +20,8 @@ class UpdateCompanyNameAndDebtorLogic extends AbstractControllerLogic */ protected function notification():array { return [ - 'title' => 'Update Company Account Status', - 'message' => 'You have successfully updated the Company Account Status' + 'title' => 'Update Company Details', + 'message' => 'You have successfully updated the Company Details' ]; } /** @var CanUpdateCompany */ @@ -77,6 +77,4 @@ class UpdateCompanyNameAndDebtorLogic extends AbstractControllerLogic return $this->resourceResponse(new CompanyResource($query)); } - } - diff --git a/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php new file mode 100644 index 00000000..02779898 --- /dev/null +++ b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceInfoDTO.php @@ -0,0 +1,43 @@ +tin = (string) ($data['tin'] ?? ''); + $this->msicCode = (string) ($data['msic_code'] ?? ''); + $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'] ?? 0); + } + + public function toArray(): array + { + return [ + '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, + 'post_code' => $this->postCode, + ]; + } +} diff --git a/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceRequestDTO.php b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceRequestDTO.php new file mode 100644 index 00000000..4d56cd20 --- /dev/null +++ b/app/Classes/Modules/Companies/DataTransferObjects/EInvoiceRequestDTO.php @@ -0,0 +1,25 @@ +eInvoiceRequest = $data['e_invoice_request']; + $this->companyId = $data['company_id']; + } + + public function toArray(): array + { + return [ + 'e_invoice_request' => $this->eInvoiceRequest, + 'company_id' => $this->companyId, + ]; + } +} diff --git a/app/Classes/Modules/Companies/DataTransferObjects/UpdateCompanyDetailsDTO.php b/app/Classes/Modules/Companies/DataTransferObjects/UpdateCompanyDetailsDTO.php new file mode 100644 index 00000000..0d8ce80e --- /dev/null +++ b/app/Classes/Modules/Companies/DataTransferObjects/UpdateCompanyDetailsDTO.php @@ -0,0 +1,64 @@ +id = (int) ($data['id'] ?? 0); + $this->name = (string) ($data['name'] ?? ''); + $this->debtor = (string) ($data['debtor'] ?? ''); + $this->reference = (string) ($data['reference'] ?? ''); + $this->type = (int) ($data['type'] ?? 0); + + $this->tin = (string) ($data['tin'] ?? 0); + $this->msicCode = (string) ($data['msic_code'] ?? ''); + $this->addressId = (int) ($data['address_id'] ?? 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'] ?? 0); + } + + public function toArray(): array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'debtor' => $this->debtor, + 'reference' => $this->reference, + 'type' => $this->type, + + 'tin' => $this->tin, + 'msic_code' => $this->msicCode, + 'address_id' => $this->addressId, + 'district_id' => $this->districtId, + 'state_id' => $this->stateId, + 'company_id' => $this->companyId, + 'street_one' => $this->streetOne, + 'street_two' => $this->streetTwo, + 'post_code' => $this->postCode, + ]; + } +} diff --git a/app/Classes/Modules/Companies/Services/UpdatesCompanyEInvoiceInfo.php b/app/Classes/Modules/Companies/Services/UpdatesCompanyEInvoiceInfo.php new file mode 100644 index 00000000..0a2c5cc5 --- /dev/null +++ b/app/Classes/Modules/Companies/Services/UpdatesCompanyEInvoiceInfo.php @@ -0,0 +1,25 @@ +tin = $tin; + $model->msic_code = $msicCode; + + return $this->handler($model); + } +} diff --git a/app/Classes/Modules/Companies/Services/UpdatesCompanyEInvoiceRequest.php b/app/Classes/Modules/Companies/Services/UpdatesCompanyEInvoiceRequest.php new file mode 100644 index 00000000..bdef7fc8 --- /dev/null +++ b/app/Classes/Modules/Companies/Services/UpdatesCompanyEInvoiceRequest.php @@ -0,0 +1,29 @@ +e_invoice_requested_at)) { + if($eInvoice){ + $model->e_invoice_requested_at = now(); + } + } + $model->e_invoice = $eInvoice; + + return $this->handler($model); + } +} diff --git a/app/Classes/Modules/Exports/Services/ExportsEInvoiceDebtorSummary.php b/app/Classes/Modules/Exports/Services/ExportsEInvoiceDebtorSummary.php new file mode 100644 index 00000000..7edf2d72 --- /dev/null +++ b/app/Classes/Modules/Exports/Services/ExportsEInvoiceDebtorSummary.php @@ -0,0 +1,106 @@ +whereNull('debtor')->orWhere('debtor', ''); + $query->whereNotNull('e_invoice'); + })->whereNotIn('id', [2207, 2248, 2029])->where('business_type', BusinessType::IMPORTER)->where('status', ApprovalStatus::APPROVED)->where(function($query){ + $query->whereHas('transactions', function($query){ + return $query->whereIn('type', [TransactionType::PAYMENT, TransactionType::TOP_UP])->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED, ApprovalStatus::PENDING_VERIFICATION]); + })->orWhereHas('wallets', function($query){ + return $query->whereHas('transactions'); + }); + }); + } + + /** + * @param Company $company + * + * @return array + */ + public function map($company): array + { + return [ + '<>', // Code + $company->e_invoice, // Need Tax INV? + $company->e_invoice_requested_at, // Request Date + $company->tin, // TIN NO. + '300-0000', // DebtorControlAcc + '300-0000', // ControlAccount + $company->name.' (PURCHASE)', // CompanyName + $company->reference, // Desc2 + '', // DebtorType + 'PIA', // DisplayTerm + 'MYR', // CurrencyCode + '', // RegisterNo + '', // Address1 + '', // Address2 + '', // Address3 + '', // PostCode + '', // DeliverAddr1 + '', // DeliverAddr2 + '', // DeliverAddr3 + '', // DeliverPostCode + '', // EmailAddress + '', // Attention + '', // Phone1 + '', // Phone2 + '', // Fax1 + ]; + } +} diff --git a/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php b/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php index 53a71772..11e48130 100644 --- a/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php +++ b/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php @@ -72,28 +72,28 @@ class ExportsNullDebtors implements FromQuery, WithHeadings, WithHeadingRow, Wit public function map($company): array { return [ - '<>', - '300-0000', - '300-0000', - $company->name.' (PURCHASE)', - $company->reference, - '', - 'PIA', - 'MYR', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '',//EmailAddress - '', - '', - '', - '' + '<>', // Code + '300-0000', // DebtorControlAcc + '300-0000', // ControlAccount + $company->name.' (PURCHASE)', // CompanyName + $company->reference, // Desc2 + '', // DebtorType + 'PIA', // DisplayTerm + 'MYR', // CurrencyCode + '', // RegisterNo + '', // Address1 + '', // Address2 + '', // Address3 + '', // PostCode + '', // DeliverAddr1 + '', // DeliverAddr2 + '', // DeliverAddr3 + '', // DeliverPostCode + '', // EmailAddress + '', // Attention + '', // Phone1 + '', // Phone2 + '', // Fax1 ]; } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Rules/ControllersLogic/CheckEInvoiceRuleLogic.php b/app/Classes/Modules/Rules/ControllersLogic/CheckEInvoiceRuleLogic.php new file mode 100644 index 00000000..9c690686 --- /dev/null +++ b/app/Classes/Modules/Rules/ControllersLogic/CheckEInvoiceRuleLogic.php @@ -0,0 +1,70 @@ + 'Rule Check E-Invoice', + 'message' => 'You have successfully passed all rules evaluated' + ]; + } + + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** @var CanPassEInvoicePromptedRule */ + private $canPassEInvoicePromptedRule; + + /** @var CanPassTINRule */ + private $canPassTINRule; + + /** + * CheckEInvoiceRuleLogic constructor. + */ + public function __construct(RuleEvaluator $ruleEvaluator, CanPassEInvoicePromptedRule $canPassEInvoicePromptedRule, CanPassTINRule $canPassTINRule) + { + $this->ruleEvaluator = $ruleEvaluator; + $this->canPassEInvoicePromptedRule = $canPassEInvoicePromptedRule; + $this->canPassTINRule = $canPassTINRule; + } + + /** + * @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 CheckEInvoiceRuleDTO($request->all()); + + $result = $this->ruleEvaluator->evaluate([ + $this->canPassEInvoicePromptedRule, + $this->canPassTINRule, + ], $dto); + + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } + + return $this->resourceResponse(new RuleResource((object)$result)); + } +} diff --git a/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php b/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php new file mode 100644 index 00000000..b6abaf1a --- /dev/null +++ b/app/Classes/Modules/Rules/ControllersLogic/CheckPurchaseOrderRuleLogic.php @@ -0,0 +1,82 @@ + 'Rule Check Purchase Order', + 'message' => 'You have successfully passed all rules evaluated' + ]; + } + + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** @var CanPassOrderDurationLimitRule */ + private $canPassOrderDurationLimitRule; + + /** @var CanPassEInvoicePromptedRule */ + private $canPassEInvoicePromptedRule; + + /** @var CanPassPurchaseOrderRule */ + private $canPassPurchaseOrderRule; + + + /** + * CheckPurchaseOrderRuleLogic constructor. + * @param RuleEvaluator $ruleEvaluator + * @param CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule + * @param CanPassEInvoicePromptedRule $canPassEInvoicePromptedRule + * @param CanPassPurchaseOrderRule $canPassPurchaseOrderRule + + */ + public function __construct(RuleEvaluator $ruleEvaluator, CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule, CanPassEInvoicePromptedRule $canPassEInvoicePromptedRule, CanPassPurchaseOrderRule $canPassPurchaseOrderRule) + { + $this->ruleEvaluator = $ruleEvaluator; + $this->canPassOrderDurationLimitRule = $canPassOrderDurationLimitRule; + $this->canPassEInvoicePromptedRule = $canPassEInvoicePromptedRule; + $this->canPassPurchaseOrderRule = $canPassPurchaseOrderRule; + } + + /** + * @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 CheckPurchaseOrderRuleDTO($request->all()); + + $result = $this->ruleEvaluator->evaluate([ + $this->canPassOrderDurationLimitRule, + $this->canPassEInvoicePromptedRule, + $this->canPassPurchaseOrderRule, + ], $dto); + + if ($result->failed()) { + throw new CriteriaNotFulfilledException("- " . implode("
- ", $result->messages())); + } + + return $this->resourceResponse(new RuleResource((object)$result)); + } +} diff --git a/app/Classes/Modules/Rules/ControllersLogic/CheckTransferRuleLogic.php b/app/Classes/Modules/Rules/ControllersLogic/CheckTransferRuleLogic.php new file mode 100644 index 00000000..a87a00d5 --- /dev/null +++ b/app/Classes/Modules/Rules/ControllersLogic/CheckTransferRuleLogic.php @@ -0,0 +1,66 @@ + 'Rule Check Transfer', + 'message' => 'You have successfully passed all rules evaluated' + ]; + } + + /** @var RuleEvaluator */ + private $ruleEvaluator; + + /** @var CanPassOrderDurationLimitRule */ + private $canPassOrderDurationLimitRule; + + /** + * CheckTransferRuleLogic constructor. + * @param RuleEvaluator $ruleEvaluator + * @param CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule + */ + public function __construct(RuleEvaluator $ruleEvaluator, CanPassOrderDurationLimitRule $canPassOrderDurationLimitRule) + { + $this->ruleEvaluator = $ruleEvaluator; + $this->canPassOrderDurationLimitRule = $canPassOrderDurationLimitRule; + } + + /** + * @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([ + $this->canPassOrderDurationLimitRule, + ], $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/CheckEInvoiceRuleDTO.php b/app/Classes/Modules/Rules/DataTransferObjects/CheckEInvoiceRuleDTO.php new file mode 100644 index 00000000..75726e47 --- /dev/null +++ b/app/Classes/Modules/Rules/DataTransferObjects/CheckEInvoiceRuleDTO.php @@ -0,0 +1,22 @@ +companyId = $data['company_id']; + } + + public function toArray(): array + { + return [ + 'company_id' => $this->companyId, + ]; + } +} diff --git a/app/Classes/Modules/Rules/DataTransferObjects/CheckPurchaseOrderRuleDTO.php b/app/Classes/Modules/Rules/DataTransferObjects/CheckPurchaseOrderRuleDTO.php new file mode 100644 index 00000000..2c5c2ab5 --- /dev/null +++ b/app/Classes/Modules/Rules/DataTransferObjects/CheckPurchaseOrderRuleDTO.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/DataTransferObjects/CheckTransferRuleDTO.php b/app/Classes/Modules/Rules/DataTransferObjects/CheckTransferRuleDTO.php new file mode 100644 index 00000000..a89ffe0e --- /dev/null +++ b/app/Classes/Modules/Rules/DataTransferObjects/CheckTransferRuleDTO.php @@ -0,0 +1,28 @@ +bookingId = $data['booking_id']; + $this->companyId = $data['company_id']; + $this->paymentReference = $data['payment_reference'] ?? ''; + } + + public function toArray(): array + { + return [ + 'booking_id' => $this->bookingId, + 'company_id' => $this->companyId, + 'payment_reference' => $this->paymentReference, + ]; + } +} diff --git a/app/Classes/Modules/Rules/Services/RuleEvaluator.php b/app/Classes/Modules/Rules/Services/RuleEvaluator.php new file mode 100644 index 00000000..ac248a25 --- /dev/null +++ b/app/Classes/Modules/Rules/Services/RuleEvaluator.php @@ -0,0 +1,44 @@ +passes($object)) { + $success = false; + $messages[] = get_class($rule) . ' failed without exception'; + } + } catch (AccessForbiddenException | RequestValidationException | CriteriaNotFulfilledException $e) { + $success = false; + $messages[] = $e->getMessage(); + } catch (\Exception $e) { + $success = false; + $messages[] = 'Unexpected error in ' . get_class($rule) . ': ' . $e->getMessage(); + } + } + + return new RuleEvaluationResult($success, $messages); + } + +} diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanPassEInvoicePromptedRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassEInvoicePromptedRule.php new file mode 100644 index 00000000..5ab57fdf --- /dev/null +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassEInvoicePromptedRule.php @@ -0,0 +1,56 @@ +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 account requires E-Invoice + $company = $this->fetchesCompany->execute(['id' => $object->companyId]); + if($company->e_invoice === null){ + 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/CanPassOrderDurationLimitRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassOrderDurationLimitRule.php new file mode 100644 index 00000000..9e07bc23 --- /dev/null +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassOrderDurationLimitRule.php @@ -0,0 +1,92 @@ +fetchesBooking = $fetchesBooking; + $this->fetchesCompanyPaymentAttemptLimit = $fetchesCompanyPaymentAttemptLimit; + $this->calculatesTransactionExpiryDateTime = $calculatesTransactionExpiryDateTime; + $this->deletesBillplzBill = $deletesBillplzBill; + $this->fetchesTransaction = $fetchesTransaction; + } + + /** + * @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) + $expiresOn = $this->calculatesTransactionExpiryDateTime->execute($object->bookingId); + $isExpired = Carbon::now()->greaterThan($expiresOn); + if($isExpired){ + + if($object->paymentReference){ + $transaction = $this->fetchesTransaction->execute(['payment_reference' => $object->paymentReference]); + if($transaction->status === ApprovalStatus::PENDING_SUBMISSION && $transaction->payment_method == PaymentMethodType::PAYMENT_GATEWAY){ + $this->deletesBillplzBill->execute($object->paymentReference); + } + } + + throw new CriteriaNotFulfilledException("Transfer has already expired."); + } + return true; + } +} diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanPassPurchaseOrderRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassPurchaseOrderRule.php new file mode 100644 index 00000000..db17173f --- /dev/null +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassPurchaseOrderRule.php @@ -0,0 +1,96 @@ +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 the transfer/booking already has purchase order filled + $booking = $this->fetchesBooking->execute(['id' => $object->bookingId]); + $purchaseOrder = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first(); + + $isPOEmptyException = false; + if (!$purchaseOrder || $purchaseOrder->status === ApprovalStatus::PENDING_SUBMISSION) { + $isPOEmptyException = true; + } + + if($isPOEmptyException){ //If PO is indeed empty, there are some scenarios where PO actually can be left empty + $ids = ServiceType::whereIn('name', [ + ServiceTypeNameConstants::PAYMENT_1688, + ServiceTypeNameConstants::VIP_1688, + ])->get()->pluck('id'); + + //It can be left empty when booking is of type 1688: specifcally: 1688 Payment and 1688 VIP + if (in_array($booking->service_id, $ids->all())) { + $isPOEmptyException = false; + } + + //However when the customer falls under the segment '1688 Manual PO Periodic', even if their booking is of type 1688 (1688 Payment and 1688 VIP), + //they still must fill up the PO. Confusing?? IKR + $company = $this->fetchesCompany->execute(['id' => $object->companyId]); + $filteredSegments = $company->segments()->whereIn('name', [SegmentNameConstants::MANUAL_PO_PERIODIC_1688, SegmentNameConstants::MANUAL_PO_1688])->get(); + if(!$isPOEmptyException){ + if (!$filteredSegments->isEmpty()) { + $isPOEmptyException = true; + } + } + } + + if($isPOEmptyException){ + throw new CriteriaNotFulfilledException("Please complete the purchase order form."); + } + return true; + } +} diff --git a/app/Classes/Modules/Rules/Standards/Rules/CanPassTINRule.php b/app/Classes/Modules/Rules/Standards/Rules/CanPassTINRule.php new file mode 100644 index 00000000..3f767912 --- /dev/null +++ b/app/Classes/Modules/Rules/Standards/Rules/CanPassTINRule.php @@ -0,0 +1,55 @@ +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 TIN already provided if account requires E-Invoice + $company = $this->fetchesCompany->execute(['id' => $object->companyId]); + if($company->e_invoice === 1 && !$company->tin){ + throw new CriteriaNotFulfilledException("Please provide all requested E-Invoice Info."); + } + return true; + } + +} diff --git a/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/CreatePurchaseOrderTransactionLogic.php index 9bab107f..d5d97536 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,14 +17,18 @@ 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\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 { @@ -33,7 +39,7 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic protected function notification():array { return [ 'title' => 'Update Purchase Order', - 'message' => 'You have successfully updated you booking\'s purchase order' + 'message' => 'You have successfully updated your booking\'s purchase order' ]; } @@ -46,17 +52,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 +80,22 @@ 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 { + //This checking is excluded for (1) Admin, (2) Update of booking amount post payment as user + if(!in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES) && !$request->has('is_privilleged_update')){ + $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 +116,4 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic return $this->resourceResponse(new TransactionResource($transaction)); } - - - } diff --git a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfLogic.php index 4a0af327..4241ac91 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfLogic.php @@ -13,6 +13,10 @@ use App\Classes\ValueObjects\Constants\TransactionType; use App\Classes\General\AWSS3Helper; use Illuminate\Support\Facades\Storage; +/** + * @deprecated This class is deprecated and should not be used. + * Use `GenerateCreditNotePdfV2Logic` instead + */ class GenerateCreditNotePdfLogic { diff --git a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php new file mode 100644 index 00000000..824d5528 --- /dev/null +++ b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php @@ -0,0 +1,115 @@ +fetchesTransaction = $fetchesTransaction; + $this->fetchesCompany = $fetchesCompany; + } + + /** + * @param Request $request + * @return string|\Symfony\Component\HttpFoundation\Response + * @throws \App\Classes\Exceptions\MalformedRequestException + */ + public function execute(Request $request) + { + $pdfTemplateName = 'pages.pdfs.credit_note_v2'; //default since e-invoice implementation + $transaction = $this->fetchesTransaction->execute(['id' => $request->route('id')]); + + if($transaction->type === TransactionType::REFUND){ + //Retrieve TransactionType::CREDIT_NOTE + $booking = $transaction->owner->booking; + $kvp = $transaction->attributesKVP()->latest()->first(); + if($kvp){ + if($kvp->key === 'App\Models\Transaction'){ + $transaction = $this->fetchesTransaction->execute(['id' => $kvp->value ]); + } + } + } + else{ + //For Old Cases + $booking = $transaction->booking; + $pdfTemplateName = 'pages.pdfs.credit_note'; //default + + //For New Cases with e-invoice: Retrieve the refund transaction for this credit note + $kvp = KeyValuePair::where('key', 'App\Models\Transaction')->where('value', $transaction->id)->first(); + if($kvp){ + $kvpOwner = $kvp->owner; + if($kvpOwner && $kvpOwner instanceof Transaction && $kvpOwner->type === TransactionType::REFUND){ + $booking = $kvpOwner->owner->booking; + } + } + } + + $date = $transaction->created_at; + $supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]); + $brn = $supplier->documents->where('document_type', DocumentType::SSM_REGISTRATION)->first(); + + $eInvoiceStarted = false; + $eInvoiceStartDate = Carbon::parse(env('E_INVOICE_START_DATE', '2025-07-01 00:00:00')); + $bookingCreatedDate = Carbon::parse($booking->created_at); + + if ($bookingCreatedDate->isAfter($eInvoiceStartDate)) { + $eInvoiceStarted = true; + } + // $eInvoiceStarted = false; //cief todo: 90 - for testing + + if($eInvoiceStarted) + { + if($supplier->e_invoice === 1){ + Log::info('Based on booking created date, E-Credit Note started and company wants e-invoice ' . json_encode($booking)); + $date = $booking->updated_at->copy()->endOfMonth(); + $pdfTemplateName = 'pages.pdfs.e_credit_note'; + } + else{ + Log::info('Based on booking created date, E-Credit Note started and company do not wants e-invoice'); + $pdfTemplateName = 'pages.pdfs.credit_note_v2'; + } + } + else{ + Log::info('Based on booking created date, E-Credit Note not yet started'); + } + + $pdf = LaravelMpdf::loadView($pdfTemplateName, ['transaction' => $transaction, 'booking' => $booking, 'supplier' => $supplier, 'date' => $date, 'brn' => $brn,]); + + $exportFileName = 'CreditNote.pdf'; + $filesystemDriver = Storage::getDefaultDriver(); + if($filesystemDriver === 's3'){ + $pdfContent = $pdf->output(); + return response([ 'src' => AWSS3Helper::S3PDF($exportFileName, $pdfContent) ]); + } + else{ + return $pdf->stream($exportFileName); + } + } +} diff --git a/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php index d2b921d1..a8e6ca69 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/UpdateRefundTransactionStatusLogic.php @@ -2,7 +2,6 @@ namespace App\Classes\Modules\Transactions\ControllersLogic; -use App\Classes\Exceptions\MalformedRequestException; use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Bookings\ControllersLogic\UpdateBookingAmountLogic; use App\Classes\Modules\Companies\Services\FetchesCompany; @@ -12,12 +11,11 @@ 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\ValueObjects\Constants\RemarkRefundReason; use App\Classes\ValueObjects\Constants\TransactionType; -use Illuminate\Support\Facades\Auth; class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic { @@ -86,9 +84,10 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic */ public function logic(Request $request) : JsonResponse { - if(auth()->user()->type === 3) { - throw new MalformedRequestException('You do not have the permission to refund the order.'); - } + //cief todo: 90 - move this into rules + // if(auth()->user()->type === 3) { + // throw new MalformedRequestException('You do not have the permission to refund the order.'); + // } $refundTransaction = $this->fetchesTransaction->execute(['id' => $request->route('id')]); @@ -100,14 +99,22 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic $booking = $paymentTransaction->owner; - $reference = $paymentTransaction->amount - $refundTransaction->amount < 0.01 ? 'Fully Refund for Ref. ' . $booking->marking : 'Partially Refund for Ref. ' . $booking->marking; + // $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); + $this->creditWalletProcessor->execute($booking->company, $refundTransaction->type, $refundTransaction->amount, $reference, $refundTransaction); + $po_transaction = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first(); if ($po_transaction) { @@ -129,4 +136,4 @@ class UpdateRefundTransactionStatusLogic extends AbstractControllerLogic return $this->response([]); } -} \ No newline at end of file +} 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/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php index 1db15909..8bb2c797 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php @@ -10,6 +10,7 @@ use App\Classes\ValueObjects\Constants\DocumentType; use App\Classes\ValueObjects\Constants\TransactionType; use App\Models\Booking; use App\Models\Document; +use Carbon\Carbon; use Illuminate\Support\Facades\Log; use Mccarlosen\LaravelMpdf\Facades\LaravelMpdf; use Webklex\PDFMerger\Facades\PDFMergerFacade as PDFMerger; @@ -45,9 +46,26 @@ class CreateInvoiceDocumentProcessor public function execute($transaction, $purchaseOrder, $supplier, $document_type, $voucherRedemption = null) { // calculate current Paid Amount - $booking = $transaction->owner_type == Booking::class ? $transaction->owner : null; + $booking = $transaction->owner_type == Booking::class ? $transaction->owner : null; $currentPaidAmount = null; + $brn = $supplier->documents->where('document_type', DocumentType::SSM_REGISTRATION)->first(); + $documentDate = $supplier->segments->whereIn('id', [23])->first() ? \Carbon\Carbon::now() : $purchaseOrder->booking->created_at; + $eInvoiceStartDate = Carbon::parse(env('E_INVOICE_START_DATE', '2025-07-01 00:00:00')); + if ($booking) { + $bookingCreatedDate = Carbon::parse($booking->created_at); + if ($bookingCreatedDate->isAfter($eInvoiceStartDate)) { + $lastPaymentTransaction = $booking->transactions()->where('type', TransactionType::PAYMENT)->whereIn('status', [ApprovalStatus::COMPLETED, ApprovalStatus::APPROVED])->latest()->first(); + $documentDate = $lastPaymentTransaction->created_at; + if(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){ + $documentDate = $booking->updated_at; + } + } + + if($document_type === DocumentType::EINVOICE){ + $lastDayOfMonth = $documentDate->copy()->endOfMonth(); + $documentDate = $lastDayOfMonth; + } $payment = $booking->transactions()->where('type', TransactionType::PAYMENT)->whereIn('status', [ApprovalStatus::COMPLETED, ApprovalStatus::APPROVED])->first(); $refundAmount = $payment->transactions()->refunds()->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED])->sum('amount'); $paymentAmount = $payment->amount; @@ -56,9 +74,9 @@ class CreateInvoiceDocumentProcessor $lowercaseDocumentType = strtolower($document_type); - $order_pdf = LaravelMpdf::loadView('pages.pdfs.' . $lowercaseDocumentType, ['transaction' => $transaction, 'po_order_transaction' => $purchaseOrder, 'supplier' => $supplier, 'voucher_redemption' => $voucherRedemption, 'current_paid_amount' => $currentPaidAmount]); + $order_pdf = LaravelMpdf::loadView('pages.pdfs.' . $lowercaseDocumentType, ['transaction' => $transaction, 'po_order_transaction' => $purchaseOrder, 'supplier' => $supplier, 'voucher_redemption' => $voucherRedemption, 'current_paid_amount' => $currentPaidAmount, 'document_date' => $documentDate, 'brn' => $brn, 'autocountId' => null]); //cief todo: 90 - autocount id to be updated - if($purchaseOrder->booking->service_id === 4) { + if($purchaseOrder && $purchaseOrder->booking->service_id === 4) { $purchaseOrderDocuments = $purchaseOrder->booking->documents()->where('document_type', DocumentType::ECOMMERCE_PURCHASE_ORDER)->get(); @@ -112,8 +130,12 @@ class CreateInvoiceDocumentProcessor ); /** @var Document $document */ - $document = $this->createsDocument->execute($purchaseOrder->booking, $document_object); + if($document_type === DocumentType::RECEIPT_VOUCHER){ + $document = $this->createsDocument->execute($transaction, $document_object); + } + else{ + $document = $this->createsDocument->execute($purchaseOrder->booking, $document_object); + } $this->createsFile->execute($document, $document_object); - } } diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php index 7f105dc8..e8c7ff21 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php @@ -20,6 +20,7 @@ use App\Classes\ValueObjects\Constants\TransactionType; use App\Classes\ValueObjects\Constants\DocumentType; use App\Models\Booking; use App\Models\SegmentConstant; +use Carbon\Carbon; class CreateInvoiceTransactionProcessor { @@ -83,10 +84,12 @@ class CreateInvoiceTransactionProcessor /** * @param Booking $booking + * @param String $invoiceNo + * @param bool $isAllowEInvoice * @return void * @throws MalformedRequestException */ - public function execute(Booking $booking) + public function execute(Booking $booking, String $invoiceNo= "", bool $isAllowEInvoice = false) { if ($booking->status === ApprovalStatus::COMPLETED) { @@ -121,10 +124,26 @@ class CreateInvoiceTransactionProcessor // ->first(); $transaction = $booking->transactions() - ->where('type', TransactionType::PAYMENT) - ->latest()->get()[0]; + ->where('type', TransactionType::PAYMENT) + ->latest()->get()[0]; + $supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]); - $billNumber = $this->generatesTransactionBillNumber->execute('INV-'); + // Check if eInvoice implementation has started and company opted in for eInvoice + $eInvoice = false; + $eInvoiceStartDate = Carbon::parse(env('E_INVOICE_START_DATE', '2025-07-01 00:00:00')); + $bookingCreatedDate = Carbon::parse($booking->created_at); + if ($bookingCreatedDate->isAfter($eInvoiceStartDate) && $supplier->e_invoice === 1) { + $eInvoice = true; + } + // $eInvoice = true; //cief todo: 90 - for testing + + if($invoiceNo){ + $billNumber = $invoiceNo; + } + else{ + $billNUmberPrefix = $eInvoice ? 'EINV-' : 'INV-'; + $billNumber = $this->generatesTransactionBillNumber->execute($billNUmberPrefix); + } $booking_currency_average_rate = $this->calculatesBookingCurrencyAverageRate->execute($booking, TransactionType::PAYMENT); @@ -138,6 +157,7 @@ class CreateInvoiceTransactionProcessor ->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]) ->sum('tax'); + $transaction_object = new TransactionObject( $billNumber, TransactionType::INVOICE, @@ -159,16 +179,24 @@ class CreateInvoiceTransactionProcessor $voucherRedemption = $transaction->voucherRedemption; - $supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]); - // purchase order $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::PURCHASE_ORDER, $voucherRedemption); // deliver order $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::DELIVER_ORDER, $voucherRedemption); + // e-invoice + if ($eInvoice) + { + if($isAllowEInvoice){ + $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::EINVOICE, $voucherRedemption); + } + } // invoice - $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::INVOICE, $voucherRedemption); + else + { + $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::INVOICE, $voucherRedemption); + } $billNumber = $this->generatesTransactionBillNumber->execute('SPDO-'); diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionWithInvoiceNoProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionWithInvoiceNoProcessor.php index 612033f8..3bb33f28 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionWithInvoiceNoProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionWithInvoiceNoProcessor.php @@ -21,6 +21,10 @@ use App\Classes\ValueObjects\Constants\DocumentType; use App\Models\Booking; use App\Models\SegmentConstant; +/** + * @deprecated This class is deprecated and should not be used. + * Use `CreateInvoiceTransactionProcessor` instead or write a new one based on CreateInvoiceTransactionProcessor + */ class CreateInvoiceTransactionWithInvoiceNoProcessor { @@ -53,7 +57,7 @@ class CreateInvoiceTransactionWithInvoiceNoProcessor /** - * CreateInvoiceTransactionProcessor constructor. + * CreateInvoiceTransactionWithInvoiceNoProcessor constructor. * @param ListsTransactions $listsTransactions * @param CreatesTransaction $createsTransaction * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber diff --git a/app/Classes/Modules/Transactions/Processors/CreateReceiptVoucherTransactionProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateReceiptVoucherTransactionProcessor.php new file mode 100644 index 00000000..16b37380 --- /dev/null +++ b/app/Classes/Modules/Transactions/Processors/CreateReceiptVoucherTransactionProcessor.php @@ -0,0 +1,113 @@ +createsTransaction = $createsTransaction; + $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; + $this->calculatesBookingCurrencyAverageRate = $calculatesBookingCurrencyAverageRate; + $this->fetchesCompany = $fetchesCompany; + $this->invoiceDocumentProcessor = $invoiceDocumentProcessor; + } + + /** + * @param Booking $booking + * @param Transaction $transaction + * @param bool $isRegenerate + * @return void + * @throws MalformedRequestException + */ + public function execute(Booking $booking, Transaction $transaction, bool $isRegenerate = false) + { + $purchaseOrder = $booking->transactions() + ->where('type', TransactionType::PURCHASE_ORDER) + // ->complete() + ->first(); + + if(!$transaction->type === TransactionType::PAYMENT){ + return; + } + + if(!($transaction->status === ApprovalStatus::APPROVED || $transaction->status === ApprovalStatus::COMPLETED)){ + return; + } + Log::info('CreateReceiptVoucherTransactionProcessor booking id: '. $booking->id); + Log::info('CreateReceiptVoucherTransactionProcessor transaction: '. json_encode($transaction)); + + //If payment receipt voucher already exists, return (unless you want to regenerate) + if($transaction->transactions()->where('type', TransactionType::RECEIPT_VOUCHER)->exists() && !$isRegenerate){ + return; + } + + $billNumber = $this->generatesTransactionBillNumber->execute('RV-'); + + $booking_currency_average_rate = $this->calculatesBookingCurrencyAverageRate->execute($booking, TransactionType::PAYMENT); + + $transaction_object = new TransactionObject( + $billNumber, + TransactionType::RECEIPT_VOUCHER, + $transaction->issuer, + $transaction->receiver, + $transaction->recipient_bank_account_id, + $transaction->payment_method, + $transaction->amount, + $transaction->original_amount, + $transaction->currency_id, + $transaction->original_currency_id, + $booking_currency_average_rate, + 0, + 0, + null, + ApprovalStatus::APPROVED + ); + $invoice_transaction = $this->createsTransaction->execute($transaction, $transaction_object); + + $voucherRedemption = $transaction->voucherRedemption; + + $supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]); + + // receipt voucher - Receipt is mandatory for customer who wants e-invoice and those who does not + $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::RECEIPT_VOUCHER, $voucherRedemption); + } +} diff --git a/app/Classes/Modules/Transactions/Services/CalculatesTransactionExpiryDateTime.php b/app/Classes/Modules/Transactions/Services/CalculatesTransactionExpiryDateTime.php new file mode 100644 index 00000000..bf4e42d7 --- /dev/null +++ b/app/Classes/Modules/Transactions/Services/CalculatesTransactionExpiryDateTime.php @@ -0,0 +1,91 @@ +fetchesBooking = $fetchesBooking; + $this->fetchesCompanyPaymentAttemptLimit = $fetchesCompanyPaymentAttemptLimit; + } + + + /** + * @param int $bookingId + * @return Carbon|null $returnDateTime + */ + public function execute(int $bookingId) + { + $isExpired = false; + $booking = $this->fetchesBooking->execute(['id' => $bookingId]); + $paymentAttemptLimit = $this->fetchesCompanyPaymentAttemptLimit->execute($booking->company); + + $createdAt = Carbon::parse($booking->created_at); + Log::info("1. Booking created at {$createdAt}."); + $bookingExpiresAt = $createdAt->addMinutes($paymentAttemptLimit); + Log::info("1. Booking expires at {$bookingExpiresAt}. (original)"); + $newBookingExpiresAt = null; + $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(); + Log::info("2. Booking earliest payment : {$earliestPayment->id}, {$earliestPayment->created_at}"); + $newBookingExpiresAt = Carbon::parse($earliestPayment->created_at)->addMinutes($paymentAttemptLimit); + Log::info("2. Booking expires at : {$newBookingExpiresAt}. (new)"); + $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("2. Booking initially expired, but found valid pending payment(s).", $logDetails); + + $isExpired = Carbon::now()->greaterThan($newBookingExpiresAt); + Log::info("2. Booking expired: {$isExpired}"); + } else { + Log::info("Booking expired and no valid pending payments for booking ID: {$booking->id}"); + } + // } + $returnDateTime = $newBookingExpiresAt ? $newBookingExpiresAt : $bookingExpiresAt; + return $returnDateTime; + } +} diff --git a/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php b/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php index 65f3b9b6..b0fd0e1c 100644 --- a/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php +++ b/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php @@ -2,6 +2,7 @@ namespace App\Classes\Modules\Wallets\Processors; +use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject; use App\Models\Wallet; use App\Models\Company; use App\Classes\ValueObjects\Constants\ApprovalStatus; @@ -14,6 +15,8 @@ use App\Classes\Modules\Transactions\Services\CreatesTransaction; use App\Classes\Modules\Wallets\DataTransferObjects\WalletObject; use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject; use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber; +use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair; +use App\Models\Transaction; class CreditWalletProcessor { @@ -32,6 +35,9 @@ class CreditWalletProcessor /** @var UpdatesWallet */ private $updatesWallet; + /** @var CreatesKeyValuePair */ + private $createsKeyValuePair; + /** * CreateWalletLogic constructor. * @param GeneratesWalletCode $generatesWalletCode @@ -39,13 +45,15 @@ class CreditWalletProcessor * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber * @param CreatesTransaction $createsTransaction * @param UpdatesWallet $updatesWallet + * @param CreatesKeyValuePair $createsKeyValuePair */ public function __construct( GeneratesWalletCode $generatesWalletCode, CreatesWallet $createsWallet, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, - UpdatesWallet $updatesWallet + UpdatesWallet $updatesWallet, + CreatesKeyValuePair $createsKeyValuePair ) { $this->generatesWalletCode = $generatesWalletCode; @@ -53,6 +61,7 @@ class CreditWalletProcessor $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; $this->createsTransaction = $createsTransaction; $this->updatesWallet = $updatesWallet; + $this->createsKeyValuePair = $createsKeyValuePair; } @@ -61,10 +70,11 @@ class CreditWalletProcessor * @param int $transactionType * @param float $amount * @param string $reference + * @param $relatedTransaction * @return \Illuminate\Database\Eloquent\Model * @throws \App\Classes\Exceptions\MalformedRequestException */ - public function execute(Company $company, int $transactionType, float $amount, string $reference) + public function execute(Company $company, int $transactionType, float $amount, string $reference, $relatedTransaction = null) { if (!$company->wallets()->first()) { $object = new WalletObject($company->id, 1, $this->generatesWalletCode->execute()); @@ -75,16 +85,26 @@ class CreditWalletProcessor $wallet = $company->wallets()->first(); $billNumber = $this->generatesTransactionBillNumber->execute($transactionType === 2 ? 'DEBIT-NOTE-' : 'CREDIT-NOTE-'); - + $transaction_object = new TransactionObject($billNumber, $transactionType === 2 ? TransactionType::DEBIT_NOTE : TransactionType::CREDIT_NOTE, 1, $wallet->owner->id, 1, PaymentMethodType::CASH, $amount, $amount, 1, 1, 1, 0, 0, null, ApprovalStatus::APPROVED, [], $reference); $transaction = $this->createsTransaction->execute($wallet, $transaction_object); - + $updateWalletAmount = $transactionType === 2 ? ($wallet->amount - $transaction->amount) : ($wallet->amount + $transaction->amount); $walletObject = new WalletObject($wallet->owner->id, $wallet->currency_id, $wallet->code, $updateWalletAmount); $wallet = $this->updatesWallet->execute($wallet, $walletObject); + if($relatedTransaction && $relatedTransaction instanceof Transaction){ + $kvp = $relatedTransaction->attributesKVP()->where('key', 'App\Models\Transaction')->where('value', $transaction->id)->latest()->first(); + if(!$kvp){ + $keyValuePairObject = new KeyValuePairObject( + "App\Models\Transaction", + $transaction->id + ); + $this->createsKeyValuePair->execute($relatedTransaction, $keyValuePairObject); + } + } return $wallet; } } diff --git a/app/Classes/ValueObjects/Constants/DocumentType.php b/app/Classes/ValueObjects/Constants/DocumentType.php index 42f8222b..b11b6874 100644 --- a/app/Classes/ValueObjects/Constants/DocumentType.php +++ b/app/Classes/ValueObjects/Constants/DocumentType.php @@ -27,4 +27,7 @@ final class DocumentType { public const BULK_PURCHASE_ORDER = 'BULK_PURCHASE_ORDER'; public const BILL_GROUP_PAYMENT_PROOF = 'BILL_GROUP_PAYMENT_PROOF'; + + public const RECEIPT_VOUCHER = 'RECEIPT_VOUCHER'; + public const EINVOICE = 'E_INVOICE'; //cief todo: 90 - why is there no E-CREDITNOTE } diff --git a/app/Classes/ValueObjects/Constants/RemarkRefundReason.php b/app/Classes/ValueObjects/Constants/RemarkRefundReason.php new file mode 100644 index 00000000..d8143b21 --- /dev/null +++ b/app/Classes/ValueObjects/Constants/RemarkRefundReason.php @@ -0,0 +1,16 @@ + 'Return Inward', + 'Goods Damage/ Loss Compensation' => 'Return Inward', + 'Cancel Partial Order' => 'Return Inward', + 'Overpaid due to Supplier amend price' => 'Discount Allowed', + 'Defective Item' => 'Discount Allowed', + 'Cancel Full Order' => 'Return Inward', + 'Others' => '', + ]; +} diff --git a/app/Classes/ValueObjects/Constants/SegmentNameConstants.php b/app/Classes/ValueObjects/Constants/SegmentNameConstants.php new file mode 100644 index 00000000..5b29b3a6 --- /dev/null +++ b/app/Classes/ValueObjects/Constants/SegmentNameConstants.php @@ -0,0 +1,8 @@ + "PAYMENT_ATTEMPT", self::PAYMENT => "PAYMENT", @@ -56,5 +58,5 @@ final class TransactionType { self::SUPPLIER_PAYMENT => "SUPPLIER_PAYMENT", self::SUPPLIER_REFUND => "SUPPLIER_REFUND", ]; - + } diff --git a/app/Classes/ValueObjects/Response/RuleEvaluationResult.php b/app/Classes/ValueObjects/Response/RuleEvaluationResult.php new file mode 100644 index 00000000..264eaf26 --- /dev/null +++ b/app/Classes/ValueObjects/Response/RuleEvaluationResult.php @@ -0,0 +1,30 @@ +success = $success; + $this->messages = $messages; + } + + public function failed(): bool + { + return ! $this->success; + } + + public function passed(): bool + { + return $this->success; + } + + public function messages(): array + { + return $this->messages; + } +} 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/Controllers/Bookings/RegenerateBookingEInvoiceController.php b/app/Http/Controllers/Bookings/RegenerateBookingEInvoiceController.php new file mode 100644 index 00000000..0b07d938 --- /dev/null +++ b/app/Http/Controllers/Bookings/RegenerateBookingEInvoiceController.php @@ -0,0 +1,20 @@ +execute($request); + } +} diff --git a/app/Http/Controllers/Bookings/RegenerateBookingPaymentRVController.php b/app/Http/Controllers/Bookings/RegenerateBookingPaymentRVController.php new file mode 100644 index 00000000..29a75c70 --- /dev/null +++ b/app/Http/Controllers/Bookings/RegenerateBookingPaymentRVController.php @@ -0,0 +1,20 @@ +execute($request); + } +} diff --git a/app/Http/Controllers/Bookings/UpdateBookingAmountController.php b/app/Http/Controllers/Bookings/UpdateBookingAmountController.php index d0f6dd6c..394d4039 100644 --- a/app/Http/Controllers/Bookings/UpdateBookingAmountController.php +++ b/app/Http/Controllers/Bookings/UpdateBookingAmountController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Bookings; use App\Classes\Modules\Bookings\ControllersLogic\UpdateBookingAmountLogic; +use App\Classes\Modules\Bookings\ControllersLogic\UpdateBookingAmountOnHoldLogic; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -17,4 +18,12 @@ class UpdateBookingAmountController return $logic->execute($request); } -} \ No newline at end of file + /** + * @param Request $request + * @param UpdateBookingAmountOnHoldLogic $logic + * @return JsonResponse + */ + public function updateOnHold(Request $request, UpdateBookingAmountOnHoldLogic $logic): JsonResponse { + return $logic->execute($request); + } +} diff --git a/app/Http/Controllers/Companies/FetchCompanyEInvoiceInfoController.php b/app/Http/Controllers/Companies/FetchCompanyEInvoiceInfoController.php new file mode 100644 index 00000000..d506e24e --- /dev/null +++ b/app/Http/Controllers/Companies/FetchCompanyEInvoiceInfoController.php @@ -0,0 +1,20 @@ +execute($request); + } +} diff --git a/app/Http/Controllers/Companies/UpdateCompanyDetailsController.php b/app/Http/Controllers/Companies/UpdateCompanyDetailsController.php new file mode 100644 index 00000000..e8cbfd1c --- /dev/null +++ b/app/Http/Controllers/Companies/UpdateCompanyDetailsController.php @@ -0,0 +1,21 @@ +execute($request); + } +} diff --git a/app/Http/Controllers/Companies/UpdateCompanyEInvoiceInfoController.php b/app/Http/Controllers/Companies/UpdateCompanyEInvoiceInfoController.php new file mode 100644 index 00000000..9157e9de --- /dev/null +++ b/app/Http/Controllers/Companies/UpdateCompanyEInvoiceInfoController.php @@ -0,0 +1,29 @@ +execute($request); + } + + /** + * @param Request $request + * @param UpdateCompanyEInvoiceRequestLogic $logic + * @return JsonResponse + */ + public function updateRequest(Request $request, UpdateCompanyEInvoiceRequestLogic $logic): JsonResponse { + return $logic->execute($request); + } +} diff --git a/app/Http/Controllers/Exports/ExportCustomersToExcelController.php b/app/Http/Controllers/Exports/ExportCustomersToExcelController.php index c63370a9..67a23d50 100644 --- a/app/Http/Controllers/Exports/ExportCustomersToExcelController.php +++ b/app/Http/Controllers/Exports/ExportCustomersToExcelController.php @@ -13,15 +13,16 @@ use App\Classes\Modules\Exports\Services\ExportsNullDebtors; use App\Classes\Modules\Exports\Services\ExportsPaymentTransactions; use App\Classes\Modules\Exports\Services\ExportsWalletTransactions; use App\Classes\Modules\Exports\Services\ExportsInvoiceTransactions; +use App\Classes\Modules\Exports\Services\ExportsReceiptTransactions; +use App\Classes\Modules\Exports\Services\ExportsImportedReceiptMappeds; +use App\Classes\Modules\Exports\Services\ExportsWhiteFormTransactions; +use App\Classes\Modules\Exports\Services\ExportsImportedInvoiceMappeds; +use App\Classes\Modules\Exports\Services\ExportsEInvoiceDebtorSummary; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Maatwebsite\Excel\Excel; -use App\Classes\Modules\Exports\Services\ExportsImportedInvoiceMappeds; use App\Models\TransactionMappingLog; -use App\Classes\Modules\Exports\Services\ExportsReceiptTransactions; -use App\Classes\Modules\Exports\Services\ExportsImportedReceiptMappeds; -use App\Classes\Modules\Exports\Services\ExportsWhiteFormTransactions; use Illuminate\Support\Facades\Storage; use App\Classes\General\AWSS3Helper; use App\Classes\Modules\Exports\Services\ExportsAllCustomersInfoForLarkSystem; @@ -249,7 +250,7 @@ class ExportCustomersToExcelController if ($password !== 'all_customers_data') { return response()->json(['error' => 'Invalid password'], 403); } - + $exportsAllCustomersInfoForLarkSystem = new ExportsAllCustomersInfoForLarkSystem($request); $exportFileName = 'exchange_all_customers_info_for_lark_system.xls'; @@ -262,5 +263,17 @@ class ExportCustomersToExcelController return $response; } } -} + public function eInvoiceDebtorSummary(ExportsEInvoiceDebtorSummary $exportsEInvoiceDebtorSummary, Request $request){ + $exportFileName = 'EINV_DEBTOR_SUMMARY.xls'; + $filesystemDriver = Storage::getDefaultDriver(); + if($filesystemDriver === 's3'){ + return response([ 'src' => AWSS3Helper::S3Exportable($exportFileName, $exportsEInvoiceDebtorSummary) ]); + } + else{ + $response = $exportsEInvoiceDebtorSummary->download($exportFileName, Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']); + ob_end_clean(); + return $response; + } + } +} diff --git a/app/Http/Controllers/Remarks/ListRefundRemarksController.php b/app/Http/Controllers/Remarks/ListRefundRemarksController.php new file mode 100644 index 00000000..7d4e9db9 --- /dev/null +++ b/app/Http/Controllers/Remarks/ListRefundRemarksController.php @@ -0,0 +1,29 @@ + $category) { + // $returnArray[] = [ + // 'name' => $reason + // ]; + // } + // $row['payload']["data"] = $returnArray; + + $allReasons = RemarkRefundReason::REFUND_REASONS; + $row['payload']['data'] = array_keys($allReasons); + return response()->json($row); + } +} diff --git a/app/Http/Controllers/Rules/CheckRuleController.php b/app/Http/Controllers/Rules/CheckRuleController.php new file mode 100644 index 00000000..59b24cd0 --- /dev/null +++ b/app/Http/Controllers/Rules/CheckRuleController.php @@ -0,0 +1,39 @@ +execute($request); + } + + /** + * @param Request $request + * @param CheckPurchaseOrderRuleLogic $logic + * @return JsonResponse + */ + 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/Controllers/Transactions/CreatePurchaseOrderTransactionController.php b/app/Http/Controllers/Transactions/CreatePurchaseOrderTransactionController.php index 67a74537..625f1afe 100644 --- a/app/Http/Controllers/Transactions/CreatePurchaseOrderTransactionController.php +++ b/app/Http/Controllers/Transactions/CreatePurchaseOrderTransactionController.php @@ -6,6 +6,8 @@ namespace App\Http\Controllers\Transactions; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use App\Classes\Modules\Transactions\ControllersLogic\CreatePurchaseOrderTransactionLogic; +use App\Classes\Modules\Bookings\ControllersLogic\UpdateBookingAmountWithPOLogic; + class CreatePurchaseOrderTransactionController @@ -15,7 +17,13 @@ class CreatePurchaseOrderTransactionController * @param CreatePurchaseOrderTransactionLogic $logic * @return JsonResponse */ - public function create(Request $request, CreatePurchaseOrderTransactionLogic $logic) : JsonResponse { - return $logic->execute($request); + public function create(Request $request, CreatePurchaseOrderTransactionLogic $createLogic, UpdateBookingAmountWithPOLogic $updateLogic) : JsonResponse { + // return $logic->execute($request); + $updateResult = $updateLogic->execute($request); + + if ($updateResult instanceof JsonResponse && $updateResult->getStatusCode() !== 200) { + return $updateResult; + } + return $createLogic->execute($request); } } diff --git a/app/Http/Controllers/Transactions/GenerateCreditNotePdfController.php b/app/Http/Controllers/Transactions/GenerateCreditNotePdfController.php index 11d27cdd..f58d419f 100644 --- a/app/Http/Controllers/Transactions/GenerateCreditNotePdfController.php +++ b/app/Http/Controllers/Transactions/GenerateCreditNotePdfController.php @@ -5,11 +5,15 @@ namespace App\Http\Controllers\Transactions; use Illuminate\Http\Request; use App\Classes\Modules\Transactions\ControllersLogic\GenerateCreditNotePdfLogic; -use Illuminate\Http\JsonResponse; +use App\Classes\Modules\Transactions\ControllersLogic\GenerateCreditNotePdfV2Logic; class GenerateCreditNotePdfController { public function download(Request $request, GenerateCreditNotePdfLogic $logic) { return $logic->execute($request); } + + public function downloadV2(Request $request, GenerateCreditNotePdfV2Logic $logic) { + return $logic->execute($request); + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index c6186664..2d50694d 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -80,5 +80,6 @@ class Kernel extends HttpKernel 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'valid.token' => ValidateToken::class, 'token.check' => \App\Http\Middleware\TokenCheckerMiddleware::class, + 'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class, //cief todo: 90 - maintenance ]; } diff --git a/app/Http/Middleware/EnsureUserIsAdmin.php b/app/Http/Middleware/EnsureUserIsAdmin.php new file mode 100644 index 00000000..58c206ac --- /dev/null +++ b/app/Http/Middleware/EnsureUserIsAdmin.php @@ -0,0 +1,27 @@ +authenticate(); + if(!in_array($user->type, RoleTypes::ADMIN_ROLES)){ + return response()->view('errors.503', [], 503); + } + return $next($request); + } +} diff --git a/app/Http/Resources/AddressEInvoiceResource.php b/app/Http/Resources/AddressEInvoiceResource.php new file mode 100644 index 00000000..cbdabe17 --- /dev/null +++ b/app/Http/Resources/AddressEInvoiceResource.php @@ -0,0 +1,28 @@ + $this->id, + 'street_one' => $this->street_one, + 'street_two' => $this->street_two, + 'district' => $this->district, + 'state' => $this->state, + 'post_code' => (int) $this->postcode, + 'country' => $this->country, + 'billing' => (int) $this->billing + ]; + } +} diff --git a/app/Http/Resources/BookingResource.php b/app/Http/Resources/BookingResource.php index 83e53249..449c6a59 100644 --- a/app/Http/Resources/BookingResource.php +++ b/app/Http/Resources/BookingResource.php @@ -24,6 +24,17 @@ class BookingResource extends JsonResource */ public function toArray($request) { + $eInvoice = false; + $eInvoiceStartDate = Carbon::parse(env('E_INVOICE_START_DATE', '2025-07-01 00:00:00')); + $bookingCreatedDate = Carbon::parse($this->created_at); + $eInvoiceRequestedDate = Carbon::parse($this->company->e_invoice_requested_at); + //cief todo: 90 - for testing + if ($bookingCreatedDate->isAfter($eInvoiceStartDate) && $this->company->e_invoice === 1) { //&& $bookingCreatedDate->diffInMinutes($eInvoiceRequestedDate) <= 480 cief todo: 90 + $eInvoice = true; + } + // if ($this->company->e_invoice === 1) { + // $eInvoice = true; + // } return [ 'id' => $this->id, 'company' => new CompanyResource($this->company), @@ -42,6 +53,7 @@ class BookingResource extends JsonResource 'purchase_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::PURCHASE_ORDER)->first()), 'delivery_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::DELIVER_ORDER)->first()), 'invoice' => new DocumentResource($this->documents()->where('document_type', DocumentType::INVOICE)->first()), + 'e_invoice' => new DocumentResource($this->documents()->where('document_type', DocumentType::EINVOICE)->latest()->first()), '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()), @@ -76,7 +88,8 @@ class BookingResource extends JsonResource }); }); })->latest()->get()) - ]) + ]), + 'einvoice' => $eInvoice, ]; } } diff --git a/app/Http/Resources/CompanyResource.php b/app/Http/Resources/CompanyResource.php index b63d0f8d..da166177 100644 --- a/app/Http/Resources/CompanyResource.php +++ b/app/Http/Resources/CompanyResource.php @@ -37,11 +37,15 @@ class CompanyResource extends JsonResource 'name' => $this->name, 'reference' => $this->reference, 'debtor' => $this->debtor, + 'e_invoice' => $this->e_invoice, + 'tin' => $this->tin, + 'msic_code' => $this->msic_code, 'type' => (int) $this->type, 'business_type' => (int) $this->business_type, 'status' => (int) $this->status, 'contact' => new ContactResource ($this->when($this->has('contacts'), $this->contacts->first())), 'address' => new AddressResource($this->when($this->has('addresses'), $this->addresses->where('billing', true)->first())), + 'address_einvoice' => $this->e_invoice ? new AddressEInvoiceResource($this->when($this->has('addresses'), $this->addresses->where('billing', false)->where('e_invoice', true)->sortByDesc('created_at')->first())) : null, 'employee' => new UserResource(Auth::user()->type === RoleTypes::USER ? $this->employees()->where('email', '=', Auth::user()->email)->first() : $this->employees()->orderBy('id', 'DESC')->first()), 'identification' => new DocumentResource($this->documents->whereIn('document_type', DocumentType::IDENTIFICATION_DOCUMENTS)->sortByDesc('created_at')->first()), 'bookings' => $this->whenLoaded('bookings', $this->bookings()->orderBy('id', 'DESC')->get(), []), diff --git a/app/Http/Resources/EInvoiceInfoResource.php b/app/Http/Resources/EInvoiceInfoResource.php new file mode 100644 index 00000000..d183032c --- /dev/null +++ b/app/Http/Resources/EInvoiceInfoResource.php @@ -0,0 +1,31 @@ + $this->id, + 'street_one' => $this->street_one, + 'street_two' => $this->street_two, + 'district' => $this->district, + 'state' => $this->state, + 'post_code' => (int) $this->postcode, + 'country' => $this->country, + 'billing' => (int) $this->billing, + 'msic_code' => (string) $this->msic_code, + 'tin' => (string) $this->tin, + 'e_invoice' => (int) $this->e_invoice, + ]; + } +} diff --git a/app/Http/Resources/RuleResource.php b/app/Http/Resources/RuleResource.php new file mode 100644 index 00000000..1fb42181 --- /dev/null +++ b/app/Http/Resources/RuleResource.php @@ -0,0 +1,22 @@ + $this->success, + 'messages' => $this->messages, + ]; + } +} 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/app/Http/Resources/TransactionResource.php b/app/Http/Resources/TransactionResource.php index 4271f030..46c1f29b 100644 --- a/app/Http/Resources/TransactionResource.php +++ b/app/Http/Resources/TransactionResource.php @@ -8,6 +8,7 @@ use App\Classes\ValueObjects\Constants\TransactionType; use App\Models\Booking; use Carbon\Carbon; use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Support\Facades\Log; class TransactionResource extends JsonResource { @@ -56,6 +57,7 @@ class TransactionResource extends JsonResource 'currency_rate' => (double) $this->currency_rate, 'status' => (int) $this->status, 'details' => TransactionDetailResource::collection($this->transactionDetails), + 'receipt_voucher' => $this->type === TransactionType::PAYMENT && $this->transactions()->where('type', TransactionType::RECEIPT_VOUCHER)->latest()->first() ? new DocumentResource($this->transactions()->where('type', TransactionType::RECEIPT_VOUCHER)->latest()->first()->documents()->first()) : null, 'documents' => new DocumentResource($this->documents()->first()), 'transaction_bill' => new TransactionResource($this->when((int) $this->type === TransactionType::PAYMENT, $this->transactions()->bills()->first())), 'transaction_refunds' => TransactionResource::collection($this->when((int) $this->type === TransactionType::PAYMENT, $this->transactions()->refunds()->get())), 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/app/Models/Booking.php b/app/Models/Booking.php index 259ca01e..d488c1e4 100644 --- a/app/Models/Booking.php +++ b/app/Models/Booking.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Classes\General\Interfaces\Documentable; +use App\Classes\General\Interfaces\KeyValueInterface; use App\Classes\General\Interfaces\Transactionable; use App\Classes\General\Interfaces\Voucherifiable; use App\Classes\General\Traits\LogData; @@ -26,7 +27,7 @@ use Staudenmeir\EloquentHasManyDeep\HasRelationships; * @property int convertible_currency_id * @property int conversion_currency_id */ -class Booking extends AbstractModel implements Documentable, Transactionable, Voucherifiable +class Booking extends AbstractModel implements Documentable, Transactionable, Voucherifiable, KeyValueInterface { use HasRelationships; use SoftDeletes; @@ -130,4 +131,12 @@ class Booking extends AbstractModel implements Documentable, Transactionable, Vo return $this->morphMany(VoucherEntityMapping::class, 'owner'); } + /** + * @return MorphMany + */ + public function attributesKVP(): MorphMany + { + return $this->morphMany(KeyValuePair::class, 'owner'); + } + } diff --git a/composer.json b/composer.json index 757d16f2..c9975f33 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "fruitcake/laravel-cors": "^1.0", "guzzlehttp/guzzle": "^7.0.1", "intervention/image": "^2.5", + "kwn/number-to-words": "^2.11", "laravel/framework": "^8.0", "laravel/tinker": "^2.0", "laravel/vapor-cli": "^1.55", diff --git a/config/maintenance.php b/config/maintenance.php new file mode 100644 index 00000000..66376756 --- /dev/null +++ b/config/maintenance.php @@ -0,0 +1,6 @@ + env('MAINTENANCE_MESSAGE_TITLE', "We'll be back soon!"), + 'message' => env('MAINTENANCE_MESSAGE', "Sorry for the inconvenience but we're performing some maintenance at the moment."), +]; diff --git a/config/qr.php b/config/qr.php new file mode 100644 index 00000000..e42be03a --- /dev/null +++ b/config/qr.php @@ -0,0 +1,5 @@ + 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=', +]; diff --git a/database/migrations/2025_05_04_150726_add_einvoice_to_addresses_table.php b/database/migrations/2025_05_04_150726_add_einvoice_to_addresses_table.php new file mode 100644 index 00000000..990a9e1c --- /dev/null +++ b/database/migrations/2025_05_04_150726_add_einvoice_to_addresses_table.php @@ -0,0 +1,32 @@ +boolean('e_invoice')->default(false)->after('billing'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('addresses', function (Blueprint $table) { + $table->dropColumn('e_invoice'); + }); + } +} diff --git a/database/migrations/2025_05_04_150735_add_tin_to_companies_table.php b/database/migrations/2025_05_04_150735_add_tin_to_companies_table.php new file mode 100644 index 00000000..ed5f2c58 --- /dev/null +++ b/database/migrations/2025_05_04_150735_add_tin_to_companies_table.php @@ -0,0 +1,38 @@ +string('tin')->nullable()->after('debtor'); + $table->string('msic_code')->nullable()->after('debtor')->comment("5-digit code representing business activity"); + $table->timestamp('e_invoice_requested_at')->nullable()->after('debtor'); + $table->boolean('e_invoice')->nullable()->default(null)->after('debtor'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('companies', function (Blueprint $table) { + $table->dropColumn('tin'); + $table->dropColumn('msic_code'); + $table->dropColumn('e_invoice_requested_at'); + $table->dropColumn('e_invoice'); + }); + } +} diff --git a/resources/assets/vue/components/address/forms/EInvoiceInfoFormComponent.vue b/resources/assets/vue/components/address/forms/EInvoiceInfoFormComponent.vue new file mode 100644 index 00000000..7722e33e --- /dev/null +++ b/resources/assets/vue/components/address/forms/EInvoiceInfoFormComponent.vue @@ -0,0 +1,258 @@ + + diff --git a/resources/assets/vue/components/bookings/elements/EInvoiceInfoComponent.vue b/resources/assets/vue/components/bookings/elements/EInvoiceInfoComponent.vue new file mode 100644 index 00000000..0a168216 --- /dev/null +++ b/resources/assets/vue/components/bookings/elements/EInvoiceInfoComponent.vue @@ -0,0 +1,68 @@ + + diff --git a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue index f35f8573..53007c6b 100644 --- a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue +++ b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue @@ -6,7 +6,7 @@
-
+
Status
{{ item.status === 7 ? 'Refunded' : (item.status === 1 ? 'Pending Verification' : item.status === 4 ? 'Rejected' : 'Payment Approved')}} @@ -15,13 +15,13 @@ {{ item.status === 1 ? 'Pending Verification' : item.status === 4 ? 'Rejected' : 'Processing Payment'}}
-
+
Payment Amount
{{item.original_currency.short_code}} {{(Math.round((item.original_amount - item.refunded_amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}
-
+
Refunded Amount
{{item.original_currency.short_code}} {{(Math.round((item.refunded_amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}} @@ -45,10 +45,23 @@
+
+ + + +
-
+
+

Payment Slip

@@ -71,19 +84,19 @@ {{ item.transaction_bill.status === 1 ? 'Processing Payment' : 'Transferred'}}
-
+
Payment Amount
{{item.original_currency.short_code}} {{(Math.round((item.original_amount - item.refunded_amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}
-
+
Refunded Amount
{{item.original_currency.short_code}} {{(Math.round((totalRefunds + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}
-
+
Service Type
{{item.booking.service.name}} ({{item.booking.service.id}}) @@ -114,12 +127,25 @@
+
+ + + +
@@ -220,6 +246,7 @@
Your Payment Proof
+
@@ -233,6 +260,7 @@
+
+
+
+ + + + + + +
+
-
- +
+ - + + + +
+
+
+
+ + +
@@ -332,38 +381,47 @@
-
+
Created At
{{ refund.created_at }}
-
+
Status
{{ refund.status === 1 ? 'Pending Verification' : refund.status === 2 ? 'Approved' : 'Rejected'}}
-
- - - - - - +
+
+ + + + + + +
-
@@ -436,6 +494,7 @@ bank_id: 1 }, section: 'bookingDetailSection', + eInvoiceStartDate: window.E_INVOICE_START_DATE || '' } }, computed: { @@ -476,6 +535,14 @@ hasRefundInProgress() { var refundTransactionsStatus = this.data.transaction_refunds.length > 0 ? this.data.transaction_refunds.map(refund => refund.status) : []; return refundTransactionsStatus.includes(0) || refundTransactionsStatus.includes(1) + }, + showEditBookingAmount(){ + return this.data.booking.company.employee.status === 2 && this.data.booking.company.status === 2 && (Math.round((this.data.booking.outstanding_amount + Number.EPSILON) * 100) / 100) > 0; + }, + showDownloadCreditNote() { + const today = new Date(); + const einvoiceStartDate = new Date(this.eInvoiceStartDate); + return today > einvoiceStartDate; } }, methods: { @@ -495,6 +562,26 @@ paymentMethodArray[5] = 'Payment Gateway'; return paymentMethodArray[paymentMethod]; }, + downloadCreditNote(transactionId) { + let url = this.route('transaction.credit_note.download.v2', transactionId); + if(window.LARAVEL_VAPOR_ENABLED){ + this.$store.dispatch('crudRequest', {endpoint: url, method: 'get'}) + .then(response => + { + let success = response.ok; + response.json().then(response => { + if(!success){return;} + if (response.src) { + window.open(response.src, '_blank'); + } + }); + } + ); + } + else{ + window.open(url, '_blank'); + } + }, }, mixins: [componentHandler] } diff --git a/resources/assets/vue/components/bookings/elements/RequestCreditNoteComponent.vue b/resources/assets/vue/components/bookings/elements/RequestCreditNoteComponent.vue new file mode 100644 index 00000000..69a58c18 --- /dev/null +++ b/resources/assets/vue/components/bookings/elements/RequestCreditNoteComponent.vue @@ -0,0 +1,175 @@ + + + diff --git a/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue new file mode 100644 index 00000000..03c327cf --- /dev/null +++ b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationV2Component.vue @@ -0,0 +1,845 @@ + + + diff --git a/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue b/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue index 975c4592..3b440ab2 100644 --- a/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue @@ -111,7 +111,11 @@ section: { type: String, required: true - } + }, + companyId: { + type: Number, + required: true + }, }, data(){ return { @@ -121,14 +125,16 @@ amount: this.amount, bank_code: this.bank_code, voucher_code: this.calculation.voucher_code, - voucher_discount_amount: this.calculation.voucher_discount_amount + voucher_discount_amount: this.calculation.voucher_discount_amount, + booking_id: this.id, + company_id: this.companyId, } } }, methods: { submitForm(){ this.isLoading = true; - this.submit(route('api.booking.payment.create', this.id), 'post', this.section, false, false); + this.submit(route('api.booking.payment.create', this.id), 'post', this.section, false, true); }, successHandler(response){ if (response.payload.data.payment_method === 5) { diff --git a/resources/assets/vue/components/bookings/forms/EditBookingAmountFormV2Component.vue b/resources/assets/vue/components/bookings/forms/EditBookingAmountFormV2Component.vue new file mode 100644 index 00000000..a7001a99 --- /dev/null +++ b/resources/assets/vue/components/bookings/forms/EditBookingAmountFormV2Component.vue @@ -0,0 +1,69 @@ + + diff --git a/resources/assets/vue/components/bookings/forms/PaymentVerificationFormComponent.vue b/resources/assets/vue/components/bookings/forms/PaymentVerificationFormComponent.vue index 4e47728a..1e5af3c3 100644 --- a/resources/assets/vue/components/bookings/forms/PaymentVerificationFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PaymentVerificationFormComponent.vue @@ -118,15 +118,17 @@ }, methods: { submitForm(){ - this.parameters = { - files: this.files + files: this.files, + booking_id: this.id, + company_id: this.data.booking.company.id, + payment_id: this.data.id, }; - this.submit(this.route('api.booking.payment.verification.create', this.id, this.data.id), 'post', this.section, true, false) + this.submit(this.route('api.booking.payment.verification.create', this.id, this.data.id), 'post', this.section, true, true); } }, mixins: [ModalFromHandler] } - \ No newline at end of file + diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue index de4e496b..1009153b 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -13,7 +13,7 @@
-

Any Purchase Orders that aren't submitted within 60 days will be closed for editing.

+

Ensure PO details are fill in correctly — NO changes allowed after submission.

Please note that this customer request to manual fill up the PO.

@@ -150,8 +150,15 @@
- +
+ + + +
@@ -188,12 +195,13 @@
@@ -235,7 +243,8 @@ }, created() { this.products = this.data.purchase_order ? this.data.purchase_order.details : []; - this.submitted = this.data.purchase_order ? this.data.purchase_order.status === 1 || this.data.purchase_order.status === 2: false; + this.submitted = this.data.purchase_order ? true : false; + // this.submitted = this.data.purchase_order ? this.data.purchase_order.status === 1 || this.data.purchase_order.status === 2: false; }, computed: { productTotal(){ @@ -245,13 +254,29 @@ return this.products.reduce(function(last, product) { return last + product.total; }, 0); + }, + allowPOEditing() { + //Condition 1 + const noPaymentsMade = Math.round((this.data.paid_amount + Number.EPSILON) * 100) / 100 === 0; + const noPaymentPendingVerifications = this.data.payment_history.every(payment => payment.status !== 1); + + //Condition 2 + const paymentsMade = Math.round((this.data.paid_amount + Number.EPSILON) * 100) / 100 > 0; + const outstandingAmount = Math.round((this.data.outstanding_amount + Number.EPSILON) * 100) / 100 > 0; + const allPaymentApproved = this.data.payment_history.every(payment => payment.status === 2); + + //Condition 3 + const adminBeforeApproval = this.$store.getters.isAdmin && !(this.data.purchase_order.status === 2); + + return (noPaymentsMade && noPaymentPendingVerifications) || (paymentsMade && outstandingAmount && allPaymentApproved) || adminBeforeApproval; } }, watch: { 'data': function () { if (this.data && this.data.purchase_order && this.data.purchase_order.details) { this.products = this.data.purchase_order.details; - this.submitted = this.data.purchase_order ? this.data.purchase_order.status === 1 || this.data.purchase_order.status === 2: false; + this.submitted = this.data.purchase_order ? true : false; + // this.submitted = this.data.purchase_order ? this.data.purchase_order.status === 1 || this.data.purchase_order.status === 2: false; } else { this.products = []; } @@ -278,10 +303,12 @@ } }, - submitForm(){ + handleConfirmed(value) { 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); diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderSubmitConfirmationComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderSubmitConfirmationComponent.vue new file mode 100644 index 00000000..cdc1256b --- /dev/null +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderSubmitConfirmationComponent.vue @@ -0,0 +1,38 @@ + + diff --git a/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue b/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue new file mode 100644 index 00000000..6d6d15f9 --- /dev/null +++ b/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue @@ -0,0 +1,41 @@ + + diff --git a/resources/assets/vue/components/bookings/forms/RegenerateReceiptVoucherComponent.vue b/resources/assets/vue/components/bookings/forms/RegenerateReceiptVoucherComponent.vue new file mode 100644 index 00000000..6445272b --- /dev/null +++ b/resources/assets/vue/components/bookings/forms/RegenerateReceiptVoucherComponent.vue @@ -0,0 +1,43 @@ + + diff --git a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue index e12074b0..68dffd3a 100644 --- a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue +++ b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue @@ -31,7 +31,7 @@
-
+
+
+ + + +
@@ -14,16 +19,20 @@ props: { validator: { required: true - } + }, }, data(){ return { errorMessages:{ required: 'this field is required', email: 'enter a valid email address', + maxLength: 'this field must have at most', minLength: 'this field must have at least', sameAs: 'this field must match the', - maxValue: 'this value must not exceeds' + maxValue: 'this value must not exceeds', + alphaNum: 'this value must be alphanumeric', + fiveDigits: 'this value must be exactly 5 digits', //custom + // notZero: 'this value cannot be zero', }, } }, diff --git a/resources/assets/vue/components/wallets/elements/CustomerWalletTransactionComponent.vue b/resources/assets/vue/components/wallets/elements/CustomerWalletTransactionComponent.vue index ad5ccb07..c8220b04 100644 --- a/resources/assets/vue/components/wallets/elements/CustomerWalletTransactionComponent.vue +++ b/resources/assets/vue/components/wallets/elements/CustomerWalletTransactionComponent.vue @@ -1,7 +1,9 @@