From e5a8936b50a0290ab88157298d4a917a825b48cb Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 4 Sep 2025 12:21:43 +0800 Subject: [PATCH 1/4] E-Invoice - PM Requested for August sales invoice export --- docker-setup/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-setup/docker-compose.yml b/docker-setup/docker-compose.yml index 9e33f109..4fd3ec64 100644 --- a/docker-setup/docker-compose.yml +++ b/docker-setup/docker-compose.yml @@ -32,6 +32,7 @@ services: MYSQL_DATABASE: exchange-db MYSQL_USER: master MYSQL_PASSWORD: cDe7gcrRBWetaAP + TZ: Asia/Singapore volumes: - mysql-data:/var/lib/mysql networks: From 299de854ad18d2666676432e6f991625df09ca50 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Sun, 7 Sep 2025 13:50:32 +0800 Subject: [PATCH 2/4] E-Invoice - New Sales Invoice Report (with refund) --- .../ExportsSalesInvoiceWithRefundReport.php | 259 ++++++++++++++++++ .../Controllers/Exports/ExportController.php | 9 +- .../elements/DownloadUploadComponent.vue | 2 + routes/export.php | 1 + 4 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php diff --git a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php new file mode 100644 index 00000000..86fe3f0b --- /dev/null +++ b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php @@ -0,0 +1,259 @@ +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', + 'ShipInfo', + 'AccNo', + 'DetailDescription', + 'FurtherDescription', + 'Classification', + 'DeptNo', + 'Qty', + 'UnitPrice', + 'submiteinvoice', + 'ConsolidatedEinvoice', + ]; + } + + /** + * @return \Illuminate\Support\Collection|mixed + */ + public function query() + { + $startDate = $this->startDate; + $endDate = $this->endDate; + + Log::info('ExportsSalesInvoiceWithRefundReport startDate: ' . $startDate->format('Y-m-d H:i:s')); + Log::info('ExportsSalesInvoiceWithRefundReport endDate: ' . $endDate->format('Y-m-d H:i:s')); + + return Booking::whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED, ApprovalStatus::SUSPENDED]) + ->whereHas('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->where('status', ApprovalStatus::REFUNDED) + ->whereBetween('created_at', [$startDate, $endDate]) + ->latest('created_at'); + }); + } + + /** + * @param Booking $booking + * @return array + */ + public function map($booking): array + { + Log::info('ExportsSalesInvoiceWithRefundReport booking : ' . json_encode($booking)); + $records = []; + $averageCurrencyRate = 0; + $currencyId = 0; + + $purchaseOrder = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first(); + $company = $booking->company()->first(); + + // $lastPaymentTransaction = $booking->transactions()->payments()->complete()->latest()->first(); + $lastPaymentTransaction = $booking->transactions()->payments()->where('status', ApprovalStatus::REFUNDED)->latest()->first(); + if(!$lastPaymentTransaction){ + return $records; + } + + $documentDate = $lastPaymentTransaction->created_at; + if ($documentDate < $this->startDate || $documentDate > $this->endDate) { + return $records; + } + + if($company->e_invoice === 1){ + $documentDate = $documentDate->copy()->endOfMonth(); + } + + $invoiceTransaction = $booking->transactions()->where('type', TransactionType::INVOICE)->complete()->first(); + // $invoiceTransaction = $booking->transactions()->where('type', TransactionType::INVOICE)->first(); + + $currencyId = $booking->fix_currency_id; + + $subtotal = 0; + $displayedSubtotal = 0; + $totalPayment = 0; + $averageCurrencyRate = $invoiceTransaction ? $invoiceTransaction->currency_rate : 0; + $paymentSum = $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->get() + ->sum(function ($transaction) { + return round($transaction->amount, 2); + }); + if ($paymentSum){ + $averageCurrencyRate = $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->get() + ->sum(function ($transaction) { + return $transaction->currency_rate; + }) / $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->count(); + + $refundedAmount = (App()->make(CalculatesBookingRefundAmount::class))->execute($booking, 1); + $refundedServiceCharge = (App()->make(CalculatesBookingRefundServiceCharge::class))->execute($booking, 1); + + $totalPayment = $paymentSum - $refundedAmount - $refundedServiceCharge; + } + + $formattedDocumentDate = Carbon::parse($documentDate)->format('m/d/Y'); + + $firstItem = true; + $transactionDetails = $purchaseOrder ? $purchaseOrder->transactionDetails : null; + if($transactionDetails){ + foreach ($transactionDetails as $detail) { + $displayUnitPrice = 0; + if($averageCurrencyRate && $currencyId){ + $exactUnitPrice = ($currencyId) === 1 ? $detail->price : bcdiv($detail->price, $averageCurrencyRate, 7); + $displayUnitPrice = round($exactUnitPrice, 2); + $itemTotal = bcmul($exactUnitPrice, $detail->quantity, 5); + $displayedItemTotal = round(bcmul($displayUnitPrice, $detail->quantity, 7), 2); + $displayedSubtotal = bcadd($displayedSubtotal, $displayedItemTotal, 2); + $subtotal = bcadd($subtotal, $itemTotal, 5); + } + + + $records[] = [ + $firstItem ? '<>' : '', + $formattedDocumentDate, + $company->debtor, + $booking->marking, + $booking->marking, + '500-0000', + 'PRODUCT NAME :', + $detail->product_name, + '022', + 'C', + $detail->quantity, + $displayUnitPrice ? number_format($displayUnitPrice, 2): 0, + $firstItem ? 'T' : '', + $company->e_invoice ? 'F' : 'T' + ]; + + if($firstItem) { + $firstItem = false; + } + } + + // Service Charge - Starts + $serviceCharge = 0; + if (!$totalPayment && $invoiceTransaction) { + $serviceCharge = $invoiceTransaction->service_charge; + } + else { + $serviceCharge = $booking->transactions() + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->get() + ->sum(function ($transaction) { + return $transaction->service_charge; + }); + } + + $records[] = [ + '', + $formattedDocumentDate, + $company->debtor, + $booking->marking, + $booking->marking, + '500-0000', + 'PRODUCT NAME :', + 'Service Charge', + '022', + 'C', + '1', + $serviceCharge ? number_format($serviceCharge, 2): '0', + '', + $company->e_invoice ? 'F' : 'T' + ]; + // Service Charge - Ends + + // Adjustment - Starts + if($invoiceTransaction){ + $adjustment = 0; + $voucherRedemption = $invoiceTransaction->voucherRedemption; + $voucherDiscount = $voucherRedemption ? bcmul((string)$voucherRedemption->value, "-1", 2) : "0"; + + $displayedSubtotal = is_numeric($displayedSubtotal) ? sprintf('%F', $displayedSubtotal) : '0'; + $serviceCharge = is_numeric($serviceCharge) ? sprintf('%F', $serviceCharge) : '0'; + $tax = is_numeric($invoiceTransaction->tax) ? sprintf('%F', $invoiceTransaction->tax) : '0'; + $voucherDiscount = is_numeric($voucherDiscount) ? sprintf('%F', $voucherDiscount) : '0'; + + $displayedTotal = bcadd( + bcadd( + bcadd($displayedSubtotal, $serviceCharge, 5), + $tax, + 5 + ), + $voucherDiscount, + 5 + ); + + $expectedTotal = bcadd(bcadd(bcadd($subtotal, $serviceCharge, 5), $invoiceTransaction->tax, 5), $voucherDiscount, 5); + $adjustment = bcsub($expectedTotal, $displayedTotal, 5); + + if ($totalPayment) { + $expectedTotal = $totalPayment; + $adjustment = bcsub($expectedTotal, $displayedTotal, 5); + } + + $records[] = [ + '', + $formattedDocumentDate, + $company->debtor, + $booking->marking, + $booking->marking, + '500-0000', + 'PRODUCT NAME :', + 'Adjustment', + '022', + 'C', + '1', + $adjustment ? number_format($adjustment, 2): '0', + '', + $company->e_invoice ? 'F' : 'T' + ]; + } + // Adjustment - Ends + } + else{ + Log::info('ExportsSalesInvoiceWithRefundReport EMPTY RECORD !!!'); + } + return $records; + } +} diff --git a/app/Http/Controllers/Exports/ExportController.php b/app/Http/Controllers/Exports/ExportController.php index 3e482509..a5156d5f 100644 --- a/app/Http/Controllers/Exports/ExportController.php +++ b/app/Http/Controllers/Exports/ExportController.php @@ -3,11 +3,12 @@ namespace App\Http\Controllers\Exports; -use App\Classes\Modules\Exports\Services\ExportsSalesInvoiceReport; use Illuminate\Http\Request; use Maatwebsite\Excel\Excel; use Illuminate\Support\Facades\Storage; use App\Classes\General\AWSS3Helper; +use App\Classes\Modules\Exports\Services\ExportsSalesInvoiceReport; +use App\Classes\Modules\Exports\Services\ExportsSalesInvoiceWithRefundReport; use App\Classes\Modules\Exports\Services\ExportsARCreditNoteReport; use App\Classes\Modules\Exports\Services\ExportsCompanies; use App\Classes\Modules\Exports\Services\ExportsReceivePaymentDepositEntryReport; @@ -22,6 +23,12 @@ class ExportController return $this->handleExport($exporter, 'Exchange - Sales Invoice Report.xls'); } + public function salesInvoicesWithRefund(Request $request){ + [$startDate, $endDate] = $this->getValidatedDates($request); + $exporter = new ExportsSalesInvoiceWithRefundReport($startDate, $endDate); + return $this->handleExport($exporter, 'Exchange - Sales Invoice Report (with refund).xls'); + } + public function companies(Request $request){ [$startDate, $endDate] = $this->getValidatedDates($request); $exporter = new ExportsCompanies($startDate, $endDate); diff --git a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue index c43d73dc..7ebc580b 100644 --- a/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue +++ b/resources/assets/vue/components/bookings/elements/DownloadUploadComponent.vue @@ -138,6 +138,7 @@ export default { options() { return [ 'Sales Invoice Report', + 'Sales Invoice Report (with refund)', 'Customers Report', '01D - RECEIVE PAYMENT (FULL PAYMENT) [AR DEPOSIT ENTRY]', '01R - RECEIVE PAYMENT (FULL PAYMENT) [AR RECEIVE PAYMENT]', @@ -152,6 +153,7 @@ export default { const routesMap = { 'Sales Invoice Report': route('api.export.bookings.sales_invoices'), + 'Sales Invoice Report (with refund)': route('api.export.bookings.sales_invoices_w_refund'), 'Customers Report': route('api.export.companies.customers_data'), '01D - RECEIVE PAYMENT (FULL PAYMENT) [AR DEPOSIT ENTRY]': route('api.export.transactions.receive_payment_deposit_entry'), '01R - RECEIVE PAYMENT (FULL PAYMENT) [AR RECEIVE PAYMENT]': route('api.export.transactions.receive_payment_for_booking'), diff --git a/routes/export.php b/routes/export.php index 4b1c2576..6410117c 100644 --- a/routes/export.php +++ b/routes/export.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Route; Route::group(['prefix' => 'export', 'as' => 'export.', 'namespace' => 'Exports'], function () { Route::group(['prefix' => 'bookings', 'as' => 'bookings.'], function () { Route::get('/sales-invoices', [ExportController::class, 'salesInvoices'])->name('sales_invoices'); + Route::get('/sales-invoices-refund', [ExportController::class, 'salesInvoicesWithRefund'])->name('sales_invoices_w_refund'); }); Route::group(['prefix' => 'companies', 'as' => 'companies.'], function () { Route::get('/customers-data', [ExportController::class, 'companies'])->name('customers_data'); From 96a3f78d4052f0c77dd1e8db52597859725c8a65 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 9 Sep 2025 16:13:03 +0800 Subject: [PATCH 3/4] E-Invoice - New Sales Invoice Report (with refund) --- .../Services/ExportsSalesInvoiceWithRefundReport.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php index 86fe3f0b..3905355e 100644 --- a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php +++ b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php @@ -43,8 +43,8 @@ class ExportsSalesInvoiceWithRefundReport implements FromQuery, WithHeadings, Wi 'DeptNo', 'Qty', 'UnitPrice', - 'submiteinvoice', - 'ConsolidatedEinvoice', + 'SubmitEinvoice', + 'ConsolidatedEInvoice', ]; } @@ -162,7 +162,7 @@ class ExportsSalesInvoiceWithRefundReport implements FromQuery, WithHeadings, Wi $detail->quantity, $displayUnitPrice ? number_format($displayUnitPrice, 2): 0, $firstItem ? 'T' : '', - $company->e_invoice ? 'F' : 'T' + $firstItem ? ($company->e_invoice ? 'F' : 'T') : '' ]; if($firstItem) { @@ -199,7 +199,7 @@ class ExportsSalesInvoiceWithRefundReport implements FromQuery, WithHeadings, Wi '1', $serviceCharge ? number_format($serviceCharge, 2): '0', '', - $company->e_invoice ? 'F' : 'T' + '' ]; // Service Charge - Ends @@ -246,7 +246,7 @@ class ExportsSalesInvoiceWithRefundReport implements FromQuery, WithHeadings, Wi '1', $adjustment ? number_format($adjustment, 2): '0', '', - $company->e_invoice ? 'F' : 'T' + '' ]; } // Adjustment - Ends From 495a2f0bdd5e58445b29eed7133e899cd75783ec Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 10 Sep 2025 11:38:17 +0800 Subject: [PATCH 4/4] E-Invoice - New Sales Invoice Report (with refund) --- .../ExportsSalesInvoiceWithRefundReport.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php index 3905355e..2809fad9 100644 --- a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php +++ b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceWithRefundReport.php @@ -252,7 +252,23 @@ class ExportsSalesInvoiceWithRefundReport implements FromQuery, WithHeadings, Wi // Adjustment - Ends } else{ - Log::info('ExportsSalesInvoiceWithRefundReport EMPTY RECORD !!!'); + Log::info('ExportsSalesInvoiceWithRefundReport EMPTY RECORD for booking marking: ' . $booking->marking); + $records[] = [ + '<>', + $formattedDocumentDate, + $company->debtor, + $booking->marking, + $booking->marking, + '500-0000', + 'PRODUCT NAME :', + 'First Mile Delivery', + '022', + 'C', + '1', + '0', + '', + '' + ]; } return $records; }