From ea383dd0065c52095943deb3a8d36bcd17ecf729 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Thu, 9 Oct 2025 21:32:30 +0800 Subject: [PATCH] E-Invoice - API, Sales Invoice Report --- .../General/Eloquent/Filters/DateRange.php | 51 +++++ .../Eloquent/Filters/DateRangeNoData.php | 60 ++++++ .../ListBookingsSalesInvoiceLogic.php | 52 +++++ .../ListBookingsSalesInvoiceController.php | 20 ++ ...ountSalesInvoiceBookingDetailsResource.php | 35 ++++ .../AutocountSalesInvoiceBookingResource.php | 198 ++++++++++++++++++ routes/apipub.php | 5 + 7 files changed, 421 insertions(+) create mode 100644 app/Classes/General/Eloquent/Filters/DateRange.php create mode 100644 app/Classes/General/Eloquent/Filters/DateRangeNoData.php create mode 100644 app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php create mode 100644 app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php create mode 100644 app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php create mode 100644 app/Http/Resources/AutocountSalesInvoiceBookingResource.php diff --git a/app/Classes/General/Eloquent/Filters/DateRange.php b/app/Classes/General/Eloquent/Filters/DateRange.php new file mode 100644 index 00000000..f1ffc82a --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/DateRange.php @@ -0,0 +1,51 @@ +startOfDay() + : Carbon::now()->subMonth()->startOfDay(); + + $endDate = isset($dt2) && $dt2 + ? Carbon::createFromFormat('Y-m-d', $dt2)->endOfDay() + : Carbon::now()->endOfDay(); + + return $builder->with([ + 'company', + 'transactions.transactionDetails', + 'transactions.voucherRedemption', + ]) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]) + ->latest('created_at'); + }) + ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->complete() + ->where(function ($q) use ($startDate, $endDate) { + $q->where('created_at', '<', $startDate) + ->orWhere('created_at', '>', $endDate); + }); + }); + } +} diff --git a/app/Classes/General/Eloquent/Filters/DateRangeNoData.php b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php new file mode 100644 index 00000000..25f9a343 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/DateRangeNoData.php @@ -0,0 +1,60 @@ +startOfDay() + : Carbon::now()->subMonth()->startOfDay(); + + $endDate = isset($dt2) && $dt2 + ? Carbon::createFromFormat('Y-m-d', $dt2)->endOfDay() + : Carbon::now()->endOfDay(); + + return $builder->with([ + 'company', + 'transactions.transactionDetails', + 'transactions.voucherRedemption', + ]) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->payments() + ->complete() + ->whereBetween('created_at', [$startDate, $endDate]) + ->latest('created_at'); + }) + ->whereDoesntHave('transactions', function ($query) use ($startDate, $endDate) { + $query->payments() + ->complete() + ->where(function ($q) use ($startDate, $endDate) { + $q->where('created_at', '<', $startDate) + ->orWhere('created_at', '>', $endDate); + }); + }) + ->whereHas('transactions', function ($query) use ($startDate, $endDate){ + $query->where('type', TransactionType::INVOICE) + ->latest('created_at') + ->whereDoesntHave('attributesKVP', function ($invoiceQuery) { + $invoiceQuery->where('key', KVPKey::AUTOCOUNT_DOCNO_INVOICE); + }); + }); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php new file mode 100644 index 00000000..a2134295 --- /dev/null +++ b/app/Classes/Modules/Bookings/ControllersLogic/ListBookingsSalesInvoiceLogic.php @@ -0,0 +1,52 @@ + 'Retrieved Bookings with Sales Invoice', + 'message' => 'You have successfully retrieved a list of Bookings with Sales Invoice' + ]; + } + + /** @var CanListBookings */ + private $canListBookings; + + /** @var ListsBookings */ + private $listsBookings; + + /** + * ListBookingsSalesInvoiceLogic constructor. + * @param CanListBookings $canListBookings + * @param ListsBookings $listsBookings + */ + public function __construct(CanListBookings $canListBookings, ListsBookings $listsBookings) + { + $this->canListBookings = $canListBookings; + $this->listsBookings = $listsBookings; + } + + public function logic(Request $request) : JsonResponse + { + // $this->canListBookings->passes(); //cief todo: 90 - apipub + + $query = $this->listsBookings->execute($this->listsBookings->deserializeFilters($request->input('filters'))); + + return $this->collectionResponse(AutocountSalesInvoiceBookingResource::collection($query)); + + } +} diff --git a/app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php b/app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php new file mode 100644 index 00000000..249278af --- /dev/null +++ b/app/Http/Controllers/Bookings/ListBookingsSalesInvoiceController.php @@ -0,0 +1,20 @@ +execute($request); + } +} diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php new file mode 100644 index 00000000..fa00fdc6 --- /dev/null +++ b/app/Http/Resources/AutocountSalesInvoiceBookingDetailsResource.php @@ -0,0 +1,35 @@ + $this['DocNo'], + 'DocDate' => $this['DocDate'], + 'DebtorCode' => $this['DebtorCode'], + 'Ref' => $this['Ref'], + 'ShipInfo' => $this['ShipInfo'], + 'AccNo' => $this['AccNo'], + 'DetailDescription' => $this['DetailDescription'], + 'FurtherDescription' => $this['FurtherDescription'], + 'Classification' => $this['Classification'], + 'DeptNo' => $this['DeptNo'], + 'Qty' => $this['Qty'], + 'UnitPrice' => $this['UnitPrice'], + 'submiteinvoice' => $this['submiteinvoice'], + 'ConsolidatedEinvoice' => $this['ConsolidatedEinvoice'], + ]; + } + +} diff --git a/app/Http/Resources/AutocountSalesInvoiceBookingResource.php b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php new file mode 100644 index 00000000..f02d08be --- /dev/null +++ b/app/Http/Resources/AutocountSalesInvoiceBookingResource.php @@ -0,0 +1,198 @@ +transactions->where('type', TransactionType::PURCHASE_ORDER)->first(); + $company = $this->company; + + $lastPaymentTransaction = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]) + ->sortByDesc('created_at') + ->first(); + + $documentDate = Carbon::parse($lastPaymentTransaction->created_at); + + // if ($documentDate < $this->startDate || $documentDate > $this->endDate) { + // return []; + // } + + if($company->e_invoice === 1){ + $documentDate = $documentDate->copy()->endOfMonth(); + } + + $invoiceTransaction = $this->transactions->where('type', TransactionType::INVOICE)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->first(); + + $currencyId = $this->fix_currency_id; + + $subtotal = 0; + $displayedSubtotal = 0; + $totalPayment = 0; + $averageCurrencyRate = $invoiceTransaction->currency_rate; + $paymentSum = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->sum(function ($transaction) { + return round($transaction->amount, 2); + }); + if ($paymentSum){ + $averageCurrencyRate = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->sum(function ($transaction) { + return $transaction->currency_rate; + }) / $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->count(); + + $booking = Booking::where('id', $this->id)->first(); + $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->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); + } + + $detail = [ + 'DocNo' => $firstItem ? '<>' : '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction->bill_no, + 'ShipInfo' => $this->marking, + 'AccNo' => '500-0000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => $detail->product_name, + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => number_format($detail->quantity, 0), + 'UnitPrice' => $displayUnitPrice ? number_format($displayUnitPrice, 2) : '0', + 'submiteinvoice' => $firstItem ? 'T' : '', + 'ConsolidatedEinvoice' => $firstItem ? ($company->e_invoice ? 'F' : 'T') : '', + ]; + $details->push($detail); + + if($firstItem) { + $firstItem = false; + } + } + + // Service Charge - Starts + $serviceCharge = 0; + if (!$totalPayment) { + $serviceCharge = $invoiceTransaction->service_charge; + } + else { + $serviceCharge = $this->transactions + ->where('type', TransactionType::PAYMENT) + ->where('status', ApprovalStatus::COMPLETED) + ->sum(function ($transaction) { + return $transaction->service_charge; + }); + } + + $detail = [ + 'DocNo' => '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction->bill_no, + 'ShipInfo' => $this->marking, + 'AccNo' => '500-0000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => 'Service Charge', + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => '1', + 'UnitPrice' => $serviceCharge ? number_format($serviceCharge, 2) : '0', + 'submiteinvoice' => '', + 'ConsolidatedEinvoice' => '', + ]; + $details->push($detail); + // Service Charge - Ends + + // Adjustment - Starts + $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); + } + + $detail = [ + 'DocNo' => '', + 'DocDate' => $formattedDocumentDate, + 'DebtorCode' => $company->debtor, + 'Ref' => $invoiceTransaction->bill_no, + 'ShipInfo' => $this->marking, + 'AccNo' => '500-0000', + 'DetailDescription' => 'PRODUCT NAME :', + 'FurtherDescription' => 'Adjustment', + 'Classification' => '022', + 'DeptNo' => 'C', + 'Qty' => '1', + 'UnitPrice' => $adjustment ? number_format($adjustment, 2) : '0', + 'submiteinvoice' => '', + 'ConsolidatedEinvoice' => '', + ]; + $details->push($detail); + // Adjustment - Ends + + return AutocountSalesInvoiceBookingDetailsResource::collection( + collect($details) + ); + } +} diff --git a/routes/apipub.php b/routes/apipub.php index b2a5e2ce..3fdbcd80 100644 --- a/routes/apipub.php +++ b/routes/apipub.php @@ -1,9 +1,14 @@ 'apipub', 'prefix' => 'v1', 'as' => 'apipub.'], function () { Route::group(['middleware' => 'token.check'], function () { Route::get('transactions/mappable/query', 'Transactions\ListMappableTransactionsController@list')->name('transaction.mappable.list'); + + Route::group(['prefix' => 'booking', 'as' => 'booking.'], function () { + Route::get('/sales-invoice', [ListBookingsSalesInvoiceController::class, 'list'])->name('booking.sales-invoice.list'); + }); }); });