Merge branch 'dillon/160-credit-notes-options' into vapor/development

This commit is contained in:
Dillon Ngo
2026-06-04 11:24:42 +08:00
13 changed files with 292 additions and 13 deletions
@@ -48,6 +48,7 @@ class GenerateCreditNotePdfV2Logic
$transaction = $this->fetchesTransaction->execute(['id' => $request->route('id')]);
$autoCountInvoiceId = '';
$autoCountEInvoiceValidationLink = 'CIEF';
$kvpCreditNoteNormalOptInOverride = $transaction->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE)->latest()->first(); //transaction type 6
if($transaction->type === TransactionType::REFUND){
//Retrieve TransactionType::CREDIT_NOTE
@@ -69,6 +70,7 @@ class GenerateCreditNotePdfV2Logic
if($kvp){
$kvpOwner = $kvp->owner;
if($kvpOwner && $kvpOwner instanceof Transaction && $kvpOwner->type === TransactionType::REFUND){
$kvpCreditNoteNormalOptInOverride = $kvpOwner->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE)->latest()->first(); //transaction type 6
$booking = $kvpOwner->owner->booking;
}
}
@@ -144,6 +146,10 @@ class GenerateCreditNotePdfV2Logic
Log::info('Based on booking created date, E-Credit Note started and company do not wants e-invoice');
$pdfTemplateName = 'pages.pdfs.credit_note_v2';
}
if($kvpCreditNoteNormalOptInOverride && $kvpCreditNoteNormalOptInOverride->value == 1){
$pdfTemplateName = 'pages.pdfs.credit_note_v2';
}
}
else{
Log::info('Based on booking created date, E-Credit Note not yet started. / Not Yet Ready.');
@@ -0,0 +1,101 @@
<?php
namespace App\Classes\Modules\Transactions\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use Illuminate\Http\Request;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Transactions\Services\FetchesTransaction;
use App\Classes\Modules\KeyValuePairs\Services\CreatesKeyValuePair;
use App\Classes\Modules\KeyValuePairs\Services\UpdatesKeyValuePair;
use App\Classes\Modules\KeyValuePairs\DataTransferObjects\KeyValuePairObject;
use App\Classes\Modules\Transactions\Standards\Rules\CanUpdateCreditNote;
use App\Classes\ValueObjects\Constants\KVPKey;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class UpdateCreditNoteOptLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Updated Credit Note',
'message' => 'You have successfully updated this credit note'
];
}
/** @var CanUpdateCreditNote */
private $canUpdateCreditNote;
/** @var FetchesTransaction */
private $fetchesTransaction;
/** @var FetchesCompany */
private $fetchesCompany;
/** @var CreatesKeyValuePair */
private $createsKeyValuePair;
/** @var UpdatesKeyValuePair */
private $updatesKeyValuePair;
/**
* UpdateCreditNoteOptLogic constructor.
* @param FetchesTransaction $fetchesTransaction
* @param FetchesCompany $fetchesCompany
* @param CreatesKeyValuePair $createsKeyValuePair
* @param UpdatesKeyValuePair $updatesKeyValuePair
* @param CanUpdateCreditNote $canUpdateCreditNote
*/
public function __construct(FetchesTransaction $fetchesTransaction, FetchesCompany $fetchesCompany, CreatesKeyValuePair $createsKeyValuePair, UpdatesKeyValuePair $updatesKeyValuePair, CanUpdateCreditNote $canUpdateCreditNote)
{
$this->fetchesTransaction = $fetchesTransaction;
$this->fetchesCompany = $fetchesCompany;
$this->createsKeyValuePair = $createsKeyValuePair;
$this->updatesKeyValuePair = $updatesKeyValuePair;
$this->canUpdateCreditNote = $canUpdateCreditNote;
}
/**
* @param Request $request
* @return string|\Symfony\Component\HttpFoundation\Response
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function logic(Request $request): JsonResponse
{
$this->canUpdateCreditNote->passes();
$transaction = $this->fetchesTransaction->execute(['id' => $request->route('id')]);
$kvp = $transaction->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE)->latest()->first();
$supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]);
if($supplier->e_invoice === 1) {
if($kvp && $kvp->value == 1) {
$this->updateOrCreateKeyValuePair($transaction, KVPKey::CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE, 0);
}
else
{
$this->updateOrCreateKeyValuePair($transaction, KVPKey::CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE, 1);
}
}
return $this->response([]);
}
private function updateOrCreateKeyValuePair($transaction, $key, $value)
{
$keyValuePairObject = new KeyValuePairObject($key, $value);
$metadata = $transaction->attributesKVP()->where('key', $key)->first();
if ($metadata) {
$this->updatesKeyValuePair->execute($metadata, $keyValuePairObject);
} else {
$this->createsKeyValuePair->execute($transaction, $keyValuePairObject);
}
}
}
@@ -234,14 +234,20 @@ class CreateInvoiceTransactionV2Processor
}
$invoice_transaction = null;
if($booking->status === ApprovalStatus::APPROVED){
//NEW INVOICE for non-einvoice user applicable only when booking is completed
$invoice_transaction = $booking->transactions()
->where('type', TransactionType::INVOICE)
->complete()
->latest()
->first();
}
// if($booking->status === ApprovalStatus::APPROVED){
// //NEW INVOICE for non-einvoice user applicable only when booking is completed
// $invoice_transaction = $booking->transactions()
// ->where('type', TransactionType::INVOICE)
// ->complete()
// ->latest()
// ->first();
// }
$invoice_transaction = $booking->transactions()
->whereIn('type', [TransactionType::INVOICE])
->complete()
->latest()
->first();
if(!$invoice_transaction) {
$transaction_object = new TransactionObject(
@@ -0,0 +1,42 @@
<?php
namespace App\Classes\Modules\Transactions\Standards\Rules;
use App\Classes\General\Abstracts\AbstractRule;
use App\Classes\ValueObjects\Constants\RoleTypes;
use Illuminate\Support\Facades\Auth;
class CanUpdateCreditNote extends AbstractRule
{
/**
* @return bool
*/
protected function authorized($object): bool
{
if (in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)) {
return true;
}
return false;
}
/**
* @param $object
* @return bool
* @throws \App\Classes\Exceptions\RequestValidationException
*/
protected function validators($object): bool
{
return true;
}
/**
* @param $object
* @return bool
*/
protected function criteria($object): bool
{
return true;
}
}
@@ -6,6 +6,7 @@ use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Wallet;
use Illuminate\Support\Facades\Log;
class UpdatesWalletBalance extends AbstractUpdateRecord
{
@@ -32,6 +33,15 @@ class UpdatesWalletBalance extends AbstractUpdateRecord
}
$auditBalance = ($topups + $credit) - ($payments + $debit);
Log::info('Wallet audit completed', [
'wallet_id' => $model->id,
'topups' => $topups,
'credit' => $credit,
'payments' => $payments,
'debit' => $debit,
'audit_balance' => $auditBalance,
]);
$model->amount = $auditBalance;
return $this->handler($model);
}
@@ -19,6 +19,7 @@ class KVPKey
public const AUTOCOUNT_EINVOICE_VALIDATION_LINK_CREDIT_NOTE = 'AUTOCOUNT_EINVOICE_VALIDATION_LINK_CN';
public const CREDIT_NOTE_APPROVAL_DATE = 'CREDIT_NOTE_APPROVAL_DATE';
public const CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE = 'CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE';
public const TRANSACTION_MODEL_CLASS = 'App\Models\Transaction';
@@ -0,0 +1,62 @@
<?php
namespace App\Console\Commands\V2;
use App\Classes\Jobs\Commands\V2\CreateInvoiceTransactionV2CommandJob;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\TransactionType;
use Illuminate\Console\Command;
use App\Models\Booking;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Log;
class TestCreateEInvoiceV2Command extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test-create-einvoice-command';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Test Create E-Invoices';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// $bookings = Booking::whereIn('marking', ['644794'])->get();
$bookings = Booking::whereIn('marking', ['507945'])->get();
$count = 0;
foreach ($bookings as $booking) {
CreateInvoiceTransactionV2CommandJob::dispatch($booking);
$count++;
Log::info('Processed: ' . $count);
Log::info('Booking ID: ' . $booking->marking . ' for E-Invoice ');
}
}
}
@@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\Transactions;
use Illuminate\Http\Request;
use App\Classes\Modules\Transactions\ControllersLogic\UpdateCreditNoteOptLogic;
class UpdateCreditNoteOptController
{
public function opt(Request $request, UpdateCreditNoteOptLogic $logic) {
return $logic->execute($request);
}
}
@@ -40,6 +40,8 @@ class TransactionResource extends JsonResource
//Check if Transaction of type PAYMENT has an override for recipient bank - ends
$eInvoice = false;
$manualOptInNormalCreditNote = false;
if($booking && $this->type === TransactionType::REFUND){
$kvp = $this->attributesKVP()->where('key', KVPKey::TRANSACTION_MODEL_CLASS)->first();
if($kvp){
@@ -51,6 +53,12 @@ class TransactionResource extends JsonResource
}
}
}
$kvp = $this->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_NORMAL_OPT_IN_OVERRIDE)->first();
if($kvp){
if($kvp->value == 1){
$manualOptInNormalCreditNote = true;
}
}
}
$days = $this->created_at->endOfDay()->addWeekdays($booking->service_id === 3 ? 3 : 1);
@@ -90,6 +98,7 @@ class TransactionResource extends JsonResource
'bank' => ((int) $this->type === TransactionType::PAYMENT) ? new BankResource($bank) : null, //When a transaction (of type payment) has an override recipient bank details on booking, this is NOT null
'bank_recipient_edited' => $isEditedBankRecipient,
'e_invoice' => $this->when($this->type === TransactionType::REFUND, $eInvoice),
'manual_opt_in_normal_credit_note' => $this->when($this->type === TransactionType::REFUND, $manualOptInNormalCreditNote),
];
}
}
@@ -470,12 +470,37 @@
</modal-component>
</div>
</div>
<div class="row m-b-15 text-right parentContainer" v-if="$store.getters.isSuperAdmin && refund.status === 2">
<div class="row m-b-15 text-right parentContainer" v-if="$store.getters.isAdmin && refund.status === 2">
<div class="col">
<button class="btn btn-xs btn-outline-danger b-rad-none m-r-5 requestModal" data-type="deleteRefund">
<button class="btn btn-xs btn-outline-danger b-rad-none m-r-5 requestModal"
data-type="optInNormalCreditNote" v-if="refund.e_invoice && refund.booking && !refund.manual_opt_in_normal_credit_note">
E-Credit Note to Normal Credit Note
</button>
<button class="btn btn-xs btn-outline-danger b-rad-none m-r-5 requestModal"
data-type="optInNormalCreditNote" v-else-if="refund.e_invoice && refund.booking && refund.manual_opt_in_normal_credit_note">
Restore Default E-Credit Note
</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="optInNormalCreditNote">
<general-confirmation-form-component
:contentText="refund.manual_opt_in_normal_credit_note
? 'Are you sure you want to restore the default E-Credit Note for this refund?'
: 'Are you sure you want to change E-Credit Note to Normal Credit Note for this refund?'"
modalType="confirm"
class="text-center"
:apiRoute="route('api.transaction.refund.opt', refund.id)"
apiMethod="get"
:section="section"
>
</general-confirmation-form-component>
</modal-component>
<button class="btn btn-xs btn-outline-danger b-rad-none m-r-5 requestModal" data-type="deleteRefund" v-if="$store.getters.isSuperAdmin">
<i class="fa fa-times fa-fw"></i>
</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="deleteRefund">
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="deleteRefund" v-if="$store.getters.isSuperAdmin">
<general-confirmation-form-component
contentText="Are you sure you want to delete this refund?"
modalType="delete"
@@ -24,7 +24,7 @@
</div>
<div class="col-12 p-0">
<label :style="minWidth">Contacts</label>
<label><i class="fa fa-envelope text-primary fs-10"></i>&nbsp;{{this.company_details.contact ? this.company_details.contact.email : ""}}</label>
<label><i class="fa fa-envelope text-primary fs-10"></i>&nbsp;{{this.company_details.contact && this.company_details.contact.email ? this.company_details.contact.email : "-"}}</label>
</div>
<div class="col-12 p-0">
<label :style="minWidth">&nbsp;</label>
+1 -1
View File
@@ -683,7 +683,7 @@
</modal-form-component>
</div>
</div>
<list-component section="serviceTypeSection" :endpoint="route('api.service_type.list')">
<list-component section="serviceTypeSection" :endpoint="route('api.service_type.list')" :options="{per_page: 100}">
<template slot="list" slot-scope="{data}">
<service-component :data="data"></service-component>
</template>
+3
View File
@@ -30,6 +30,7 @@ use App\Http\Controllers\Transactions\ApproveBillGroupPaymentVerificationControl
use App\Http\Controllers\Transactions\DeleteBillGroupController;
use App\Http\Controllers\Transactions\CancelBillGroupController;
use App\Http\Controllers\Transactions\CreateBillGroupPaymentTransactionController;
use App\Http\Controllers\Transactions\UpdateCreditNoteOptController;
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'transactions', 'as' => 'transaction.'], function () {
@@ -81,4 +82,6 @@ Route::group(['prefix' => 'transactions', 'as' => 'transaction.'], function () {
// Route::post('/bulk/po', [CreateBulkPurchaseOrderDocumentController::class, 'create'])->name('bulk.po');
});
});
Route::get('/refund/{id}/opt', [UpdateCreditNoteOptController::class, 'opt'])->name('refund.opt');
});