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 RefBooking AmountPaid AmountOutstanding AmountCustomerOrder 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 @@ + + 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 E-Invoice
@@ -342,6 +342,14 @@
+
+
+
Regenerate Normal Inv
+
+ + + +
@@ -497,7 +505,7 @@
-
+
Change Booking Owner
@@ -614,7 +622,7 @@