Merge branch 'dillon/90-e-invoice-f-2' into vapor/production

This commit is contained in:
Dillon Ngo
2025-08-06 22:26:19 +08:00
10 changed files with 263 additions and 26 deletions
@@ -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();
@@ -0,0 +1,127 @@
<?php
namespace App\Classes\Modules\Exports\Services;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\KVPKey;
use App\Models\KeyValuePair;
use App\Models\Transaction;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class ExportsARCreditNoteReport implements FromQuery, WithHeadings, WithHeadingRow, WithMapping, ShouldAutoSize
{
use Exportable;
protected $startDate;
protected $endDate;
public function __construct($startDate = null, $endDate = null) {
$this->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 [
'<<New>>', //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
];
}
}
@@ -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 != "<<New>>"){
$this->updateOrCreateKeyValuePair($booking, "AUTOCOUNT_DOCNO", $docNo);
}
if($eInvoiceValidationLink){
$this->updateOrCreateKeyValuePair($booking, "AUTOCOUNT_EINVOICE_VALIDATION_LINK", $eInvoiceValidationLink);
if($booking){
if($docNo != "" && $docNo != "<<New>>"){
$this->updateOrCreateKeyValuePair($booking, KVPKey::AUTOCOUNT_DOCNO, $docNo);
}
if($eInvoiceValidationLink){
$this->updateOrCreateKeyValuePair($booking, KVPKey::AUTOCOUNT_EINVOICE_VALIDATION_LINK, $eInvoiceValidationLink);
}
}
}
}
@@ -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,]);
@@ -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;
}
@@ -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);
}
}
}
@@ -0,0 +1,17 @@
<?php
namespace App\Classes\ValueObjects\Constants;
class KVPKey
{
public const AUTOCOUNT_DOCNO = 'AUTOCOUNT_DOCNO';
public const AUTOCOUNT_EINVOICE_VALIDATION_LINK = 'AUTOCOUNT_EINVOICE_VALIDATION_LINK';
public const CREDIT_NOTE_APPROVAL_DATE = 'CREDIT_NOTE_APPROVAL_DATE';
public const TRANSACTION_MODEL_CLASS = 'App\Models\Transaction';
}
@@ -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;
}
}
@@ -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}`;
+3
View File
@@ -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 () {