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 RefBooking AmountPaid AmountOutstanding AmountCustomerOrder 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 @@
Credit Note
- - + +
+
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 @@ + + 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 E-Invoice
@@ -342,6 +342,14 @@
+
+
+
Regenerate Normal Inv
+
+ + + +
@@ -497,7 +505,7 @@
-
+
Change Booking Owner
@@ -614,7 +622,7 @@