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/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/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 7ce160ac..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; @@ -68,11 +69,11 @@ class CreateInvoiceDocumentProcessor } 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; } 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 @@ +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/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue index ab1524e8..1461d0c6 100644 --- a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue +++ b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue @@ -97,13 +97,6 @@ export default { generateEInvoicesUrl: null, } }, - mounted(){ - switch(this.section) { - case 'paymentsReportSection': - this.parameters.reportType = 'Sales Invoice Report' - break; - } - }, validations: { parameters: { startDate: { @@ -122,6 +115,7 @@ export default { return [ 'Sales Invoice Report', 'Customers Report', + 'AR Credit Note Report', ]; }, handleExportClick(){ @@ -132,6 +126,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/routes/export.php b/routes/export.php index 2a766539..4801bc4e 100644 --- a/routes/export.php +++ b/routes/export.php @@ -12,6 +12,9 @@ Route::group(['prefix' => 'export', 'as' => 'export.', 'namespace' => 'Exports'] Route::group(['prefix' => 'companies', 'as' => 'companies.'], function () { Route::get('/customers-data', [ExportController::class, 'companies'])->name('customers-data'); }); + Route::group(['prefix' => 'transactions', 'as' => 'transactions.'], function () { + Route::get('/ar-credit-note', [ExportController::class, 'arCreditNote'])->name('ar-credit-note'); + }); }); Route::group(['prefix' => 'import', 'as' => 'import.', 'namespace' => 'Imports'], function () {