E-Invoice - Partial completion of => Doc Delivery - E-INVOICE, E-CN, E-RN + Edit Booking Amount (3.0)

This commit is contained in:
Dillon Ngo
2025-06-08 03:52:14 +08:00
parent 893631064b
commit 6cfa6f8d75
9 changed files with 300 additions and 21 deletions
@@ -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,78 @@
<?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\ValueObjects\Constants\TransactionType;
use App\Http\Resources\BookingResource;
use App\Models\Booking;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
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;
/**
* UpdateBookingAmountWithPOLogic constructor.
* @param FetchesBooking $fetchesBooking
* @param UpdatesBookingFixedAmount $updatesBookingFixedAmount
*/
public function __construct(FetchesBooking $fetchesBooking, UpdatesBookingFixedAmount $updatesBookingFixedAmount)
{
$this->fetchesBooking = $fetchesBooking;
$this->updatesBookingFixedAmount = $updatesBookingFixedAmount;
}
/**
* @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]);
$bookingAttribute = $booking->attributesKVP()->where('key', "BOOKING_AMOUNT_UPDATE")->first();
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));
}
}
@@ -39,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'
];
}
@@ -84,7 +84,8 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic
*/
public function logic(Request $request, $id = '') : JsonResponse
{
if(!in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)){
//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
@@ -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);
}
}
@@ -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);
}
}
+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');
}
}
@@ -336,7 +336,8 @@
<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-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>
@@ -402,8 +403,7 @@
</modal-component>
</div>
</div>
<!-- With Credit Note PDF Download - Start -->
<div class="col-3 text-right" v-if="showDownloadCreditNote && refund.status === 2">
<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>
@@ -422,18 +422,6 @@
</a>
</div>
</div>
<!-- With Credit Note PDF Download - Ends -->
<!-- Without Credit Note PDF Download - Start -->
<div class="col-5 text-right" v-else>
<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 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>
<!-- Without Credit Note PDF Download - Ends -->
</div>
<div class="row m-b-15 text-right parentContainer" v-if="$store.getters.isSuperAdmin && refund.status === 1">
<div class="col">
@@ -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>
+2
View File
@@ -2,6 +2,7 @@
use App\Http\Controllers\Bookings\RegenerateBookingPaymentRVController;
use App\Http\Controllers\Bookings\RegenerateBookingEInvoiceController;
use App\Http\Controllers\Bookings\UpdateBookingAmountController;
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'booking', 'as' => 'booking.', 'namespace' => 'Bookings'], function () {
@@ -36,6 +37,7 @@ Route::group(['prefix' => 'booking', 'as' => 'booking.', 'namespace' => 'Booking
Route::delete('{id}/purchase_order/pdf', 'DeletePurchaseOrderPdfController@delete')->name('po.pdf.delete');
Route::put('{id}/updateAmount', 'UpdateBookingAmountController@update')->name('booking_amount.update');
Route::put('{id}/updateAmountWithPO', [UpdateBookingAmountController::class, 'updateOnHold'])->name('amount.update');
Route::put('{id}/updateOrderReferences', 'UpdateBookingOrderReferenceController@update')->name('booking_order_reference.update');
Route::post('/merge', 'MergeBookingController@merge')->name('merge');