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

This commit is contained in:
Edmond Lang
2025-06-28 16:22:59 +08:00
126 changed files with 5886 additions and 241 deletions
+5
View File
@@ -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."
@@ -0,0 +1,14 @@
<?php
namespace App\Classes\Exceptions;
use App\Classes\ValueObjects\Constants\HttpStatus;
final class CriteriaNotFulfilledException extends ServiceApiException {
public function __construct(?string $message = null) {
parent::__construct(
$message ?? 'One or more required criteria were not fulfilled.',
HttpStatus::VALIDATION_FAILED
);
}
}
@@ -4,6 +4,7 @@ namespace App\Classes\General\Abstracts;
use App\Classes\Exceptions\AccessForbiddenException;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\Exceptions\RequestValidationException;
use App\Classes\General\Interfaces\DataTransferObject;
@@ -23,6 +24,7 @@ abstract class AbstractRule
* @return bool
* @throws AccessForbiddenException
* @throws RequestValidationException
* @throws CriteriaNotFulfilledException
*/
public function passes(?DataTransferObject $object = null): bool {
try {
@@ -35,12 +37,14 @@ abstract class AbstractRule
return true;
} catch(AccessForbiddenException $exception){
throw new AccessForbiddenException('You don\'t have permission to perform this action');
} catch(CriteriaNotFulfilledException $exception){
throw new CriteriaNotFulfilledException($exception->getMessage());
} catch(\Exception $exception){
throw new RequestValidationException($exception->getMessage());
}
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class IsEInvoice implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('e_invoice', $value);
}
}
+22 -1
View File
@@ -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');
}
}
@@ -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
}
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Classes\Modules\Addresses\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Addresses\Services\ListsStates;
use App\Classes\Modules\Addresses\Standards\Rules\CanListAddresses;
use App\Http\Resources\StateResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListStatesLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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));
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Addresses\Services;
use App\Classes\General\Eloquent\AbstractListRecord;
use App\Models\State;
use Illuminate\Database\Eloquent\Builder;
class ListsStates extends AbstractListRecord
{
/** @var State */
private $repository;
/**
* ListsStates constructor.
* @param State $repository
*/
public function __construct(State $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Addresses\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Models\Address;
class UpdatesAddressMetadata extends AbstractUpdateRecord
{
/**
* @param Address $model
* @param bool $billing
* @param bool $eInvoice
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(Address $model, bool $billing, bool $eInvoice) {
$model->billing = $billing;
$model->e_invoice = $eInvoice;
return $this->handler($model);
}
}
@@ -0,0 +1,36 @@
<?php
namespace App\Classes\Modules\Addresses\Services;
use App\Classes\General\Eloquent\AbstractUpdateRelationshipRecord;
use App\Classes\Modules\Addresses\DataTransferObjects\AddressObject;
use App\Models\Address;
use App\Models\Company;
class UpsertsAddress extends AbstractUpdateRelationshipRecord
{
/**
* Create or update the company's address.
*
* @param Company $company
* @param AddressObject $object
* @param int $id
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(Company $company, AddressObject $object, int $id)
{
//Create new or update?
$model = $company->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);
}
}
@@ -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]);
}
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Billplzs\Services;
use Illuminate\Support\Facades\Http;
use App\Classes\Exceptions\MalformedRequestException;
use Illuminate\Support\Facades\Log;
class DeletesBillplzBill
{
/**
* @param string $billID
* @throws MalformedRequestException
*/
public function execute(string $billID) {
try{
$response = Http::withBasicAuth(config('billplz.api_key').':', '')->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());
}
}
}
@@ -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([]);
}
}
}
@@ -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("<br>- ", $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));
@@ -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) {
@@ -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("<br>- ", $result->messages()));
}
$transaction = $this->fetchesTransaction->execute(['id' => $request->route('payment_id')]);
@@ -80,4 +102,4 @@ class CreatePaymentVerificationDocumentLogic extends AbstractControllerLogic
return $this->response([]);
}
}
}
@@ -0,0 +1,81 @@
<?php
namespace App\Classes\Modules\Bookings\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Transactions\Services\FetchesTransaction;
use App\Classes\Modules\Bookings\Standards\Rules\CanFetchBooking;
use App\Classes\Modules\Transactions\Processors\CreateReceiptVoucherTransactionProcessor;
use App\Http\Resources\BookingResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class RegenerateBookingPaymentRVLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => '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));
}
}
@@ -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));
}
@@ -0,0 +1,115 @@
<?php
namespace App\Classes\Modules\Bookings\ControllersLogic;
use App\Classes\Modules\Bookings\Services\UpdatesBookingFixedAmount;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Bookings\Standards\Rules\CanUpdateBooking;
use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair;
use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair;
use App\Classes\Modules\Bookings\Services\CalculatesBookingOutstanding;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject;
use App\Http\Resources\BookingResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use ErrorException;
class UpdateBookingAmountOnHoldLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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));
}
}
@@ -0,0 +1,103 @@
<?php
namespace App\Classes\Modules\Bookings\ControllersLogic;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Bookings\Services\UpdatesBookingFixedAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingOutstanding;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Http\Resources\BookingResource;
use App\Models\Booking;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class UpdateBookingAmountWithPOLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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));
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Bookings\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class ConfirmBookingDTO implements DataTransferObject
{
public int $bookingId;
public int $companyId;
public function __construct(array $data)
{
$this->bookingId = $data['booking_id'];
$this->companyId = $data['company_id'];
}
public function toArray(): array
{
return [
'booking_id' => $this->bookingId,
'company_id' => $this->companyId,
];
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Classes\Modules\Bookings\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class CreatePaymentVerificationDocumentDTO implements DataTransferObject
{
public int $bookingId;
public int $companyId;
public int $paymentId;
public function __construct(array $data)
{
$this->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,
];
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Classes\Modules\Companies\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Companies\Standards\Rules\CanFetchCompany;
use App\Http\Resources\EInvoiceInfoResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class FetchCompanyEInvoiceInfoLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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));
}
}
@@ -0,0 +1,132 @@
<?php
namespace App\Classes\Modules\Companies\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Companies\DataTransferObjects\CompanyObject;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Companies\Services\UpdatesCompany;
use App\Classes\Modules\Companies\Services\UpdatesCompanyDebtor;
use App\Classes\Modules\Addresses\Services\UpsertsAddress;
use App\Classes\Modules\Addresses\Services\FetchesDistrict;
use App\Classes\Modules\Addresses\Services\UpdatesAddressMetadata;
use App\Classes\Modules\Addresses\Standards\Rules\CanCreateAddress;
use App\Classes\Modules\Addresses\DataTransferObjects\AddressObject;
use App\Classes\Modules\Companies\Services\UpdatesCompanyEInvoiceInfo;
use App\Classes\Modules\Companies\Standards\Rules\CanUpdateCompany;
use App\Classes\Modules\Companies\DataTransferObjects\UpdateCompanyDetailsDTO;
use App\Http\Resources\CompanyResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpdateCompanyDetailsLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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));
}
}
@@ -0,0 +1,114 @@
<?php
namespace App\Classes\Modules\Companies\ControllersLogic;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Addresses\Services\CreatesAddress;
use App\Classes\Modules\Addresses\Services\FetchesDistrict;
use App\Classes\Modules\Addresses\Services\UpdatesAddressMetadata;
use App\Classes\Modules\Addresses\Standards\Rules\CanCreateAddress;
use App\Classes\Modules\Addresses\DataTransferObjects\AddressObject;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Companies\Services\UpdatesCompanyEInvoiceInfo;
use App\Classes\Modules\Companies\DataTransferObjects\EInvoiceInfoDTO;
use App\Classes\Modules\Rules\Services\RuleEvaluator;
use App\Classes\Modules\Rules\Standards\Rules\CanPassEInvoicePromptedRule;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpdateCompanyEInvoiceInfoLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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("<br>- ", $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([]);
}
}
@@ -0,0 +1,59 @@
<?php
namespace App\Classes\Modules\Companies\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Companies\Services\UpdatesCompanyEInvoiceRequest;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Companies\DataTransferObjects\EInvoiceRequestDTO;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpdateCompanyEInvoiceRequestLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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([]);
}
}
@@ -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));
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Classes\Modules\Companies\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class EInvoiceInfoDTO implements DataTransferObject
{
public string $tin;
public string $msicCode;
public int $districtId;
public int $stateId;
public int $companyId;
public string $streetOne;
public string $streetTwo;
public int $postCode;
public function __construct(array $data)
{
$this->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,
];
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Companies\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class EInvoiceRequestDTO implements DataTransferObject
{
public bool $eInvoiceRequest;
public int $companyId;
public function __construct(array $data)
{
$this->eInvoiceRequest = $data['e_invoice_request'];
$this->companyId = $data['company_id'];
}
public function toArray(): array
{
return [
'e_invoice_request' => $this->eInvoiceRequest,
'company_id' => $this->companyId,
];
}
}
@@ -0,0 +1,64 @@
<?php
namespace App\Classes\Modules\Companies\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class UpdateCompanyDetailsDTO implements DataTransferObject
{
public int $id;
public string $name;
public string $debtor;
public string $reference;
public int $type;
public string $tin;
public string $msicCode;
public int $addressId;
public int $districtId;
public int $stateId;
public int $companyId;
public string $streetOne;
public string $streetTwo;
public int $postCode;
public function __construct(array $data)
{
$this->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,
];
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Companies\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Models\Company;
class UpdatesCompanyEInvoiceInfo extends AbstractUpdateRecord
{
/**
* @param Company $model
* @param string $tin
* @param string $msicCode
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(Company $model, string $tin, string $msicCode)
{
$model->tin = $tin;
$model->msic_code = $msicCode;
return $this->handler($model);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Classes\Modules\Companies\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Models\Company;
class UpdatesCompanyEInvoiceRequest extends AbstractUpdateRecord
{
/**
* @param Company $model
* @param bool $eInvoice
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(Company $model, bool $eInvoice)
{
if (is_null($model->e_invoice_requested_at)) {
if($eInvoice){
$model->e_invoice_requested_at = now();
}
}
$model->e_invoice = $eInvoice;
return $this->handler($model);
}
}
@@ -0,0 +1,106 @@
<?php
namespace App\Classes\Modules\Exports\Services;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\BusinessType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Company;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ExportsEInvoiceDebtorSummary implements FromQuery, WithHeadings, WithHeadingRow, WithMapping, ShouldAutoSize
{
use Exportable;
public function headings(): array
{
return [
'Code',
'Need Tax INV?',
'Request Date',
'TIN NO.',
'DebtorControlAcc',
'ControlAccount',
'CompanyName',
'Desc2',
'DebtorType',
'DisplayTerm',
'CurrencyCode',
'RegisterNo',
'Address1',
'Address2',
'Address3',
'PostCode',
'DeliverAddr1',
'DeliverAddr2',
'DeliverAddr3',
'DeliverPostCode',
'EmailAddress',
'Attention',
'Phone1',
'Phone2',
'Fax1'
];
}
/**
* @return \Illuminate\Support\Collection|mixed
*/
public function query()
{
return Company::where(function($query){
// $query->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 [
'<<New>>', // 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
];
}
}
@@ -72,28 +72,28 @@ class ExportsNullDebtors implements FromQuery, WithHeadings, WithHeadingRow, Wit
public function map($company): array
{
return [
'<<New>>',
'300-0000',
'300-0000',
$company->name.' (PURCHASE)',
$company->reference,
'',
'PIA',
'MYR',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',//EmailAddress
'',
'',
'',
''
'<<New>>', // 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
];
}
}
}
@@ -0,0 +1,70 @@
<?php
namespace App\Classes\Modules\Rules\ControllersLogic;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Rules\DataTransferObjects\CheckEInvoiceRuleDTO;
use App\Classes\Modules\Rules\Services\RuleEvaluator;
use App\Classes\Modules\Rules\Standards\Rules\CanPassEInvoicePromptedRule;
use App\Classes\Modules\Rules\Standards\Rules\CanPassTINRule;
use App\Http\Resources\RuleResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CheckEInvoiceRuleLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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("<br>- ", $result->messages()));
}
return $this->resourceResponse(new RuleResource((object)$result));
}
}
@@ -0,0 +1,82 @@
<?php
namespace App\Classes\Modules\Rules\ControllersLogic;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Rules\DataTransferObjects\CheckPurchaseOrderRuleDTO;
use App\Classes\Modules\Rules\Services\RuleEvaluator;
use App\Classes\Modules\Rules\Standards\Rules\CanPassEInvoicePromptedRule;
use App\Classes\Modules\Rules\Standards\Rules\CanPassOrderDurationLimitRule;
use App\Classes\Modules\Rules\Standards\Rules\CanPassPurchaseOrderRule;
use App\Http\Resources\RuleResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CheckPurchaseOrderRuleLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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("<br>- ", $result->messages()));
}
return $this->resourceResponse(new RuleResource((object)$result));
}
}
@@ -0,0 +1,66 @@
<?php
namespace App\Classes\Modules\Rules\ControllersLogic;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Rules\DataTransferObjects\CheckTransferRuleDTO;
use App\Classes\Modules\Rules\Services\RuleEvaluator;
use App\Classes\Modules\Rules\Standards\Rules\CanPassOrderDurationLimitRule;
use App\Http\Resources\RuleResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CheckTransferRuleLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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("<br>- ", $result->messages()));
}
return $this->resourceResponse(new RuleResource((object)$result));
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Classes\Modules\Rules\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class CheckEInvoiceRuleDTO implements DataTransferObject
{
public int $companyId;
public function __construct(array $data)
{
$this->companyId = $data['company_id'];
}
public function toArray(): array
{
return [
'company_id' => $this->companyId,
];
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Rules\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class CheckPurchaseOrderRuleDTO implements DataTransferObject
{
public int $bookingId;
public int $companyId;
public function __construct(array $data)
{
$this->bookingId = $data['booking_id'];
$this->companyId = $data['company_id'];
}
public function toArray(): array
{
return [
'booking_id' => $this->bookingId,
'company_id' => $this->companyId,
];
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Classes\Modules\Rules\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class CheckTransferRuleDTO implements DataTransferObject
{
public int $bookingId;
public int $companyId;
public string $paymentReference;
public function __construct(array $data)
{
$this->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,
];
}
}
@@ -0,0 +1,44 @@
<?php
namespace App\Classes\Modules\Rules\Services;
use App\Classes\General\Abstracts\AbstractRule;
use App\Classes\General\Interfaces\DataTransferObject;
use App\Classes\Exceptions\AccessForbiddenException;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\Exceptions\RequestValidationException;
use App\Classes\ValueObjects\Response\RuleEvaluationResult;
class RuleEvaluator //For using AbstractRule
{
/**
* Evaluate multiple rules.
*
* @param AbstractRule[] $rules
* @param DataTransferObject|null $object
* @return RuleEvaluationResult
*/
public function evaluate(array $rules, ?DataTransferObject $object = null): RuleEvaluationResult
{
$messages = [];
$success = true;
foreach ($rules as $rule) {
try {
if (!$rule->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);
}
}
@@ -0,0 +1,56 @@
<?php
namespace App\Classes\Modules\Rules\Standards\Rules;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractRule;
use App\Classes\Modules\Companies\Services\FetchesCompany;
class CanPassEInvoicePromptedRule extends AbstractRule
{
/** @var FetchesCompany */
private $fetchesCompany;
/**
* CanPassEInvoicePromptedRule constructor.
* @param FetchesCompany $fetchesCompany
*/
public function __construct(FetchesCompany $fetchesCompany)
{
$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 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;
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Classes\Modules\Rules\Standards\Rules;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractRule;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount;
use App\Classes\ValueObjects\Constants\TransactionType;
class CanPassEditingPORule extends AbstractRule
{
/** @var FetchesBooking */
private $fetchesBooking;
/** @var FetchesCompany */
private $fetchesCompany;
/**
* CanPassEditingPORule constructor.
* @param FetchesBooking $fetchesBooking
* @param FetchesCompany $fetchesCompany
*/
public function __construct(FetchesBooking $fetchesBooking, FetchesCompany $fetchesCompany)
{
$this->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;
}
}
@@ -0,0 +1,92 @@
<?php
namespace App\Classes\Modules\Rules\Standards\Rules;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractRule;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Companies\Services\FetchesCompanyPaymentAttemptLimit;
use App\Classes\Modules\Transactions\Services\CalculatesTransactionExpiryDateTime;
use App\Classes\Modules\Billplzs\Services\DeletesBillplzBill;
use App\Classes\Modules\Transactions\Services\FetchesTransaction;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class CanPassOrderDurationLimitRule extends AbstractRule
{
/** @var FetchesBooking */
private $fetchesBooking;
/** @var FetchesCompanyPaymentAttemptLimit */
private $fetchesCompanyPaymentAttemptLimit;
/** @var CalculatesTransactionExpiryDateTime */
private $calculatesTransactionExpiryDateTime;
/** @var DeletesBillplzBill */
private $deletesBillplzBill;
/** @var FetchesTransaction */
private $fetchesTransaction;
/**
* CanPassOrderDurationLimitRule constructor.
* @param FetchesBooking $fetchesBooking
* @param FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit
* @param CalculatesTransactionExpiryDateTime $calculatesTransactionExpiryDateTime
* @param DeletesBillplzBill $deletesBillplzBill
* @param FetchesTransaction $fetchesTransaction
*/
public function __construct(FetchesBooking $fetchesBooking, FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit, CalculatesTransactionExpiryDateTime $calculatesTransactionExpiryDateTime, DeletesBillplzBill $deletesBillplzBill, FetchesTransaction $fetchesTransaction)
{
$this->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;
}
}
@@ -0,0 +1,96 @@
<?php
namespace App\Classes\Modules\Rules\Standards\Rules;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractRule;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\SegmentNameConstants;
use App\Classes\ValueObjects\Constants\ServiceTypeNameConstants;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\ServiceType;
use Illuminate\Support\Facades\Log;
class CanPassPurchaseOrderRule extends AbstractRule
{
/** @var FetchesBooking */
private $fetchesBooking;
/** @var FetchesCompany */
private $fetchesCompany;
/**
* CanPassPurchaseOrderRule constructor.
* @param FetchesBooking $fetchesBooking
* @param FetchesCompany $fetchesCompany
*/
public function __construct(FetchesBooking $fetchesBooking, FetchesCompany $fetchesCompany)
{
$this->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;
}
}
@@ -0,0 +1,55 @@
<?php
namespace App\Classes\Modules\Rules\Standards\Rules;
use App\Classes\Exceptions\CriteriaNotFulfilledException;
use App\Classes\General\Abstracts\AbstractRule;
use App\Classes\Modules\Companies\Services\FetchesCompany;
class CanPassTINRule extends AbstractRule
{
/** @var FetchesCompany */
private $fetchesCompany;
/**
* CanPassTINRule constructor.
* @param FetchesCompany $fetchesCompany
*/
public function __construct(FetchesCompany $fetchesCompany)
{
$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 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;
}
}
@@ -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("<br>- ", $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));
}
}
@@ -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
{
@@ -0,0 +1,115 @@
<?php
namespace App\Classes\Modules\Transactions\ControllersLogic;
use Illuminate\Http\Request;
use Mccarlosen\LaravelMpdf\Facades\LaravelMpdf;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Transactions\Services\FetchesTransaction;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\General\AWSS3Helper;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\KeyValuePair;
use App\Models\Transaction;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class GenerateCreditNotePdfV2Logic
{
/** @var FetchesTransaction */
private $fetchesTransaction;
/** @var FetchesCompany */
private $fetchesCompany;
/**
* GenerateCreditNotePdfV2Logic constructor.
* @param FetchesTransaction $fetchesTransaction
* @param FetchesCompany $fetchesCompany
*/
public function __construct(FetchesTransaction $fetchesTransaction, FetchesCompany $fetchesCompany)
{
$this->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);
}
}
}
@@ -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([]);
}
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Transactions\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class CreatePurchaseOrderDTO implements DataTransferObject
{
public int $bookingId;
public int $companyId;
public function __construct(array $data)
{
$this->bookingId = $data['booking_id'];
$this->companyId = $data['company_id'];
}
public function toArray(): array
{
return [
'booking_id' => $this->bookingId,
'company_id' => $this->companyId,
];
}
}
@@ -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);
}
}
@@ -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-');
@@ -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
@@ -0,0 +1,113 @@
<?php
namespace App\Classes\Modules\Transactions\Processors;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Modules\Transactions\Services\CreatesTransaction;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Bookings\Services\CalculatesBookingCurrencyAverageRate;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\Booking;
use App\Models\Transaction;
use Illuminate\Support\Facades\Log;
class CreateReceiptVoucherTransactionProcessor
{
/** @var CreatesTransaction */
private $createsTransaction;
/** @var GeneratesTransactionBillNumber */
private $generatesTransactionBillNumber;
/** @var CalculatesBookingCurrencyAverageRate */
private $calculatesBookingCurrencyAverageRate;
/** @var FetchesCompany */
private $fetchesCompany;
/** @var CreateInvoiceDocumentProcessor */
private $invoiceDocumentProcessor;
/**
* CreateReceiptVoucherTransactionProcessor constructor.
* @param CreatesTransaction $createsTransaction
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
* @param CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate
* @param FetchesCompany $fetchesCompany
* @param CreateInvoiceDocumentProcessor $invoiceDocumentProcessor
*/
public function __construct(CreatesTransaction $createsTransaction, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate, FetchesCompany $fetchesCompany, CreateInvoiceDocumentProcessor $invoiceDocumentProcessor)
{
$this->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);
}
}
@@ -0,0 +1,91 @@
<?php
namespace App\Classes\Modules\Transactions\Services;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Companies\Services\FetchesCompanyPaymentAttemptLimit;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class CalculatesTransactionExpiryDateTime
{
/** @var FetchesBooking */
private $fetchesBooking;
/** @var FetchesCompanyPaymentAttemptLimit */
private $fetchesCompanyPaymentAttemptLimit;
/**
* CalculatesTransactionExpiryDateTime constructor.
* @param FetchesBooking $fetchesBooking
* @param FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit
*/
public function __construct(FetchesBooking $fetchesBooking, FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit)
{
$this->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;
}
}
@@ -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;
}
}
@@ -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
}
@@ -0,0 +1,16 @@
<?php
namespace App\Classes\ValueObjects\Constants;
class RemarkRefundReason
{
public const REFUND_REASONS = [
'Not enough stock' => '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' => '',
];
}
@@ -0,0 +1,8 @@
<?php
namespace App\Classes\ValueObjects\Constants;
final class SegmentNameConstants {
public const MANUAL_PO_PERIODIC_1688 = "1688 Manual PO Periodic";
public const MANUAL_PO_1688 = "1688 Manual PO";
}
@@ -0,0 +1,9 @@
<?php
namespace App\Classes\ValueObjects\Constants;
final class ServiceTypeNameConstants {
public const PAYMENT_1688 = '1688 PAYMENT';
public const VIP_1688 = '1688 VIP';
}
@@ -38,6 +38,8 @@ final class TransactionType {
public const BILL_REFUND = 16;
public const RECEIPT_VOUCHER = 17;
public const ID_TO_NAME = [
self::PAYMENT_ATTEMPT => "PAYMENT_ATTEMPT",
self::PAYMENT => "PAYMENT",
@@ -56,5 +58,5 @@ final class TransactionType {
self::SUPPLIER_PAYMENT => "SUPPLIER_PAYMENT",
self::SUPPLIER_REFUND => "SUPPLIER_REFUND",
];
}
@@ -0,0 +1,30 @@
<?php
namespace App\Classes\ValueObjects\Response;
class RuleEvaluationResult
{
public bool $success = true;
public array $messages = [];
public function __construct(bool $success = true, array $messages = [])
{
$this->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;
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Addresses;
use App\Classes\Modules\Addresses\ControllersLogic\ListStatesLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListStatesController
{
/**
* @param Request $request
* @param ListStatesLogic $logic
* @return JsonResponse
*/
public function list(Request $request, ListStatesLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Bookings;
use App\Classes\Modules\Bookings\ControllersLogic\RegenerateInvoiceBookingLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class RegenerateBookingEInvoiceController
{
/**
* @param Request $request
* @param RegenerateInvoiceBookingLogic $logic
* @return JsonResponse
*/
public function regenerate(Request $request, RegenerateInvoiceBookingLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Bookings;
use App\Classes\Modules\Bookings\ControllersLogic\RegenerateBookingPaymentRVLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class RegenerateBookingPaymentRVController
{
/**
* @param Request $request
* @param RegenerateBookingPaymentRVLogic $logic
* @return JsonResponse
*/
public function regenerate(Request $request, RegenerateBookingPaymentRVLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -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);
}
}
/**
* @param Request $request
* @param UpdateBookingAmountOnHoldLogic $logic
* @return JsonResponse
*/
public function updateOnHold(Request $request, UpdateBookingAmountOnHoldLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Companies;
use App\Classes\Modules\Companies\ControllersLogic\FetchCompanyEInvoiceInfoLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class FetchCompanyEInvoiceInfoController
{
/**
* @param Request $request
* @param FetchCompanyEInvoiceInfoLogic $logic
* @return JsonResponse
*/
public function fetch(Request $request, FetchCompanyEInvoiceInfoLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Companies;
use App\Classes\Modules\Companies\ControllersLogic\UpdateCompanyDetailsLogic;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpdateCompanyDetailsController extends Controller
{
/**
* @param Request $request
* @param UpdateCompanyDetailsLogic $logic
* @return JsonResponse
*/
public function update(Request $request, UpdateCompanyDetailsLogic $logic) : JsonResponse
{
return $logic->execute($request);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Companies;
use App\Classes\Modules\Companies\ControllersLogic\UpdateCompanyEInvoiceInfoLogic;
use App\Classes\Modules\Companies\ControllersLogic\UpdateCompanyEInvoiceRequestLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpdateCompanyEInvoiceInfoController
{
/**
* @param Request $request
* @param UpdateCompanyEInvoiceInfoLogic $logic
* @return JsonResponse
*/
public function updateInfo(Request $request, UpdateCompanyEInvoiceInfoLogic $logic): JsonResponse {
return $logic->execute($request);
}
/**
* @param Request $request
* @param UpdateCompanyEInvoiceRequestLogic $logic
* @return JsonResponse
*/
public function updateRequest(Request $request, UpdateCompanyEInvoiceRequestLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -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;
}
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Remarks;
use App\Classes\ValueObjects\Constants\RemarkRefundReason;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListRefundRemarksController
{
/**
* @param Request $request
* @return JsonResponse
*/
public function list(Request $request): JsonResponse {
// $allReasons = RemarkRefundReason::REFUND_REASONS;
// $returnArray = [];
// foreach ($allReasons as $reason => $category) {
// $returnArray[] = [
// 'name' => $reason
// ];
// }
// $row['payload']["data"] = $returnArray;
$allReasons = RemarkRefundReason::REFUND_REASONS;
$row['payload']['data'] = array_keys($allReasons);
return response()->json($row);
}
}
@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Rules;
use App\Classes\Modules\Rules\ControllersLogic\CheckEInvoiceRuleLogic;
use App\Classes\Modules\Rules\ControllersLogic\CheckPurchaseOrderRuleLogic;
use App\Classes\Modules\Rules\ControllersLogic\CheckTransferRuleLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CheckRuleController
{
/**
* @param Request $request
* @param CheckEInvoiceRuleLogic $logic
* @return JsonResponse
*/
public function checkEInvoiceRule(Request $request, CheckEInvoiceRuleLogic $logic): JsonResponse {
return $logic->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);
}
}
@@ -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);
}
}
@@ -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);
}
}
+1
View File
@@ -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
];
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use App\Classes\ValueObjects\Constants\RoleTypes;
use Closure;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
class EnsureUserIsAdmin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$user = JWTAuth::parseToken()->authenticate();
if(!in_array($user->type, RoleTypes::ADMIN_ROLES)){
return response()->view('errors.503', [], 503);
}
return $next($request);
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class AddressEInvoiceResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $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
];
}
}
+14 -1
View File
@@ -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,
];
}
}
+4
View File
@@ -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(), []),
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class EInvoiceInfoResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $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,
];
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class RuleResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'isPassed' => $this->success,
'messages' => $this->messages,
];
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class StateResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'state' => $this->name,
];
}
}
@@ -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())),
+7 -3
View File
@@ -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){
+10 -1
View File
@@ -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');
}
}
+1
View File
@@ -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",
+6
View File
@@ -0,0 +1,6 @@
<?php
return [
'title' => 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."),
];
+5
View File
@@ -0,0 +1,5 @@
<?php
return [
'qr_code_img_url' => 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=',
];
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddEinvoiceToAddressesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('addresses', function (Blueprint $table) {
$table->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');
});
}
}
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTinToCompaniesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table) {
$table->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');
});
}
}
@@ -0,0 +1,258 @@
<template>
<div class="row p-t-25 text-left">
<div class="col bg-white padding-40 b-rad-lg">
<div class="row" v-if="step === 1">
<div class="col">
<div class="row">
<div class="col">
<div class="row m-b-15">
<div class="col text-danger">
Please check carefully, you will not be able to edit this after confirm
</div>
</div>
<div class="row m-b-15">
<div class="col-6 p-r-5">
<validation-wrapper-component :validator="$v.parameters.tin">
<label>TIN</label>
<input class="form-control" name="tin" v-model="parameters.tin">
</validation-wrapper-component>
</div>
<div class="col-6 p-l-5">
<validation-wrapper-component :validator="$v.parameters.msic_code">
<label>
MSIC Code
<a href="https://sdk.myinvois.hasil.gov.my/codes/msic-codes/" target="_blank" rel="noopener noreferrer">
(View List)
</a>
</label>
<input class="form-control" name="msic_code" v-model="parameters.msic_code">
</validation-wrapper-component>
</div>
</div>
<div class="row m-b-15">
<div class="col">
<validation-wrapper-component :validator="$v.parameters.street_one">
<label>Billing Address Line 1</label>
<input class="form-control" name="street_one" v-model="parameters.street_one">
</validation-wrapper-component>
</div>
</div>
<div class="row m-b-15">
<div class="col">
<validation-wrapper-component :validator="$v.parameters.street_two">
<label>Billing Address Line 2</label>
<input class="form-control" name="street_two" v-model="parameters.street_two">
</validation-wrapper-component>
</div>
</div>
<div class="row m-b-15">
<div class="col-7 p-r-5">
<validation-wrapper-component selectable :validator="$v.parameters.district_id">
<label>District</label>
<selectable-component :endpoint="route('api.address.district.list')" section="districtListSection" valueColumn="id" :labelColumn="['city']" v-model="parameters.district_id"></selectable-component>
</validation-wrapper-component>
</div>
<div class="col-5 p-l-5">
<validation-wrapper-component :validator="$v.parameters.post_code" v-money="integer">
<label>Post Code</label>
<input class="form-control" name="post_code" v-model="parameters.post_code">
</validation-wrapper-component>
</div>
</div>
<div class="row m-b-15">
<div class="col-6 p-r-5">
<validation-wrapper-component selectable :validator="$v.parameters.state_id">
<label>State</label>
<selectable-component :endpoint="route('api.address.state.list')" section="stateListSection" valueColumn="id" :labelColumn="['state']" v-model="parameters.state_id"></selectable-component>
</validation-wrapper-component>
</div>
</div>
</div>
</div>
<div class="row m-b-15">
<div class="col">
<div class="row">
<div class="col text-right">
<button type="button"
class="btn btn-success btn-block b-rad-none"
@click="updateStep(2)">Update E-Invoice Info</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" v-if="step === 2">
<div class="col">
<div class="row m-b-15">
<div class="col">
<div class="row m-b-15">
<div class="col text-danger">
Please check carefully, you will not be able to edit this after confirm
</div>
</div>
<div class="row">
<div class="col">
<div class="padding-15 bg-master-lightest">
<p class="muted">TIN</p>
<p class="m-b-0">{{parameters.tin}}</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="padding-15 bg-master-lightest" v-if="parameters.msic_code !== 0">
<p class="muted">MSIC Code</p>
<p class="m-b-0">{{parameters.msic_code}}</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="padding-15 bg-master-lightest">
<p class="muted">Billing Address</p>
<p class="m-b-0">{{parameters.street_one}} {{parameters.street_two}}, {{districts[parseFloat(parameters.district_id) - 1].city}} {{parameters.post_code}} {{states[parseFloat(parameters.state_id) - 1].state}}, {{districts[parseFloat(parameters.district_id) - 1].country.name}}</p>
</div>
</div>
</div>
</div>
</div>
<div class="row m-b-15">
<div class="col">
<div class="row">
<div class="col-auto p-r-5">
<button type="button" class="btn bg-master-lighter b-rad-none" @click="updateStep(1)">Edit E-Invoice Info</button>
</div>
<div class="col p-l-0">
<button type="button" class="btn btn-success btn-block b-rad-none" @click="submitForm()">Confirm E-Invoice Info</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
import { required, minLength, maxLength, alphaNum, helpers } from 'vuelidate/lib/validators'
function mustContainLetterAndNumber(value) {
if (!value) return true;
const hasLetter = /[a-zA-Z]/.test(value);
const hasNumber = /\d/.test(value);
return hasLetter && hasNumber;
}
function notZero(value) {
return value !== 0 && value !== '0' && value !== null && value !== undefined
}
function fiveDigits(value) {
return /^\d{5}$/.test(value)
}
export default {
props: {
companyId: {
type: Number,
required: true
},
companyType: {
type: Number,
required: true
},
},
watch: {
'id': function() {
this.parameters.company_id = this.companyId;
},
// 'eInvoiceData': function() { //cief todo: 90
// this.resetForm();
// this.parameters = {
// company_id: this.companyId,
// street_one: this.eInvoiceData.street_one,
// street_two: this.eInvoiceData.street_two,
// district_id: this.eInvoiceData.district.id,
// state_id: this.eInvoiceData.state.id,
// post_code: this.eInvoiceData.post_code,
// tin: this.eInvoiceData.tin,
// msic_code: this.eInvoiceData.msic_code,
// }
// }
},
computed: {
districts () { //cief todo: 90
return this.$store.getters.getSelectableList('original_districtListSection');
},
states () {
return this.$store.getters.getSelectableList('original_stateListSection');
}
},
data() {
return {
step: 1,
parameters : {
company_id: this.companyId,
street_one: '',
street_two: '',
district_id: '',
state_id: '',
post_code: 0,
tin: '',
msic_code: 0,
}
}
},
validations() {
const companyType = this.companyType
return {
parameters: {
street_one: { required },
street_two: {},
district_id: { required },
state_id: { required },
post_code: { notZero, fiveDigits},
tin: {
...(companyType === 0
? {
required,
alphaNum,
minLength: minLength(10), //was 11
maxLength: maxLength(13)
}
: {}),
...(companyType === 1
? {
required,
alphaNum,
minLength: minLength(10),
maxLength: maxLength(13) //was 12
}
: {})
},
msic_code: companyType === 1 ? {
notZero, fiveDigits
}: {},
}
}
},
methods:{
updateStep(step){
if(step === 2){
if(!this.validate()){ return; }
}
this.step = step
},
submitForm(){
this.submit((this.route('api.company.einvoice.info.update')), 'post', this.section, true, true);
},
successHandler(response){
this.$emit('eInvoiceInfoUpdated', response.payload.data);
this.closeModal();
}
},
mixins: [ModalFormHandler],
}
</script>
@@ -0,0 +1,68 @@
<template>
<div class="row p-t-20 text-left">
<div class="col bg-white padding-30 b-rad-lg">
<div class="row">
<div class="col text-center">
<h3>E-Invoice Info</h3>
</div>
</div>
<div class="row" v-if="eInvoiceData && eInvoiceData.district">
<div class="col">
<div class="padding-15 bg-master-lightest m-b-10">
<p class="muted">TIN</p>
<p class="m-b-0">{{ eInvoiceData.tin }}</p>
</div>
<div class="padding-15 bg-master-lightest m-b-10" v-if="eInvoiceData.msic_code !== '0'">
<p class="muted">MSIC Code</p>
<p class="m-b-0">{{ eInvoiceData.msic_code }}</p>
</div>
<div class="padding-15 bg-master-lightest">
<p class="muted">Billing Address</p>
<p class="mb-0">
<a data-toggle="collapse" href="#billingDetails" role="button" aria-expanded="false" aria-controls="billingDetails">
{{eInvoiceData.street_one}} {{eInvoiceData.street_two}}, {{eInvoiceData.district.name}}, {{eInvoiceData.post_code}} {{eInvoiceData.state.name}}, {{eInvoiceData.country.name}}
</a>
</p>
<div class="collapse mt-2" id="billingDetails">
<ul class="list-unstyled mb-0">
<li><strong>Street 1:</strong> {{eInvoiceData.street_one}}</li>
<li><strong>Street 2:</strong> {{eInvoiceData.street_two}}</li>
<li><strong>District:</strong> {{eInvoiceData.district.name}}</li>
<li><strong>Postcode:</strong> {{eInvoiceData.post_code}}</li>
<li><strong>State:</strong> {{eInvoiceData.state.name}}</li>
<li><strong>Country:</strong> {{eInvoiceData.country.name}}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
companyId: {
type: Number,
required: true
}
},
data(){
return {
eInvoiceData: null,
}
},
created(){
this.fetchData();
},
methods: {
fetchData(){
this.submit(route('api.company.einvoice.info', this.companyId), 'get', 'eInvoiceInfoViewOnlySection', false, true)
},
successHandler(response){
this.eInvoiceData = response.payload.data;
}
}
}
</script>
@@ -6,7 +6,7 @@
<div class="row">
<div class="col p-t-10 p-b-10 p-r-0 pointer" @click="clickExpand()" :class="[{'bg-master-lighter': item.status === 1 && item.type !== 6}, {'bg-white': item.status !== 1 && item.status !== 4}, {'bg-warning-lighter': item.status === 7}, {'bg-warning-lighter': item.type === 6}]">
<div class="row m-b-5">
<div class="col-auto">
<div class="col-auto p-r-0">
<div class="font-heading fs-8 muted all-caps">Status</div>
<div class="font-heading fs-10 bold" v-if="item.type === 1" :class="[{'text-danger': item.status === 1 || item.status === 4}, {'text-success': item.status !== 1 && item.status !== 4 && item.status !== 7}, {'text-danger': item.status === 7}]">
{{ 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'}}
</div>
</div>
<div class="col-auto p-l-0">
<div class="col-auto">
<div class="font-heading fs-8 muted all-caps">Payment Amount</div>
<div class="font-heading fs-10 bold">
{{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, ",")}}
</div>
</div>
<div class="col-auto p-l-0" v-if="totalRefunds !== 0">
<div class="col-auto" v-if="totalRefunds !== 0">
<div class="font-heading fs-8 muted all-caps">Refunded Amount</div>
<div class="font-heading fs-10 bold text-danger">
{{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 @@
</div>
</div>
</div>
<div class="col-auto bg-success" v-if="item.receipt_voucher">
<document-file-viewer-component class="h-100" :file="item.receipt_voucher.files[0]">
<template slot="button">
<div class="row align-items-center h-100">
<div class="col p-r-0 p-l-0 text-center">
<i class="fa fa-cloud-download text-white"></i>
<p class="fs-7 text-white bold" style="width: 50px;">Receipt</p>
</div>
</div>
</template>
</document-file-viewer-component>
</div>
<div class="col-auto" v-if="item.status !== 3" :class="[{'bg-master-light': item.status === 1 && item.type !== 6}, {'bg-master-lighter': item.status === 2}, {'bg-warning-lighter': item.status === 7}, {'bg-warning-light': item.type === 6}]">
<div class="row align-items-center h-100" v-if="item.status !== 1 || item.payment_method !== 5">
<div class="col">
<div class="col p-r-0 p-l-0 text-center">
<i class="fa" :class="[{'fa-cloud-download': item.status === 1 || item.status === 2}, {'fa-ban': item.status === 4}, {'muted': item.status === 1 || item.status === 2}, {'text-danger': item.status === 4}]"></i>
<p class="fs-7" style="width: 50px;">Payment Slip</p>
</div>
</div>
<div class="row align-items-center h-100" v-if="item.payment_method === 5 && item.status === 1">
@@ -71,19 +84,19 @@
{{ item.transaction_bill.status === 1 ? 'Processing Payment' : 'Transferred'}}
</div>
</div>
<div class="col-auto p-l-0">
<div class="col-auto">
<div class="font-heading fs-8 muted all-caps">Payment Amount</div>
<div class="font-heading fs-10 bold">
{{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, ",")}}
</div>
</div>
<div class="col-auto p-l-0 text-danger" v-if="totalRefunds !== 0">
<div class="col-auto text-danger" v-if="totalRefunds !== 0">
<div class="font-heading fs-8 muted all-caps">Refunded Amount</div>
<div class="font-heading fs-10 bold">
{{item.original_currency.short_code}} {{(Math.round((totalRefunds + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}
</div>
</div>
<div class="col-auto p-l-0" v-if="$store.getters.isSuperAdmin">
<div class="col-auto" v-if="$store.getters.isSuperAdmin">
<div class="font-heading fs-8 muted all-caps">Service Type</div>
<div class="font-heading fs-10 bold">
{{item.booking.service.name}} ({{item.booking.service.id}})
@@ -114,12 +127,25 @@
</div>
</div>
</div>
<div class="col-auto bg-success m-r-5" v-if="item.receipt_voucher">
<document-file-viewer-component class="h-100" :file="item.receipt_voucher.files[0]">
<template slot="button">
<div class="row align-items-center h-100">
<div class="col p-r-0 p-l-0 text-center">
<i class="fa fa-cloud-download text-white"></i>
<p class="fs-7 text-white bold" style="width: 50px;">Receipt</p>
</div>
</div>
</template>
</document-file-viewer-component>
</div>
<div class="col-auto bg-success" v-if="item.transaction_bill.status === 2 || item.transaction_bill.status === 3">
<document-file-viewer-component class="h-100" :file="item.transaction_bill.documents.files[0]">
<template slot="button">
<div class="row align-items-center h-100">
<div class="col">
<div class="col p-r-0 p-l-0 text-center">
<i class="fa fa-cloud-download text-white"></i>
<p class="fs-7 text-white bold" style="width: 50px;">Payment Slip</p>
</div>
</div>
</template>
@@ -220,6 +246,7 @@
<div class="row">
<div class="col">
<div class="font-heading all-caps fs-10 m-b-5">Your Payment Proof</div>
<!-- NOT Payment Gateway -->
<div class="row no-margin" v-if="item.payment_method !== 5">
<div v-if="item.documents != null">
<div v-for="file in item.documents.files" v-bind:key="file.id" class="col-auto no-padding m-r-5">
@@ -233,6 +260,7 @@
</div>
</div>
</div>
<!-- Payment Gateway -->
<div class="row no-margin" v-if="item.payment_method === 5 && (item.status === 2 || item.status === 3)">
<a :href="route('billplz.bill', item.payment_reference)" target="_blank">
<div class="icon-thumbnail fs-11 text-white icon-25 bg-primary btn-rounded float-left m-r-5">
@@ -301,11 +329,32 @@
</div>
</div>
</div>
<div class="row m-t-10" v-show="showEditBookingAmount">
<div class="col">
<button class="btn btn-xs all-caps b-rad-none bg-master-lighter btn-block no-border requestModal" data-type="editBookingAmount">Edit Booking Amount</button>
<!-- <div class="col-auto text-right">
<div class="font-heading fs-12"><i class="fa fa-edit fs-12 pointer fa-fw requestModal" data-type="editBookingAmount" v-if="$store.getters.isSuperAdmin || ($store.getters.isCustomer && $store.getters.getCompanyId === 199)"></i> {{this.data.fixed_currency.short_code}} {{(Math.round((this.data.amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}</div>
</div> -->
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="editBookingAmount">
<!-- <edit-booking-amount-form-component :data="this.data.booking" :section="section"></edit-booking-amount-form-component> -->
<edit-booking-amount-form-v2-component :data="this.data.booking" :section="section"></edit-booking-amount-form-v2-component>
</modal-component>
</div>
</div>
<div class="row m-t-10" v-show="[2, 3].includes(item.status) && (totalRequestedRefund + totalRefunds) < data.original_amount">
<div class="col" v-if="$store.getters.isAdmin">
<button class="btn btn-xs all-caps b-rad-none bg-master-lighter btn-block no-border requestModal" data-type="transferSummary">Request Refund</button>
<div class="col">
<button class="btn btn-xs all-caps b-rad-none bg-master-lighter btn-block no-border requestModal" data-type="transferSummary">REFUND / RETURN (CREDIT NOTE)</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="transferSummary" size="large">
<refund-confirmation-component :data="data" :section="section" :totalRefunds="totalRequestedRefund + totalRefunds"></refund-confirmation-component>
<!-- <refund-confirmation-component :data="data" :section="section" :totalRefunds="totalRequestedRefund + totalRefunds"></refund-confirmation-component> -->
<request-credit-note-component :data="data" :section="section" :totalRefunds="totalRequestedRefund + totalRefunds"></request-credit-note-component>
</modal-component>
</div>
</div>
<div class="row m-t-10" v-show="[2, 3].includes(item.status)">
<div class="col" v-if="$store.getters.isAdmin">
<button class="btn btn-xs all-caps b-rad-none bg-master-lighter btn-block no-border requestModal" data-type="regenerateReceiptVoucher">Regenerate Receipt Voucher</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="regenerateReceiptVoucher">
<regenerate-receipt-voucher-component :data="data" :section="section" class="text-center"></regenerate-receipt-voucher-component>
</modal-component>
</div>
</div>
@@ -332,38 +381,47 @@
<div class="col bg-white padding-15">
<div class="b-b b-grey m-b-5" v-for="(refund, index) in data.transaction_refunds">
<div class="row m-b-10 parentContainer">
<div class="col-auto">
<div class="col-3">
<div class="font-heading fs-10 muted all-caps">Created At</div>
<div class="font-heading fs-10">
{{ refund.created_at }}
</div>
</div>
<div class="col-auto">
<div class="col-2">
<div class="font-heading fs-10 muted all-caps">Status</div>
<div class="font-heading fs-10">
<div class="font-heading fs-10 bold" :class="[{'text-warning': refund.status === 1}, {'text-success': refund.status === 2}, {'text-danger': refund.status === 4}]">{{ refund.status === 1 ? 'Pending Verification' : refund.status === 2 ? 'Approved' : 'Rejected'}}</div>
</div>
</div>
<div class="col-auto" v-if="$store.getters.isAdmin">
<span class="btn requestModal no-border" size="large" data-type="chatmodal">
<i class="fa fa-comment-o"></i>
</span>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="chatmodal">
<remark-component :section="section" :data="refund" module_type="Transaction"></remark-component>
</modal-component>
<div class="col-2">
<div class="row m-l-0 m-r-0" v-if="$store.getters.isAdmin">
<span class="btn requestModal no-border" size="large" data-type="chatmodal">
<i class="fa fa-comment-o"></i>
</span>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="chatmodal">
<remark-component :section="section" :data="refund" module_type="Transaction"></remark-component>
</modal-component>
</div>
</div>
<div class="col text-right">
<div :class="['text-right', showDownloadCreditNote && refund.status === 2 ? 'col-3' : 'col-5']">
<div class="font-heading fs-10 muted all-caps">Amount</div>
<div class="font-heading fs-10">
<div class="font-heading fs-10">{{refund.currency.short_code}} {{(Math.round((refund.amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}</div>
</div>
</div>
<div class="col-auto text-right">
<div class="font-heading fs-10 muted all-caps">Amount</div>
<div class="font-heading fs-14 text-success bold">
<div class="font-heading fs-10">{{refund.original_currency.short_code}} {{(Math.round((refund.original_amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}</div>
</div>
</div>
<div class="col-2" v-if="showDownloadCreditNote && refund.status === 2">
<div class="row no-margin justify-content-end">
<div class="font-heading all-caps fs-10 m-b-5 text-right">Credit Note</div>
<a target=_blank @click="downloadCreditNote(refund.id)">
<div class="icon-thumbnail fs-11 text-white icon-25 bg-primary btn-rounded float-left m-r-0 pointer">
<i class="fa fa-file-image-o fs-10"></i>
</div>
</a>
</div>
</div>
</div>
<div class="row m-b-15 text-right parentContainer" v-if="$store.getters.isSuperAdmin && refund.status === 1">
<div class="col">
@@ -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]
}
@@ -0,0 +1,175 @@
<template>
<div class="row zig-zag-top" v-if="refund_reasons">
<div class="col bg-white padding-25">
<div class="row p-b-10">
<div class="col">
<div class="font-heading all-caps bold fs-10">Request Credit Note</div>
</div>
</div>
<div class="row m-b-10">
<div class="col-7">
<div class="row p-l-15">
<div v-for="(method, index) in refundMethods" :key="index"
class="col p-t-20 p-b-20 bg-master-lightest text-center b-grey pointer all-caps"
:class="{ 'bg-complete text-white': method.name === refundMethod.name }"
@click="updateRefundType(method)">
{{ method.name }}
</div>
</div>
</div>
</div>
<div class="row m-r-0 m-b-10" v-if="refundMethod.name === 'Partial Amount'">
<div class="col">
<validation-wrapper-component :validator="$v.refundAmount">
<label>Credit Note Amount</label>
<input class="form-control" name="amount" v-model="refundAmount"
:disabled="refundMethod.name === 'Full Amount'" v-money="moneyV2">
</validation-wrapper-component>
</div>
<div class="col-auto b-r b-t b-b b-grey">
<div class="row h-100 align-items-center">
<div class="col">
<div class="font-heading fs-10 muted">{{ data.booking.fixed_currency.short_code }}</div>
</div>
</div>
</div>
</div>
<div class="row m-t-5 m-b-5" v-if="refundMethod.name === 'Partial Amount'">
<div class="col">
<validation-wrapper-component selectable :validator="$v.refundRemark">
<label>Reason for refund</label>
<select-component :options="refund_reasons" v-model="refundRemark"></select-component>
</validation-wrapper-component>
</div>
</div>
<div class="row m-t-5 m-b-5" v-else>
<div class="col">
<validation-wrapper-component :validator="$v.refundRemark">
<label>Remarks</label>
<input class="form-control" name="amount" v-model="refundRemark" >
</validation-wrapper-component>
</div>
</div>
<div class="row m-t-5 m-b-5" v-if="refundMethod.name === 'Partial Amount' && refundRemark === 'Others'">
<div class="col">
<validation-wrapper-component :validator="$v.refundRemarkOthers">
<label>Reason Others</label>
<input class="form-control" name="amount" v-model="refundRemarkOthers">
</validation-wrapper-component>
</div>
</div>
<div class="row m-t-10 m-b-10">
<div class="col">
<div class="font-heading all-caps fs-10 m-b-5">Paid Amount: {{ paidAmount }}</div>
<div class="font-heading all-caps fs-10 m-b-5" v-if="this.data.refunded_amount > 0">Refunded Amount: {{ (Math.round((this.data.refunded_amount + Number.EPSILON) * 100) / 100).toFixed(2) }}</div>
<div class="font-heading all-caps fs-10 m-b-5">Refund Amount Requested: {{ refundAmount }}</div>
</div>
</div>
<div class="row">
<div class="col-auto">
<button class="btn btn-lg btn-default bg-master-lightest b-rad-none all-caps fs-12"
data-dismiss="modal">Cancel</button>
</div>
<div class="col text-right">
<button class="btn btn-lg btn-success b-rad-none all-caps fs-12" @click="submitForm()">Confirm &
Proceed</button>
</div>
</div>
</div>
</div>
</template>
<script>
import FormHandler from '../../../general/mixins/formHandler';
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
import { requiredIf, maxValue} from "vuelidate/lib/validators";
export default {
props: {
totalRefunds: {
type: Number,
default: 0,
}
},
data() {
return {
refundAmount: (Math.round((this.data.original_amount - this.data.refunded_amount + Number.EPSILON) * 100) / 100).toFixed(2),
refundRemark: '',
refundRemarkOthers: '',
refundMethod: { name: 'Full Amount', status: false },
refundMethods: [
{ name: 'Full Amount' },
{ name: 'Partial Amount' }
],
refund_reasons: [],
validateRefundRemark: false,
}
},
validations() {
return {
refundAmount: {
maxValue: maxValue(this.refundMaxValue)
},
refundRemark: {
required: requiredIf(function () { return this.validateRefundRemark; })
},
refundRemarkOthers: {
required: requiredIf(function () { return this.refundRemark === 'Others'; })
}
}
},
computed: {
refundMaxValue() {
return (Math.round((this.data.original_amount - this.data.refunded_amount + Number.EPSILON) * 100) / 100).toFixed(2);
},
paidAmount() {
return this.data.original_amount;
},
},
created(){
this.fetchRefundReasons();
},
methods: {
fetchRefundReasons(){
this.submit(route('api.remark.list.refund_reasons'), 'get', 'refundReasonListSection', false, false);
},
successHandler(response, section){
if(section === 'refundReasonListSection'){
const reasons = Array.isArray(response.payload.data) ? [...response.payload.data] : [];
if (!this.$store.getters.isAdmin) {
const index = reasons.indexOf("Others");
if (index !== -1) {
reasons.splice(index, 1);
}
}
this.refund_reasons = reasons;
this.validateRefundRemark = true;
}
else{
this.closeModal();
this.formHandler();
}
},
submitForm() {
this.parameters.amount = this.refundAmount;
if (this.refundRemark === 'Others') {
this.parameters.refundRemark = `${this.refundRemarkOthers}`; //`${this.refundRemark}: ${this.refundRemarkOthers}`;
} else {
this.parameters.refundRemark = this.refundRemark;
}
this.submit(this.route('api.booking.refund.create', this.data.booking.id, this.data.id), 'post', this.section, true, true)
},
updateRefundType(refund) {
this.refundMethod = { ...refund, status: !this.refundMethod.status };
if (this.refundMethod.name === 'Full Amount') {
this.refundAmount = this.refundMaxValue;
}
this.refundRemark = '';
this.refundRemarkOthers = '';
},
},
mixins: [FormHandler, ModalFormHandler]
}
</script>
File diff suppressed because one or more lines are too long
@@ -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) {
@@ -0,0 +1,69 @@
<template>
<div class="row">
<div class="col bg-white padding-40 b-rad-lg">
<div class="row">
<div class="col">
<div class="row m-b-10">
<div class="col">
<h3 class="all-caps m-b-5 bold no-margin">Edit Booking Amount</h3>
<p>To ensure both updates take effect, please update the purchase order details with the equivalent booking amount immediately after this update. If not done in sequence, neither update will be applied.</p>
</div>
</div>
<div class="row m-b-10">
<div class="col">
<validation-wrapper-component :validator="$v.amount_to_edit">
<label class="muted">Booking Amount ({{data.fixed_currency.short_code}})</label>
<input type="text" class="form-control" v-model="amount_to_edit" v-money="money">
</validation-wrapper-component>
</div>
</div>
<div class="row m-b-5 animate__animated animate__fadeInUpBig animate__fast" v-if="error">
<div class="col">
<small class="bold fs-10 text-danger">{{error}}</small>
</div>
</div>
<div class="row">
<div class="col p-r-5">
<div class="btn btn-sm btn-default bg-master-lightest btn-block b-rad-none" data-dismiss="modal">Cancel</div>
</div>
<div class="col p-l-5">
<div class="btn btn-sm btn-success btn-block b-rad-none" @click="submitForm()">Update</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { required } from "vuelidate/lib/validators";
import FormHandler from '../../../general/mixins/formHandler';
export default {
data(){
return {
error: '',
amount_to_edit: (Math.round((this.data.paid_amount + Number.EPSILON) * 100) / 100).toFixed(2)
}
},
validations: {
amount_to_edit: { required }
},
// watch: {
// 'data': function() {
// this.amount_to_edit = (Math.round((this.data.paid_amount + Number.EPSILON) * 100) / 100).toFixed(2);
// }
// },
methods: {
submitForm(){
this.parameters = {amount_to_edit : parseFloat((this.amount_to_edit).toString().replace(',', ''))}
this.submit(this.route('api.booking.amount.update', this.data.id), 'put', this.section, true, true)
},
successHandler(){
this.closeModal();
this.formHandler('');
},
},
mixins: [FormHandler]
}
</script>
@@ -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]
}
</script>
</script>
@@ -13,7 +13,7 @@
<div class="row" v-if="!submitted">
<div class="col-auto">
<div class="bg-danger p-l-15 p-r-15 p-t-5 p-b-5 m-b-10">
<p class="m-b-0 text-white fs-12" >Any Purchase Orders that aren't submitted within 60 days will be closed for editing.</p>
<p class="m-b-0 text-white fs-12" >Ensure PO details are fill in correctly NO changes allowed after submission.</p>
</div>
<div class="bg-danger p-l-15 p-r-15 p-t-5 p-b-5" v-if="$store.getters.isAdmin && companySegmentIds.includes(24)">
<p class="m-b-0 text-white fs-12" >Please note that this customer request to manual fill up the PO.</p>
@@ -150,8 +150,15 @@
<div class="col">
<div class="row m-b-10">
<div class="col">
<button id="save-purchase-order" name="save-purchase-order" class="btn btn-xs all-caps b-rad-none btn-primary btn-block" @click="submitForm()">{{(Math.round((poTotal + Number.EPSILON) * 1000) / 1000).toFixed(3) !== (Math.round((data.amount + Number.EPSILON) * 1000) / 1000).toFixed(3) ? 'Save Purchase Order' : 'Save & Confirm'}}</button>
<button id="save-purchase-order" name="save-purchase-order" class="btn btn-xs all-caps b-rad-none btn-primary btn-block requestModal" data-type="submitPOConfirmation">{{(Math.round((poTotal + Number.EPSILON) * 1000) / 1000).toFixed(3) !== (Math.round((data.amount + Number.EPSILON) * 1000) / 1000).toFixed(3) ? 'Save Purchase Order' : 'Save & Confirm'}}</button>
</div>
<modal-component
id="modal-submit-po"
class="animate__animated animate__fast animate__fadeIn"
styleType="fill-in" type="submitPOConfirmation" size="large">
<purchase-order-submit-confirmation-component class="text-center" v-on:confirm-submit="handleConfirmed" :section="section">
</purchase-order-submit-confirmation-component>
</modal-component>
</div>
<div class="row" v-if="(Math.round((poTotal + Number.EPSILON) * 1000) / 1000).toFixed(3) !== data.amount">
<div class="col">
@@ -188,12 +195,13 @@
<div class="col" v-if="submitted">
<div class="alert alert-success padding-15" role="alert">
<div class="font-heading fs-12 all-caps bold m-b-15">Your Purchase Order {{data.purchase_order.status === 1 ? 'is Being Processed for Verification' : 'has been Approved' }}</div>
<div class="row m-b-15">
<div class="row m-b-15" v-if="allowPOEditing">
<div class="col">
<div class="font-heading fs-10">If you would like to still edit your Purchase Order you can do that by clicking on the edit button below, but your purchase order verification request will be reset.</div>
</div>
</div>
<button class="btn btn-xs all-caps b-rad-none btn-default bg-master-lightest w-100" @click="submitted = false">Edit Purchase Order</button>
<button class="btn btn-xs all-caps b-rad-none btn-default bg-master-lightest w-100" @click="submitted = false" v-if="allowPOEditing">Edit Purchase Order</button>
<!-- <button class="btn btn-xs all-caps b-rad-none btn-default bg-master-lightest w-100" @click="submitted = false" >Edit Purchase Order</button> -->
<button class="btn btn-xs all-caps b-rad-none btn-complete w-100 m-t-5" v-if="!data.documents.proforma_invoice && data.outstanding_amount != 0" @click="submit(route('api.booking.proforma.create', data.id), 'post', section, true, true)">Generate Proforma Invoice</button>
</div>
</div>
@@ -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);

Some files were not shown because too many files have changed in this diff Show More