Merge branch 'fix-duplicate-statement-transactions' of gitlab.com:CIEFWorldwideSdnBhd/exchange-2.0 into fix-account-statement-issue

This commit is contained in:
edmondlang
2023-09-12 23:21:36 +08:00
7 changed files with 418 additions and 32 deletions
@@ -98,7 +98,7 @@ class ImportBankStatementLogic extends AbstractControllerLogic
$tellerId = $row[19];
$branchChannel = $row[20];
$transactionCode = $row[21];
$endBalance = $row[22];
$endBalance = ((float) str_replace(',', '', $row[22]));
$description2 = $row[25];
$description3 = $row[26];
$description4 = $row[27];
@@ -123,11 +123,10 @@ class ImportBankStatementLogic extends AbstractControllerLogic
$existingTransaction = StatementTransaction::where('transaction_ref', $transactionRef)
->where('posting_date', $postingDate)
->where('amount', $amount)
->where('transaction_description', $transactionDescription)
->where('teller_id', $tellerId)
->where('branch_channel', $branchChannel)
->where('transaction_code', $transactionCode)
->where('end_balance', $endBalance)
->whereRaw("CAST(REPLACE(end_balance,',','') AS DECIMAL(15,2)) = ?",[$endBalance])
->first();
if (!$existingTransaction) {
@@ -0,0 +1,210 @@
<?php
namespace App\Classes\Modules\Transactions\Processors;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingTransferredAmount;
use App\Classes\Modules\ServiceTypes\Services\FetchesServiceConfigurations;
use App\Classes\Modules\Transactions\Services\ListsTransactions;
use App\Classes\Modules\Transactions\Services\CreatesTransaction;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPaidAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingCurrencyAverageRate;
use App\Classes\Modules\Companies\Services\FetchesCompany;
use App\Classes\Modules\Bookings\Services\UpdatesBookingStatus;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\SegmentConstants;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\Booking;
use App\Models\SegmentConstant;
class CreateInvoiceTransactionProcessorWithInvoiceNo
{
/** @var CreatesTransaction */
private $createsTransaction;
/** @var GeneratesTransactionBillNumber */
private $generatesTransactionBillNumber;
/** @var CalculatesBookingPaidAmount */
private $calculatesBookingPaidAmount;
/** @var CalculatesBookingPayableAmount */
private $calculatesBookingPayableAmount;
/** @var CalculatesBookingTransferredAmount */
private $calculatesBookingTransferredAmount;
/** @var CalculatesBookingCurrencyAverageRate */
private $calculatesBookingCurrencyAverageRate;
/** @var FetchesCompany */
private $fetchesCompany;
/** @var UpdatesBookingStatus */
private $updatesBookingStatus;
/** @var CreateInvoiceDocumentProcessor */
private $invoiceDocumentProcessor;
/**
* CreateInvoiceTransactionProcessor constructor.
* @param ListsTransactions $listsTransactions
* @param CreatesTransaction $createsTransaction
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
* @param CalculatesBookingPaidAmount $calculatesBookingPaidAmount
* @param CalculatesBookingPayableAmount $calculatesBookingPayableAmount
* @param CalculatesBookingTransferredAmount $calculatesBookingTransferredAmount
* @param FetchesServiceConfigurations $fetchesServiceConfigurations
* @param CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate
* @param FetchesCompany $fetchesCompany
* @param UpdatesBookingStatus $updatesBookingStatus
* @param CreateInvoiceDocumentProcessor $invoiceDocumentProcessor
*/
public function __construct(ListsTransactions $listsTransactions, CreatesTransaction $createsTransaction, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CalculatesBookingPaidAmount $calculatesBookingPaidAmount, CalculatesBookingPayableAmount $calculatesBookingPayableAmount, CalculatesBookingTransferredAmount $calculatesBookingTransferredAmount, FetchesServiceConfigurations $fetchesServiceConfigurations, CalculatesBookingCurrencyAverageRate $calculatesBookingCurrencyAverageRate, FetchesCompany $fetchesCompany, UpdatesBookingStatus $updatesBookingStatus, CreateInvoiceDocumentProcessor $invoiceDocumentProcessor)
{
$this->createsTransaction = $createsTransaction;
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
$this->calculatesBookingPaidAmount = $calculatesBookingPaidAmount;
$this->calculatesBookingPayableAmount = $calculatesBookingPayableAmount;
$this->calculatesBookingTransferredAmount = $calculatesBookingTransferredAmount;
$this->calculatesBookingCurrencyAverageRate = $calculatesBookingCurrencyAverageRate;
$this->fetchesCompany = $fetchesCompany;
$this->updatesBookingStatus = $updatesBookingStatus;
$this->invoiceDocumentProcessor = $invoiceDocumentProcessor;
}
/**
* @param Booking $booking
* @return void
* @throws MalformedRequestException
*/
public function execute(Booking $booking, String $invoiceNo)
{
if ($booking->status === ApprovalStatus::COMPLETED) {
return;
}
$payable_amount = $this->calculatesBookingPayableAmount->execute($booking, $booking->fix_currency_id);
$booking_amount = $booking->fix_amount;
// confirm that booking amount has been fully paid
if ((float) $booking_amount > (float) $payable_amount) {
return;
}
// confirm that all payments has been transferred
if ($this->calculatesBookingTransferredAmount->execute($booking) !== $this->calculatesBookingPaidAmount->execute($booking)) {
return;
}
$purchaseOrder = $booking->transactions()
->where('type', TransactionType::PURCHASE_ORDER)
->complete()
->first();
$constants = SegmentConstant::where('reference', SegmentConstants::SERVICE_TYPE)->where('detail->id', $booking->service->id)->first();
if ($constants->detail->is_billable && !$purchaseOrder) {
return;
}
// $transaction = $booking->transactions()
// ->where('type', TransactionType::PAYMENT)
// ->first();
$transaction = $booking->transactions()
->where('type', TransactionType::PAYMENT)
->latest()->get()[0];
// $billNumber = $this->generatesTransactionBillNumber->execute('INV-');
$billNumber = $invoiceNo;
$booking_currency_average_rate = $this->calculatesBookingCurrencyAverageRate->execute($booking, TransactionType::PAYMENT);
$total_service_charge = $booking->transactions()
->where('type', TransactionType::PAYMENT)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->sum('service_charge');
$total_tax = $booking->transactions()
->where('type', TransactionType::PAYMENT)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->sum('tax');
$transaction_object = new TransactionObject(
$billNumber,
TransactionType::INVOICE,
$transaction->issuer,
$transaction->receiver,
$transaction->recipient_bank_account_id,
$transaction->payment_method,
$payable_amount,
$booking_amount,
$transaction->currency_id,
$transaction->original_currency_id,
$booking_currency_average_rate,
$total_tax,
$total_service_charge,
null,
ApprovalStatus::APPROVED
);
$invoice_transaction = $this->createsTransaction->execute($purchaseOrder->booking, $transaction_object);
$voucherRedemption = $transaction->voucherRedemption;
$supplier = $this->fetchesCompany->execute(['id' => $transaction->receiver]);
// purchase order
$this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::PURCHASE_ORDER, $voucherRedemption);
// deliver order
$this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::DELIVER_ORDER, $voucherRedemption);
// invoice
$this->invoiceDocumentProcessor->execute($invoice_transaction, $purchaseOrder, $supplier, DocumentType::INVOICE, $voucherRedemption);
$billNumber = $this->generatesTransactionBillNumber->execute('SPDO-');
$booking_currency_average_rate = $this->calculatesBookingCurrencyAverageRate->execute($booking, TransactionType::BILL);
$transaction = $booking->transactions()->payments()->where('status', ApprovalStatus::COMPLETED)->first()
->transactions()->where('type', TransactionType::BILL)->first();
$transaction_object = new TransactionObject(
$billNumber,
TransactionType::SUPPLIER_DELIVER,
$transaction->issuer,
$transaction->receiver,
$transaction->recipient_bank_account_id,
$transaction->payment_method,
$payable_amount,
$booking_amount,
$transaction->currency_id,
$transaction->original_currency_id,
$booking_currency_average_rate,
$total_tax,
$total_service_charge,
null,
ApprovalStatus::APPROVED
);
$supplier_deliver_order_transaction = $this->createsTransaction->execute($purchaseOrder->booking, $transaction_object);
// supply deliver order
$this->invoiceDocumentProcessor->execute($supplier_deliver_order_transaction, $purchaseOrder, $supplier, DocumentType::SUPPLIER_DELIVER_ORDER, null);
$this->updatesBookingStatus->execute($booking, ApprovalStatus::COMPLETED);
// update perfex crm
// if(config('perfexcrm.is_enabled') == 'true'){
// CreatePerfexCRMInvoice::dispatch($invoice_transaction, $purchaseOrder, $supplier);
// }
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
namespace App\Console\Commands;
use App\Models\SeasonalSegment;
use Illuminate\Console\Command;
use Carbon\Carbon;
use App\Classes\Modules\Companies\Services\RemovesCompanyFromSegment;
use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionProcessorWithInvoiceNo;
use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionProcessor;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use App\Models\Transaction;
use Illuminate\Support\Facades\Log;
class RegenerateInvoice extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'regenerateInvoice';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate invoice';
/** @var CreateInvoiceTransactionProcessor */
private $createInvoiceTransactionProcessor;
/** @var CreateInvoiceTransactionProcessorWithInvoiceNo */
private $createInvoiceTransactionProcessorWithInvoiceNo;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(CreateInvoiceTransactionProcessor $createInvoiceTransactionProcessor, CreateInvoiceTransactionProcessorWithInvoiceNo $createInvoiceTransactionProcessorWithInvoiceNo)
{
parent::__construct();
$this->createInvoiceTransactionProcessor = $createInvoiceTransactionProcessor;
$this->createInvoiceTransactionProcessorWithInvoiceNo = $createInvoiceTransactionProcessorWithInvoiceNo;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// Allocate sufficient memory as needed
// ini_set('memory_limit', '256M');
$processed_invoice = 1;
Booking::where('status', ApprovalStatus::COMPLETED)
->whereDate('updated_at', '>=', Carbon::parse('2023-01-01'))
->orderBy('id')
->chunk(100, function ($bookings) use (&$processed_invoice) {
foreach ($bookings as $booking) {
$logMessage = 'Counter ' . $processed_invoice;
$this->printAndLog($logMessage);
$processed_invoice++;
if (
!$booking->transactions()->where('transactions.type', TransactionType::INVOICE)->exists()
|| $booking->transactions()->where('transactions.type', TransactionType::INVOICE)->first()->created_at->greaterThanOrEqualTo(Carbon::parse('2023-09-06'))
) {
continue;
}
// Delete transactions and documents in one query
$booking->transactions()->whereIn('transactions.type', [TransactionType::INVOICE, TransactionType::SUPPLIER_DELIVER])->delete();
$booking->documents()->whereIn('document_type', [DocumentType::INVOICE, DocumentType::PURCHASE_ORDER, DocumentType::DELIVER_ORDER, DocumentType::SUPPLIER_DELIVER_ORDER])->delete();
$booking->status = ApprovalStatus::APPROVED;
$booking->save();
$deletedInvoice = $booking->transactions()
->whereIn('type', [TransactionType::INVOICE])
->onlyTrashed()
->orderBy('created_at', 'asc')
->first();
if ($deletedInvoice) {
$bill_no = $deletedInvoice->bill_no;
if (str_ends_with($bill_no, '-deleted')) {
$bill_no = str_replace('-deleted', '', $bill_no);
}
$deletedInvoice->bill_no = $bill_no . '-deleted';
$deletedInvoice->save();
$existing_invoice_bill_no = Transaction::where('bill_no', $bill_no)->first();
if ($existing_invoice_bill_no) {
$this->printAndLog('Delete existing bill_no');
$this->printAndLog(json_encode($existing_invoice_bill_no));
$existing_invoice_bill_no->forceDelete();
}
$invoiceProcessor = App()->make(CreateInvoiceTransactionProcessorWithInvoiceNo::class);
$invoiceProcessor->execute($booking, $bill_no);
$logMessage = 'Regenerated invoice. Booking Marking - ' . $booking->marking . '. Bill_no - ' . $bill_no . '. Old bill_no - ' . $deletedInvoice->bill_no;
$this->printAndLog($logMessage);
} else {
$invoiceProcessor = App()->make(CreateInvoiceTransactionProcessor::class);
$invoiceProcessor->execute($booking);
$logMessage = 'Regenerated new invoice. Booking Marking - ' . $booking->marking;
$this->printAndLog($logMessage);
}
}
});
}
public function printAndLog($string)
{
// $this->info($string);
Log::channel('regenerateInvoice')->info($string);
}
}
+5
View File
@@ -38,6 +38,11 @@ class Kernel extends ConsoleKernel
->dailyAt('01:00')
->appendOutputTo(storage_path().'/logs/soft-delete-seasonal-segmant-company.log')
->withoutOverlapping();
$schedule->command('regenerateInvoice')
->everyMinute()
->appendOutputTo(storage_path().'/logs/regenerateInvoice.log')
->withoutOverlapping();
}
/**
+5
View File
@@ -99,6 +99,11 @@ return [
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
'regenerateInvoice' => [
'driver' => 'single',
'path' => storage_path('logs/regenerateInvoice.log'),
'level' => 'info',
],
],
];
+7 -19
View File
@@ -73,22 +73,17 @@
<tbody>
@php
$subtotal = "0";
$voucherDiscount = $voucher_redemption ? bcmul((string)$voucher_redemption->value, "-1", 5) : "0";
$displayedSubtotal = "0";
$exactTotal = "0";
$voucherDiscount = $voucher_redemption ? bcmul((string)$voucher_redemption->value, "-1", 2) : "0";
$displayedSubtotal = 0;
@endphp
@foreach ($po_order_transaction->transactionDetails as $key => $transaction_detail)
@php
$exactUnitPrice = bcdiv($transaction_detail->price, $transaction->currency_rate, 5);
$exactUnitPrice = bcdiv($transaction_detail->price, $transaction->currency_rate, 7);
$itemTotal = bcmul($exactUnitPrice, $transaction_detail->quantity, 5);
// Round half to even for displayed item total
$displayedItemTotal = round(bcmul($exactUnitPrice, $transaction_detail->quantity, 2), 2, PHP_ROUND_HALF_EVEN);
$displayedItemTotal = bcmul($exactUnitPrice, $transaction_detail->quantity, 2);
$displayedSubtotal = bcadd($displayedSubtotal, $displayedItemTotal, 2);
$subtotal = bcadd($subtotal, $itemTotal, 5);
$exactTotal = bcadd($exactTotal, $displayedItemTotal, 5);
@endphp
<tr>
<td width="5%" class="center top">{{ $key + 1 }}</td>
@@ -135,17 +130,10 @@
</tr>
@endif
@php
// Calculate the totals with 5 decimal places
$displayedTotal = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
$expectedTotal = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
// Calculate the displayed totals with 2 decimal places
$displayedTotal = bcadd(bcadd(bcadd($displayedSubtotal, $transaction->service_charge, 2), $transaction->tax, 2), $voucherDiscount, 2);
// Calculate the discrepancy
$discrepancy = bcsub($expectedTotal, $displayedTotal, 5);
// Calculate the final total
$total = bcadd($expectedTotal, $discrepancy, 5);
$discrepancy = bcsub($displayedTotal, $expectedTotal, 5);
$total = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
@endphp
<tr>
<td colspan="4"></td>
+60 -10
View File
@@ -26,7 +26,9 @@ use Webklex\PDFMerger\Facades\PDFMergerFacade as PDFMerger;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Bookings\Processors\CreatePurchaseOrderFor1688OrderProcessor;
use App\Classes\Modules\Documents\Services\DeletesDocument;
use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionProcessorWithInvoiceNo;
use App\Classes\Modules\Transactions\Services\DeletesTransaction;
use Illuminate\Support\Facades\Log;
/*
|--------------------------------------------------------------------------
@@ -482,21 +484,69 @@ Route::get('/payments/manual', function(){
echo '</table>';
});
Route::get('/invoice/fix', function(){
set_time_limit(14400);
$bookings = Booking::where('status', ApprovalStatus::COMPLETED)->whereDate('updated_at', '>=', Carbon::parse('01-01-2023'))->get();
$processed_invoice = 1;
foreach($bookings as $booking){
$booking->transactions()->whereIn('transactions.type', [TransactionType::INVOICE, TransactionType::SUPPLIER_DELIVER])->delete();
$booking->documents()->whereIn('document_type', [DocumentType::INVOICE, DocumentType::PURCHASE_ORDER, DocumentType::DELIVER_ORDER, DocumentType::SUPPLIER_DELIVER_ORDER])->delete();
$booking->status = ApprovalStatus::APPROVED;
$booking->save();
Booking::where('status', ApprovalStatus::COMPLETED)
->whereDate('updated_at', '>=', Carbon::parse('01-01-2023'))
->orderBy('id')
->chunk(100, function ($bookings) use (&$processed_invoice) {
foreach ($bookings as $booking) {
(App()->make(CreateInvoiceTransactionProcessor::class))->execute($booking);
}
Log::channel('regenerateInvoice')->info('Counter ' . $processed_invoice);
dump('Counter ' . $processed_invoice);
$processed_invoice += 1;
if ($booking->transactions()->where('transactions.type', TransactionType::INVOICE)
->first()
->created_at
->greaterThanOrEqualTo(Carbon::parse('2023-09-07'))) {
continue;
}
$booking->transactions()->whereIn('transactions.type', [TransactionType::INVOICE, TransactionType::SUPPLIER_DELIVER])->delete();
$booking->documents()->whereIn('document_type', [DocumentType::INVOICE, DocumentType::PURCHASE_ORDER, DocumentType::DELIVER_ORDER, DocumentType::SUPPLIER_DELIVER_ORDER])->delete();
$booking->status = ApprovalStatus::APPROVED;
$booking->save();
$deletedInvoice = $booking->transactions()
->whereIn('type', [TransactionType::INVOICE])
->onlyTrashed()
->orderBy('created_at', 'asc')
->first();
if ($deletedInvoice) {
$bill_no = $deletedInvoice->bill_no;
// check if bill_no ends with '-deleted'
if (str_ends_with($bill_no, '-deleted')) {
$bill_no = str_replace('-deleted', '', $bill_no);
}
$deletedInvoice->bill_no = $bill_no . '-deleted';
$deletedInvoice->save();
$existing_invoice_bill_no = Transaction::withTrashed()->where('bill_no', $bill_no)->first();
if ($existing_invoice_bill_no) {
Log::channel('regenerateInvoice')->info('Delete existing bill_no');
Log::channel('regenerateInvoice')->info(json_encode($existing_invoice_bill_no));
$existing_invoice_bill_no->forceDelete();
}
(App()->make(CreateInvoiceTransactionProcessorWithInvoiceNo::class))->execute($booking, $bill_no);
dump('regenerated invoice. Booking Marking - ' . $booking->marking . '. Bill_no - ' . $bill_no . '. Old bill_no - ' . $deletedInvoice->bill_no);
Log::channel('regenerateInvoice')->info('regenerated invoice. Booking Marking - ' . $booking->marking . '. Bill_no - ' . $bill_no . '. Old bill_no - ' . $deletedInvoice->bill_no);
} else {
(App()->make(CreateInvoiceTransactionProcessor::class))->execute($booking);
dump('regenerated new invoice. Booking Marking - ' . $booking->marking);
Log::channel('regenerateInvoice')->info('regenerated new invoice. Booking Marking - ' . $booking->marking);
}
}
}
);
})->name('invoice.fix');
Route::get('/1688/fix/{reference}', function($reference){