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/Banks/ControllersLogic/DeleteBankLogic.php b/app/Classes/Modules/Banks/ControllersLogic/DeleteBankLogic.php
index 37c80843..871520ae 100644
--- a/app/Classes/Modules/Banks/ControllersLogic/DeleteBankLogic.php
+++ b/app/Classes/Modules/Banks/ControllersLogic/DeleteBankLogic.php
@@ -8,9 +8,10 @@ use App\Classes\Modules\Banks\Services\FetchesBank;
use App\Classes\Modules\Banks\Standards\Rules\CanDeleteBank;
use App\Classes\Modules\Banks\Services\DeletesBank;
use App\Classes\Modules\Banks\Services\CreatesBankLog;
-use App\Http\Resources\BankResource;
+use App\Classes\ValueObjects\Constants\RoleTypes;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
class DeleteBankLogic extends AbstractControllerLogic
{
@@ -66,20 +67,30 @@ class DeleteBankLogic extends AbstractControllerLogic
*/
public function logic(Request $request) : JsonResponse
{
-
$this->canDeleteBank->passes();
-
$bank = $this->fetchesBank->execute(['id' => $request->route('id')]);
+ $proceed = false;
if($bank->default){
- throw new RequestValidationException('You can\'t delete bank account when it set to default');
+ $isAuthorized = in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES);
+ if($isAuthorized) {
+ $banks = $bank->company->banks()->where('default', 1)->get();
+ if(count($banks) > 1) {
+ //User should be able to delete themselves, but sometimes there are more than 1 bank set as default (different type, why??!), we need to allow admin to do the delete
+ $proceed = true;
+ }
+ else{
+ $proceed = false;
+ }
+ }
+
+ if(!$proceed){
+ throw new RequestValidationException('You can\'t delete bank account when it set to default');
+ }
}
$bank = $this->deletesBank->execute($bank);
-
-// $bankLog = $this->createsBankLog->execute($bank);
-
+ // $bankLog = $this->createsBankLog->execute($bank);
return $this->response([]);
}
-
-}
\ No newline at end of file
+}
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/ControllersLogic/UpdateBookingAmountWithPOLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountWithPOLogic.php
index 5a5c5001..0ad1680c 100644
--- a/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountWithPOLogic.php
+++ b/app/Classes/Modules/Bookings/ControllersLogic/UpdateBookingAmountWithPOLogic.php
@@ -86,7 +86,9 @@ class UpdateBookingAmountWithPOLogic extends AbstractControllerLogic
return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price']));
});
$bookingAmountUpdate = (float)$bookingAttribute->value;
- $isTally = $total === $bookingAmountUpdate ? true : false;
+ // $isTally = ($total === $bookingAmountUpdate) ? true : false;
+ $isTally = bccomp($total, $bookingAmountUpdate, 3) === 0;
+
if(!$isTally){
throw new MalformedRequestException('Purchase Order total not tally with updated booking amount of ' . $bookingAmountUpdate);
}
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/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/Exports/Services/ExportsSalesInvoiceReport.php b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceReport.php
index afcab016..74598a8d 100644
--- a/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceReport.php
+++ b/app/Classes/Modules/Exports/Services/ExportsSalesInvoiceReport.php
@@ -2,18 +2,17 @@
namespace App\Classes\Modules\Exports\Services;
-use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\Booking;
-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 Illuminate\Http\Request;
+use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount;
+use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundServiceCharge;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
@@ -71,6 +70,8 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
public function map($booking): array
{
$records = [];
+ $averageCurrencyRate = 0;
+ $currencyId = 0;
$purchaseOrder = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first();
$company = $booking->company()->first();
@@ -79,15 +80,60 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
if(!$lastPaymentTransaction){
return $records;
}
- $documentDate = $lastPaymentTransaction->created_at;
- if(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){
- $documentDate = $booking->updated_at;
+
+ $invoiceTransaction = $booking->transactions()->where('type', TransactionType::INVOICE)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->first();
+
+ $currencyId = $booking->fix_currency_id;
+
+ $subtotal = 0;
+ $displayedSubtotal = 0;
+ $totalPayment = 0;
+ $averageCurrencyRate = $invoiceTransaction->currency_rate;
+ $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;
}
+
+ $documentDate = $lastPaymentTransaction->created_at;
+ // if(Carbon::parse($booking->updated_at)->isAfter($lastPaymentTransaction->created_at)){ //cief todo: 90 - Report E-Invoice date incorrect
+ // $documentDate = $booking->updated_at;
+ // }
$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);
+ }
+
+
$records[] = [
$firstItem ? '<>' : '',
$formattedDocumentDate,
@@ -100,7 +146,7 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
'022',
'C',
$detail->quantity,
- number_format($detail->price, 2),
+ $displayUnitPrice ? number_format($displayUnitPrice, 2): 0,
$firstItem ? 'T' : '',
$company->e_invoice ? 'F' : 'T'
];
@@ -109,6 +155,87 @@ class ExportsSalesInvoiceReport implements FromQuery, WithHeadings, WithHeadingR
$firstItem = false;
}
}
+
+
+ // Service Charge - Starts
+ $serviceCharge = 0;
+ if (!$totalPayment) {
+ $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
+ $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
+
return $records;
}
}
diff --git a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php
index 2267e51e..8f36ea91 100644
--- a/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php
+++ b/app/Classes/Modules/Transactions/Processors/CreateInvoiceDocumentProcessor.php
@@ -44,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;
@@ -63,9 +63,9 @@ 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){
@@ -88,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/Transactions/Processors/CreateProformaInvoiceTransactionProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateProformaInvoiceTransactionProcessor.php
index 7880187b..70a4cec4 100644
--- a/app/Classes/Modules/Transactions/Processors/CreateProformaInvoiceTransactionProcessor.php
+++ b/app/Classes/Modules/Transactions/Processors/CreateProformaInvoiceTransactionProcessor.php
@@ -151,7 +151,8 @@ class CreateProformaInvoiceTransactionProcessor
$payable_amount = $booking->transactions()->payments()->where(function ($query) {
return $query->where(function ($query) {
- return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString());
+ // return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString());
+ return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->where('expires_on', '>=', Carbon::now());
})->orWhere(function ($query) {
return $query->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
});
@@ -164,7 +165,8 @@ class CreateProformaInvoiceTransactionProcessor
$paymentAmount = $booking->transactions()->payments()->where(function ($query) {
return $query->where(function ($query) {
- return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString());
+ // return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString());
+ return $query->where('status', ApprovalStatus::PENDING_SUBMISSION)->where('expires_on', '>=', Carbon::now());
})->orWhere(function ($query) {
return $query->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
});
diff --git a/app/Classes/ValueObjects/Constants/BankAccountType.php b/app/Classes/ValueObjects/Constants/BankAccountType.php
index 0393091f..8885f2db 100644
--- a/app/Classes/ValueObjects/Constants/BankAccountType.php
+++ b/app/Classes/ValueObjects/Constants/BankAccountType.php
@@ -12,4 +12,29 @@ final class BankAccountType {
public const ALIPAY_RECIPIENT = 4;
+ /**
+ * Get all account type labels.
+ *
+ * @return array
+ */
+ public static function labels(): array
+ {
+ return [
+ self::PERSONAL => 'PERSONAL',
+ self::EXTERNAL => 'EXTERNAL',
+ self::ALIPAY_1688 => 'ALIPAY_1688',
+ self::ALIPAY_RECIPIENT => 'ALIPAY_RECIPIENT',
+ ];
+ }
+
+ /**
+ * Get label for a specific account type.
+ *
+ * @param int|string $type
+ * @return string
+ */
+ public static function label($type): string
+ {
+ return self::labels()[(int) $type] ?? 'Unknown';
+ }
}
diff --git a/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php b/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php
new file mode 100644
index 00000000..6bdbb1fe
--- /dev/null
+++ b/app/Console/Commands/V2/OneTimeBatchProcessEInvoicesV2Command.php
@@ -0,0 +1,81 @@
+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/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 Ref |
+ Booking Amount |
+ Paid Amount |
+ Outstanding Amount |
+ Customer |
+ Order Created Date |
+
+
+
+
+
+
+HTML;
+ }
+}
diff --git a/app/Http/Resources/BankResource.php b/app/Http/Resources/BankResource.php
index f3de3038..5e6e2e17 100644
--- a/app/Http/Resources/BankResource.php
+++ b/app/Http/Resources/BankResource.php
@@ -2,6 +2,7 @@
namespace App\Http\Resources;
+use App\Classes\ValueObjects\Constants\BankAccountType;
use Illuminate\Http\Resources\Json\JsonResource;
class BankResource extends JsonResource
@@ -27,6 +28,7 @@ class BankResource extends JsonResource
'country_id' => $this->country_id,
'default' => $this->default,
'status' => $this->status,
+ 'bank_account_type_label' => BankAccountType::label($this->type),
];
}
}
diff --git a/resources/assets/vue/components/banks/forms/DeleteBankAccountFormComponent.vue b/resources/assets/vue/components/banks/forms/DeleteBankAccountFormComponent.vue
index 3414c75f..18763be8 100644
--- a/resources/assets/vue/components/banks/forms/DeleteBankAccountFormComponent.vue
+++ b/resources/assets/vue/components/banks/forms/DeleteBankAccountFormComponent.vue
@@ -8,6 +8,7 @@
Are you Sure?
Are you sure you want to delete this bank account?
+
@@ -30,4 +31,4 @@
mixins: [componentHandler, ModalFormHandler]
}
-
\ No newline at end of file
+
diff --git a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue
index fc7b077e..c5cb9b96 100644
--- a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue
+++ b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue
@@ -415,12 +415,17 @@
diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue
index 1009153b..593c84be 100644
--- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue
+++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue
@@ -202,7 +202,19 @@
-
+
+
+
+
+
@@ -263,7 +275,7 @@
//Condition 2
const paymentsMade = Math.round((this.data.paid_amount + Number.EPSILON) * 100) / 100 > 0;
const outstandingAmount = Math.round((this.data.outstanding_amount + Number.EPSILON) * 100) / 100 > 0;
- const allPaymentApproved = this.data.payment_history.every(payment => payment.status === 2);
+ const allPaymentApproved = this.data.payment_history.every(payment => (payment.status === 2 || payment.status === 3));
//Condition 3
const adminBeforeApproval = this.$store.getters.isAdmin && !(this.data.purchase_order.status === 2);
@@ -321,11 +333,26 @@
this.submit(route('api.transaction.po.import', this.data.id), 'post', this.section, true, true);
},
- successHandler(){
- if((Math.round((this.poTotal + Number.EPSILON) * 1000) / 1000).toFixed(3) === (Math.round((this.data.amount + Number.EPSILON) * 1000) / 1000).toFixed(3)){
- this.submitted = true;
+ successHandler(response, section){
+ if(section === this.section + 'CheckTransferRule'){
+ this.checkEInvoiceRule();
+ }
+ else if(section === this.section + 'CheckEInvoiceRule'){
+ if(response.payload.data.isPassed){
+ this.submit(route('api.booking.proforma.create', this.data.id), 'post', this.section, true, true);
+ }
+ }
+ else{
+ if((Math.round((this.poTotal + Number.EPSILON) * 1000) / 1000).toFixed(3) === (Math.round((this.data.amount + Number.EPSILON) * 1000) / 1000).toFixed(3)){
+ this.submitted = true;
+ }
+ this.updateList()
+ }
+ },
+ errorHandler(error, statusCode, section) { //E-Invoice
+ if(section === this.section + 'CheckEInvoiceRule' && statusCode === 422){
+ $('#modal-einvoice-info').modal('show');
}
- this.updateList()
},
addProduct(){
this.products.push({
@@ -354,6 +381,31 @@
},
removeProduct(index){
this.products.splice(index, 1);
+ },
+ handleGenerateProformaInvoice(){
+ this.checkTransferRule();
+ },
+ checkTransferRule(){
+ this.error = '';
+ this.parameters = {
+ booking_id: this.data.id,
+ company_id: this.data.company.id,
+ };
+ this.submit(route('api.rule.check.transfer'), 'post', this.section + 'CheckTransferRule', false, true);
+ },
+ checkEInvoiceRule(){
+ this.error = '';
+ this.parameters = {
+ company_id: this.data.company.id,
+ };
+ this.submit(route('api.rule.check.einvoice'), 'post', this.section + 'CheckEInvoiceRule', false, true);
+ },
+ updatedEInvoiceInfo(info){
+ this.$store.dispatch('reloadList', {'name': "bookingDetailSection"});
+ },
+ changeOfMindEInvoiceRequest(){
+ this.parameters.e_invoice_request = false;
+ this.submit((this.route('api.company.einvoice.request.change')), 'post', this.section + 'ChangeOfMind', true, true);
}
},
mixins: [formHandler]
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 @@
+
+
+
+
+
+
+
+
+
Are you Sure?
+
Are you sure you want to regenerate normal invoice instead of e-invoice for this payment?
+
+
+
+
+
+
+
+
+
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 Normal Inv
+
+
+
+
+
@@ -497,7 +505,7 @@
-
+
Change Booking Owner
@@ -614,7 +622,7 @@