diff --git a/app/Classes/Jobs/Commands/V2/OneTimeBatchProcessEInvoicesV2CommandJob.php b/app/Classes/Jobs/Commands/V2/OneTimeBatchProcessEInvoicesV2CommandJob.php
new file mode 100644
index 00000000..26783cbe
--- /dev/null
+++ b/app/Classes/Jobs/Commands/V2/OneTimeBatchProcessEInvoicesV2CommandJob.php
@@ -0,0 +1,44 @@
+booking = $booking;
+ }
+
+ public function handle()
+ {
+ Log::info(Carbon::now() . ': Start job - Processing single booking for E-Invoices for July 2025.');
+ $start = new Carbon();
+
+ $isAllowNormalInvoice = true;
+ (App()->make(RegenerateInvoiceBookingProcessor::class))->execute($this->booking, $isAllowNormalInvoice);
+
+ $end = new Carbon();
+ $elapsedTime = $start->diff($end)->format('%H:%I:%S');
+ Log::info(Carbon::now() . ': End job - Processing single booking for E-Invoices for July 2025. ElapsedTime: ' . $elapsedTime . '.');
+ }
+}
diff --git a/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php
index 1b467042..703fe348 100644
--- a/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php
+++ b/app/Classes/Modules/Bookings/ControllersLogic/BatchBookingsGenerateEInvoiceLogic.php
@@ -8,6 +8,7 @@ use App\Classes\Jobs\Commands\V2\ProcessBookingForEInvoiceV2CommandJob;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\Booking;
use App\Classes\Modules\Bookings\Processors\RegenerateInvoiceBookingProcessor;
+use App\Classes\ValueObjects\Constants\KVPKey;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
@@ -27,7 +28,7 @@ class BatchBookingsGenerateEInvoiceLogic extends AbstractControllerLogic
return [
'title' => 'Generate Bookings E-Invoices',
'message' => sprintf(
- 'You have successfully submitted %d booking%s for E-Invoices.',
+ 'You have successfully submitted %d booking%s for E-Invoices processing.',
$this->processedCount,
$this->processedCount === 1 ? '' : 's'
),
@@ -83,10 +84,10 @@ class BatchBookingsGenerateEInvoiceLogic extends AbstractControllerLogic
$bookings = Booking::where('status', ApprovalStatus::COMPLETED)
->whereBetween('created_at', [$startDate, $endDate])
->whereHas('attributesKVP', function (Builder $query) {
- $query->where('key', 'AUTOCOUNT_DOCNO');
+ $query->where('key', KVPKey::AUTOCOUNT_DOCNO);
})
->with(['attributesKVP' => function ($query) {
- $query->where('key', 'AUTOCOUNT_DOCNO');
+ $query->where('key', KVPKey::AUTOCOUNT_DOCNO);
}])
->get();
diff --git a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php
index 39db1708..bf88251f 100644
--- a/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php
+++ b/app/Classes/Modules/Bookings/ControllersLogic/RegenerateInvoiceBookingLogic.php
@@ -69,8 +69,9 @@ class RegenerateInvoiceBookingLogic extends AbstractControllerLogic
'with_transactions' => true
]
);
+ $normalInvoice = $request->input('normal_invoice', false);
- $this->regenerateInvoiceBookingProcessor->execute($booking);
+ $this->regenerateInvoiceBookingProcessor->execute($booking, $normalInvoice);
return $this->resourceResponse(new BookingResource($booking));
}
diff --git a/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php b/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php
index 38d89f08..7b392d08 100644
--- a/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php
+++ b/app/Classes/Modules/Bookings/Processors/RegenerateInvoiceBookingProcessor.php
@@ -14,6 +14,7 @@ use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use App\Models\Transaction;
use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Log;
class RegenerateInvoiceBookingProcessor
{
@@ -48,7 +49,7 @@ class RegenerateInvoiceBookingProcessor
$this->createInvoiceTransactionProcessor = $createInvoiceTransactionProcessor;
}
- public function execute(Booking $booking)
+ public function execute(Booking $booking, bool $isAllowNormalInvoice = false)
{
$this->updatesBookingStatus->execute($booking, ApprovalStatus::APPROVED);
@@ -58,6 +59,8 @@ class RegenerateInvoiceBookingProcessor
->orderBy('created_at', 'asc')
->first();
+ Log::info('RegenerateInvoiceBookingProcessor booking: ' . json_encode($booking->marking));
+
// get the first bill_no
if($firstInvoice){
$firstBillNo = $firstInvoice->bill_no;
@@ -90,10 +93,10 @@ class RegenerateInvoiceBookingProcessor
$this->deletesDocument->execute($row);
}
- $this->createInvoiceTransactionProcessor->execute($booking, $firstBillNo, true);
+ $this->createInvoiceTransactionProcessor->execute($booking, $firstBillNo, true, $isAllowNormalInvoice);
}
else {
- $this->createInvoiceTransactionProcessor->execute($booking);
+ $this->createInvoiceTransactionProcessor->execute($booking, "", false, $isAllowNormalInvoice);
}
}
}
diff --git a/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php b/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php
index 42ce7589..d3f36916 100644
--- a/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php
+++ b/app/Classes/Modules/Bookings/Services/CalculatesBookingPaidAmount.php
@@ -15,4 +15,14 @@ class CalculatesBookingPaidAmount
return $booking->transactions()->payments()->complete()->sum('original_amount');
}
+ public function executeUntilDate(Booking $booking, Carbon $cutOffDate = null){
+ $amount = $booking->transactions()->payments()->complete();
+
+ if($cutOffDate){
+ $amount->where('created_at', '<=', $cutOffDate);
+ }
+
+ return $amount->sum('original_amount');
+ }
+
}
\ No newline at end of file
diff --git a/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php
new file mode 100644
index 00000000..9592cd93
--- /dev/null
+++ b/app/Classes/Modules/Exports/Services/ExportsARCreditNoteReport.php
@@ -0,0 +1,127 @@
+startDate = $startDate ? Carbon::parse($startDate)->startOfDay() : Carbon::now()->subMonths(1);
+ $this->endDate = $endDate ? Carbon::parse($endDate)->endOfDay() : Carbon::now();
+ }
+
+ public function headings(): array
+ {
+ return [
+ 'DocNo',
+ 'DocDate',
+ 'DebtorCode',
+ 'Ref',
+ 'Description',
+ 'Reason',
+ 'DeptNo',
+ 'Qty',
+ 'UnitPrice',
+ 'AccNo',
+ 'submiteinvoice',
+ 'ConsolidatedEinvoice',
+ 'KnockOffDocNo',
+ 'KnockOffAmt',
+ ];
+ }
+
+ /**
+ * @return \Illuminate\Support\Collection|mixed
+ */
+ public function query()
+ {
+ $type = TransactionType::CREDIT_NOTE;
+ $query = Transaction::query();
+
+ $query->where('type', $type);
+ // $query->where('owner_type', Wallet::class);
+ $query->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
+ $query->whereBetween('created_at', [$this->startDate, $this->endDate]);
+
+ return $query;
+ }
+
+ /**
+ * @param Transaction $transaction
+ * @return array
+ */
+ public function map($transaction): array
+ {
+ $booking = null;
+ $autoCountSalesInvoiceId = null;
+ $formattedDocumentDate = null;
+ $refundRemark = null;
+
+ $company = $transaction->owner->owner;
+ $kvps = KeyValuePair::where('value', $transaction->id)
+ ->where('key', 'App\Models\Transaction')
+ ->orderByDesc('created_at')
+ ->get();
+
+ foreach ($kvps as $kvp) {
+ if ($kvp && $kvp->owner && $kvp->owner->owner && $kvp->owner->owner->type === 1) {
+ $refundTransaction = $kvp->owner;
+ $refundRemark = $refundTransaction->remarks && $refundTransaction->remarks->first() ? $refundTransaction->remarks->first()->content : null;
+ $booking = $refundTransaction->owner->booking;
+ if($booking){
+ $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO)->first();
+ if($metadata){
+ $autoCountSalesInvoiceId = $metadata->value;
+ }
+ break;
+ }
+ }
+ }
+
+ $documentDate = $transaction->created_at; //NEW 2025: default in case there is no approval date
+ $metadata = $transaction->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_APPROVAL_DATE)->latest('created_at')->first();
+ if($metadata){
+ $documentDate = Carbon::parse($metadata->value);
+ }
+ if($company->e_invoice === 1){
+ $documentDate = $documentDate->copy()->endOfMonth();
+ }
+ $formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y');
+
+ return [
+ '<>', //DocNo
+ $formattedDocumentDate, //DocDate
+ $company->debtor, //DebtorCode
+ $booking ? $booking->marking : '', //Ref
+ $refundRemark ?? '', //Description
+ $refundRemark ?? '', //Reason
+ 'C', //DeptNo
+ '1', //Qty
+ number_format($transaction->amount, 2), //UnitPrice
+ '511-0000', //AccNo
+ 'F', //submiteinvoice
+ $company->e_invoice ? 'F' : 'T', //ConsolidatedEinvoice
+ $autoCountSalesInvoiceId ?? '', //KnockOffDocNo
+ number_format($transaction->amount, 2), //KnockOffAmt
+ ];
+ }
+}
diff --git a/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php b/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php
index 11e48130..54523cfa 100644
--- a/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php
+++ b/app/Classes/Modules/Exports/Services/ExportsNullDebtors.php
@@ -71,6 +71,8 @@ class ExportsNullDebtors implements FromQuery, WithHeadings, WithHeadingRow, Wit
*/
public function map($company): array
{
+ $employee = $company->employees()->orderBy('id', 'DESC')->first();
+
return [
'<>', // Code
'300-0000', // DebtorControlAcc
@@ -89,7 +91,7 @@ class ExportsNullDebtors implements FromQuery, WithHeadings, WithHeadingRow, Wit
'', // DeliverAddr2
'', // DeliverAddr3
'', // DeliverPostCode
- '', // EmailAddress
+ $employee->email, // EmailAddress
'', // Attention
'', // Phone1
'', // Phone2
diff --git a/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php b/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php
index a199f75d..c9b43488 100644
--- a/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php
+++ b/app/Classes/Modules/Imports/ControllersLogic/ImportExcelLogic.php
@@ -10,6 +10,7 @@ use App\Classes\Modules\Accounts\Services\UpdatesKeyValuePair;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
+use App\Classes\ValueObjects\Constants\KVPKey;
use App\Models\Booking;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -127,11 +128,13 @@ class ImportExcelLogic extends AbstractControllerLogic
]);
$booking = Booking::where('marking', $ref)->first();
- if($docNo != "<>"){
- $this->updateOrCreateKeyValuePair($booking, "AUTOCOUNT_DOCNO", $docNo);
- }
- if($eInvoiceValidationLink){
- $this->updateOrCreateKeyValuePair($booking, "AUTOCOUNT_EINVOICE_VALIDATION_LINK", $eInvoiceValidationLink);
+ if($booking){
+ if($docNo != "" && $docNo != "<>"){
+ $this->updateOrCreateKeyValuePair($booking, KVPKey::AUTOCOUNT_DOCNO, $docNo);
+ }
+ if($eInvoiceValidationLink){
+ $this->updateOrCreateKeyValuePair($booking, KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK, $eInvoiceValidationLink);
+ }
}
}
}
diff --git a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php
index 824d5528..7168a175 100644
--- a/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php
+++ b/app/Classes/Modules/Transactions/ControllersLogic/GenerateCreditNotePdfV2Logic.php
@@ -10,6 +10,7 @@ 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\Classes\ValueObjects\Constants\KVPKey;
use App\Models\KeyValuePair;
use App\Models\Transaction;
use Carbon\Carbon;
@@ -49,9 +50,9 @@ class GenerateCreditNotePdfV2Logic
if($transaction->type === TransactionType::REFUND){
//Retrieve TransactionType::CREDIT_NOTE
$booking = $transaction->owner->booking;
- $kvp = $transaction->attributesKVP()->latest()->first();
+ $kvp = $transaction->attributesKVP()->where('key', KVPKey::TRANSACTION_MODEL_CLASS)->latest()->first();
if($kvp){
- if($kvp->key === 'App\Models\Transaction'){
+ if($kvp->key === KVPKey::TRANSACTION_MODEL_CLASS){
$transaction = $this->fetchesTransaction->execute(['id' => $kvp->value ]);
}
}
@@ -62,7 +63,7 @@ class GenerateCreditNotePdfV2Logic
$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();
+ $kvp = KeyValuePair::where('key', KVPKey::TRANSACTION_MODEL_CLASS)->where('value', $transaction->id)->first();
if($kvp){
$kvpOwner = $kvp->owner;
if($kvpOwner && $kvpOwner instanceof Transaction && $kvpOwner->type === TransactionType::REFUND){
@@ -71,7 +72,12 @@ class GenerateCreditNotePdfV2Logic
}
}
- $date = $transaction->created_at;
+ $date = $transaction->created_at; //NEW 2025: default in case there is no approval date
+ $metadata = $transaction->attributesKVP()->where('key', KVPKey::CREDIT_NOTE_APPROVAL_DATE)->latest('created_at')->first();
+ if($metadata){
+ $date = Carbon::parse($metadata->value);
+ }
+
$supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]);
$brn = $supplier->documents->where('document_type', DocumentType::SSM_REGISTRATION)->first();
@@ -82,13 +88,34 @@ class GenerateCreditNotePdfV2Logic
if ($bookingCreatedDate->isAfter($eInvoiceStartDate)) {
$eInvoiceStarted = true;
}
- // $eInvoiceStarted = false; //cief todo: 90 - for testing
+
+ if($eInvoiceStarted) {
+ $eInvoiceStarted = false; //reset to re-evaluate second time
+ $autoCountInvoiceId = '';
+ $autoCountEInvoiceValidationLink = '';
+
+ $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO)->first();
+ if($metadata){
+ $autoCountInvoiceId = $metadata->value;
+ }
+ $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK)->first();
+ if($metadata){
+ $autoCountEInvoiceValidationLink = $metadata->value;
+ }
+
+ Log::info('autoCountInvoiceId: ' . $autoCountInvoiceId);
+ Log::info('autoCountEInvoiceValidationLink: ' . $autoCountEInvoiceValidationLink);
+
+ if($autoCountInvoiceId && $autoCountEInvoiceValidationLink){
+ $eInvoiceStarted = true;
+ }
+ }
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();
+ $date = $date->copy()->endOfMonth();
$pdfTemplateName = 'pages.pdfs.e_credit_note';
}
else{
@@ -97,7 +124,7 @@ class GenerateCreditNotePdfV2Logic
}
}
else{
- Log::info('Based on booking created date, E-Credit Note not yet started');
+ Log::info('Based on booking created date, E-Credit Note not yet started. / Not Yet Ready.');
}
$pdf = LaravelMpdf::loadView($pdfTemplateName, ['transaction' => $transaction, 'booking' => $booking, 'supplier' => $supplier, 'date' => $date, 'brn' => $brn,]);
diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php
index 80f963ca..8f36ea91 100644
--- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php
+++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php
@@ -7,6 +7,7 @@ use App\Classes\Modules\Documents\Services\CreatesFiles;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\DocumentType;
+use App\Classes\ValueObjects\Constants\KVPKey;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use App\Models\Document;
@@ -43,7 +44,7 @@ class CreateInvoiceDocumentProcessor
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
- public function execute($transaction, $purchaseOrder, $supplier, $document_type, $voucherRedemption = null)
+ public function execute($transaction, $purchaseOrder, $supplier, $document_type, $voucherRedemption = null, $isAllowNormalInvoice = false)
{
// calculate current Paid Amount
$booking = $transaction->owner_type == Booking::class ? $transaction->owner : null;
@@ -62,17 +63,17 @@ class CreateInvoiceDocumentProcessor
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(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){ //cief todo: 90 - Batch generate E-Invoice date incorrect
+ // $documentDate = $booking->updated_at;
+ // }
}
if($document_type === DocumentType::EINVOICE){
- $metadata = $booking->attributesKVP()->where('key', 'AUTOCOUNT_DOCNO')->first();
+ $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_DOCNO)->first();
if($metadata){
$autoCountInvoiceId = $metadata->value;
}
- $metadata = $booking->attributesKVP()->where('key', 'AUTOCOUNT_EINVOICE_VALIDATION_LINK')->first();
+ $metadata = $booking->attributesKVP()->where('key', KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK)->first();
if($metadata){
$autoCountEInvoiceValidationLink = $metadata->value;
}
@@ -87,6 +88,12 @@ class CreateInvoiceDocumentProcessor
$lowercaseDocumentType = strtolower($document_type);
+ if($document_type === DocumentType::EINVOICE){ //July 2025 workaround generate normal invoice instead of E-Invoice
+ if($isAllowNormalInvoice){
+ $lowercaseDocumentType = strtolower(DocumentType::INVOICE);
+ }
+ }
+
$order_pdf = LaravelMpdf::loadView('pages.pdfs.' . $lowercaseDocumentType,
[
'transaction' => $transaction,
diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php
index e8c7ff21..44f0dbbf 100644
--- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php
+++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceTransactionProcessor.php
@@ -21,6 +21,7 @@ use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\Booking;
use App\Models\SegmentConstant;
use Carbon\Carbon;
+use Illuminate\Support\Facades\Log;
class CreateInvoiceTransactionProcessor
{
@@ -89,7 +90,7 @@ class CreateInvoiceTransactionProcessor
* @return void
* @throws MalformedRequestException
*/
- public function execute(Booking $booking, String $invoiceNo= "", bool $isAllowEInvoice = false)
+ public function execute(Booking $booking, String $invoiceNo= "", bool $isAllowEInvoice = false, bool $isAllowNormalInvoice = false)
{
if ($booking->status === ApprovalStatus::COMPLETED) {
@@ -137,11 +138,18 @@ class CreateInvoiceTransactionProcessor
}
// $eInvoice = true; //cief todo: 90 - for testing
+ if($isAllowNormalInvoice){
+ $invoiceNo = ""; //July 2025 workaround generate normal invoice instead of E-Invoice
+ }
+
if($invoiceNo){
$billNumber = $invoiceNo;
}
else{
$billNUmberPrefix = $eInvoice ? 'EINV-' : 'INV-';
+ if($isAllowNormalInvoice){
+ $billNUmberPrefix = 'INV-'; //July 2025 workaround generate normal invoice instead of E-Invoice
+ }
$billNumber = $this->generatesTransactionBillNumber->execute($billNUmberPrefix);
}
@@ -189,7 +197,7 @@ class CreateInvoiceTransactionProcessor
if ($eInvoice)
{
if($isAllowEInvoice){
- $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::EINVOICE, $voucherRedemption);
+ $this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::EINVOICE, $voucherRedemption, $isAllowNormalInvoice);
}
}
// invoice
diff --git a/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php b/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php
index b0fd0e1c..6e9b33fc 100644
--- a/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php
+++ b/app/Classes/Modules/Wallets/Processors/CreditWalletProcessor.php
@@ -16,6 +16,8 @@ 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\Classes\Modules\Accounts\Services\UpdatesKeyValuePair;
+use App\Classes\ValueObjects\Constants\KVPKey;
use App\Models\Transaction;
class CreditWalletProcessor
@@ -38,6 +40,10 @@ class CreditWalletProcessor
/** @var CreatesKeyValuePair */
private $createsKeyValuePair;
+ /** @var UpdatesKeyValuePair */
+ private $updatesKeyValuePair;
+
+
/**
* CreateWalletLogic constructor.
* @param GeneratesWalletCode $generatesWalletCode
@@ -46,6 +52,7 @@ class CreditWalletProcessor
* @param CreatesTransaction $createsTransaction
* @param UpdatesWallet $updatesWallet
* @param CreatesKeyValuePair $createsKeyValuePair
+ * @param UpdatesKeyValuePair $updatesKeyValuePair
*/
public function __construct(
GeneratesWalletCode $generatesWalletCode,
@@ -53,7 +60,8 @@ class CreditWalletProcessor
GeneratesTransactionBillNumber $generatesTransactionBillNumber,
CreatesTransaction $createsTransaction,
UpdatesWallet $updatesWallet,
- CreatesKeyValuePair $createsKeyValuePair
+ CreatesKeyValuePair $createsKeyValuePair,
+ UpdatesKeyValuePair $updatesKeyValuePair
)
{
$this->generatesWalletCode = $generatesWalletCode;
@@ -62,6 +70,7 @@ class CreditWalletProcessor
$this->createsTransaction = $createsTransaction;
$this->updatesWallet = $updatesWallet;
$this->createsKeyValuePair = $createsKeyValuePair;
+ $this->updatesKeyValuePair = $updatesKeyValuePair;
}
@@ -86,9 +95,15 @@ class CreditWalletProcessor
$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);
+ $status = ApprovalStatus::APPROVED;
+
+ $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, $status, [], $reference);
$transaction = $this->createsTransaction->execute($wallet, $transaction_object);
+ if($status === ApprovalStatus::APPROVED){
+ $this->updateOrCreateKeyValuePair($transaction, KVPKey::CREDIT_NOTE_APPROVAL_DATE, now());
+ }
+
$updateWalletAmount = $transactionType === 2 ? ($wallet->amount - $transaction->amount) : ($wallet->amount + $transaction->amount);
$walletObject = new WalletObject($wallet->owner->id, $wallet->currency_id, $wallet->code, $updateWalletAmount);
@@ -107,4 +122,16 @@ class CreditWalletProcessor
}
return $wallet;
}
+
+ 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);
+ }
+ }
}
diff --git a/app/Classes/ValueObjects/Constants/KVPKey.php b/app/Classes/ValueObjects/Constants/KVPKey.php
new file mode 100644
index 00000000..a15f7d91
--- /dev/null
+++ b/app/Classes/ValueObjects/Constants/KVPKey.php
@@ -0,0 +1,17 @@
+startOfDay();
+ $endDate = Carbon::create(2025, 7, 31)->endOfDay();
+
+ $bookings = Booking::where('status', ApprovalStatus::COMPLETED)
+ ->whereBetween('created_at', [$startDate, $endDate])
+ ->get();
+
+ $count = 0;
+ foreach ($bookings as $booking) {
+ $firstInvoice = $booking->transactions()
+ ->whereIn('type', [TransactionType::INVOICE])
+ ->withTrashed()
+ ->whereBetween('created_at', [$startDate, $endDate])
+ ->orderBy('created_at', 'asc')
+ ->first();
+
+ $normalInvoice = $booking->documents()->where('document_type', DocumentType::INVOICE)->first();
+ $eInvoice = $booking->documents()->where('document_type', DocumentType::EINVOICE)->first();
+
+ // if($firstInvoice && !$normalInvoice && !$eInvoice){
+ if(!$firstInvoice && !$normalInvoice && !$eInvoice){
+ OneTimeBatchProcessEInvoicesV2CommandJob::dispatch($booking);
+ $count++;
+ Log::info('Processed: ' . $count);
+ Log::info('Booking ID: ' . $booking->marking . ' | created_at: ' . $booking->created_at);
+ }
+ }
+ }
+}
diff --git a/app/Http/Controllers/Exports/ExportController.php b/app/Http/Controllers/Exports/ExportController.php
index ccf5ffd5..3b4836a3 100644
--- a/app/Http/Controllers/Exports/ExportController.php
+++ b/app/Http/Controllers/Exports/ExportController.php
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
use Maatwebsite\Excel\Excel;
use Illuminate\Support\Facades\Storage;
use App\Classes\General\AWSS3Helper;
+use App\Classes\Modules\Exports\Services\ExportsARCreditNoteReport;
use App\Classes\Modules\Exports\Services\ExportsCompanies;
use Carbon\Carbon;
@@ -83,4 +84,39 @@ class ExportController
}
return $response;
}
+
+ public function arCreditNote(Request $request){
+ $validated = $request->validate([
+ 'startDate' => 'nullable|date_format:d-m-Y',
+ 'endDate' => 'nullable|date_format:d-m-Y|after_or_equal:startDate',
+ ]);
+
+ $startDate = null;
+ $endDate = null;
+
+ if (isset($validated['startDate']) && $validated['startDate']) {
+ $startDate = Carbon::createFromFormat('d-m-Y', $validated['startDate'])->startOfDay();
+ } else {
+ $startDate = Carbon::now()->subMonths(1)->startOfDay();
+ }
+
+ if (isset($validated['endDate']) && $validated['endDate']) {
+ $endDate = Carbon::createFromFormat('d-m-Y', $validated['endDate'])->endOfDay();
+ } else {
+ $endDate = Carbon::now()->endOfDay();
+ }
+
+ $exportsTransactions = new ExportsARCreditNoteReport($startDate, $endDate);
+
+ $exportFileName = 'Exchange - AR Credit Note Report.xls';
+ $filesystemDriver = Storage::getDefaultDriver();
+ if($filesystemDriver === 's3'){
+ return response([ 'src' => AWSS3Helper::S3Exportable($exportFileName, $exportsTransactions) ]);
+ }
+ else{
+ $response = $exportsTransactions->download($exportFileName, Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
+ ob_end_clean();
+ }
+ return $response;
+ }
}
diff --git a/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php
new file mode 100644
index 00000000..6266b4a1
--- /dev/null
+++ b/app/Http/Controllers/Reports/UnfinishedPaymentOrders.php
@@ -0,0 +1,155 @@
+input('page', 1);
+ $perPage = 2500;
+ $offset = ($page - 1) * $perPage;
+ $cutOffDate = $request->cut_off_date ? Carbon::parse($request->cut_off_date) : null;
+
+ $baseQuery = Booking::with('company')
+ ->when($cutOffDate, fn($q) => $q->where('created_at', '<=', $cutOffDate))
+ ->orderByDesc('id');
+
+ $total = $baseQuery->count();
+ $bookings = $baseQuery->offset($offset)->limit($perPage)->get();
+
+ $calculator = new CalculatesBookingPaidAmount();
+
+ $filtered = $bookings->filter(function ($b) use ($calculator, $cutOffDate) {
+ $paid = $calculator->executeUntilDate($b, $cutOffDate);
+ $outstanding = $b->fix_amount - $paid;
+ return $paid > 0 && $outstanding > 0;
+ });
+
+ return response()->json([
+ 'success' => true,
+ 'current_page' => $page,
+ 'next_page' => ($offset + $perPage < $total) ? $page + 1 : null,
+ 'count' => $filtered->count(),
+ 'data' => $filtered->map(function ($b) use ($calculator, $cutOffDate) {
+ $paid = $calculator->executeUntilDate($b, $cutOffDate);
+ return [
+ 'id' => $b->id,
+ 'order_ref' => $b->marking ?? $b->id,
+ 'booking_amount' => number_format($b->fix_amount, 2),
+ 'paid_amount' => number_format($paid, 2),
+ 'outstanding_amount' => number_format($b->fix_amount - $paid, 2),
+ 'customer' => optional($b->company)->reference,
+ 'created_at' => $b->created_at->toDateTimeString(),
+ ];
+ })->values(),
+ ]);
+ }
+
+ public function loadView(Request $request)
+ {
+ $cutOffDateString = $request->cut_off_date ?? '';
+ $cutOffDateParsed = $cutOffDateString ? Carbon::parse($cutOffDateString)->toDateString() : '-';
+
+ echo <<Cut Off Date: {$cutOffDateParsed}
+🔄 Processing... Total 0
+
+
+
+
+
+
+
+ | Order Ref |
+ Booking Amount |
+ Paid Amount |
+ Outstanding Amount |
+ Customer |
+ Order Created Date |
+
+
+
+
+
+
+HTML;
+ }
+}
diff --git a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue
index c28b108e..c1b338a9 100644
--- a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue
+++ b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue
@@ -101,13 +101,6 @@ export default {
generateEInvoicesUrl: null,
}
},
- mounted(){
- switch(this.section) {
- case 'paymentsReportSection':
- this.parameters.reportType = 'Sales Invoice Report'
- break;
- }
- },
validations: {
parameters: {
startDate: {
@@ -126,6 +119,7 @@ export default {
return [
'Sales Invoice Report',
'Customers Report',
+ 'AR Credit Note Report',
];
},
handleExportClick(){
@@ -137,6 +131,7 @@ export default {
const routesMap = {
'Sales Invoice Report': route('api.export.bookings.sales-invoices'),
'Customers Report': route('api.export.companies.customers-data'),
+ 'AR Credit Note Report': route('api.export.transactions.ar-credit-note'),
};
let url = `${routesMap[reportType]}?startDate=${this.parameters.startDate}&endDate=${this.parameters.endDate}`;
diff --git a/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue b/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue
index 6d6d15f9..1d2dd9af 100644
--- a/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue
+++ b/resources/assets/vue/components/bookings/forms/RegenerateEInvoiceComponent.vue
@@ -29,6 +29,7 @@
export default {
methods: {
submitForm() {
+ this.parameters.normal_invoice = false;
this.submit(this.route('api.booking.einvoice.regenerate', this.data.id), 'post', this.section, true, true);
},
successHandler(){
diff --git a/resources/assets/vue/components/bookings/forms/RegenerateNormalInvoiceEInvoiceComponent.vue b/resources/assets/vue/components/bookings/forms/RegenerateNormalInvoiceEInvoiceComponent.vue
new file mode 100644
index 00000000..b3279d3e
--- /dev/null
+++ b/resources/assets/vue/components/bookings/forms/RegenerateNormalInvoiceEInvoiceComponent.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
Are you Sure?
+
Are you sure you want to regenerate normal invoice instead of e-invoice for this payment?
+
+
+
+
+
+
+
+
+
diff --git a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue
index 68dffd3a..c7717536 100644
--- a/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue
+++ b/resources/assets/vue/components/bookings/sections/BookingDetailsSectionComponent.vue
@@ -334,7 +334,7 @@
-
+
+
+
+
Regenerate Normal Inv
+
+
+
+
+
@@ -497,7 +505,7 @@
-
+
Change Booking Owner
@@ -614,7 +622,7 @@