Files
exchange-2.0/app/Classes/Modules/Transactions/ControllersLogic/ImportPurchaseOrderTransactionLogic.php
T
Dillon Ngo 1ea5a9c041 DEBUG
2026-04-03 09:29:02 +08:00

314 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Classes\Modules\Transactions\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Jobs\Commands\V2\ETLPurchaseOrderTransactionV2CommandJob;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Documents\Services\CreatesDocument;
use App\Classes\Modules\Documents\Services\DeletesDocument;
use App\Classes\Modules\Documents\Services\CreatesFiles;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor;
use App\Classes\Modules\Transactions\Processors\ETLPDFPurchaseOrderClaudeProcessor;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Transactions\Standards\Rules\CanETLPurchaseOrder;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Http\Resources\TransactionResource;
use App\Models\Booking;
use App\Models\Document;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel;
class ImportPurchaseOrderTransactionLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Update Purchase Order',
'message' => $this->notificationMessage ?? "You have successfully updated your booking's purchase order",
];
}
private ?string $notificationMessage = null;
/** @var FetchesBooking */
private $fetchesBooking;
/** @var GeneratesTransactionBillNumber */
private $generatesTransactionBillNumber;
/** @var CreatePurchaseOrderTransactionProcessor */
private $createPurchaseOrderTransactionProcessor;
/** @var ETLPDFPurchaseOrderClaudeProcessor */
private $eTLPDFPurchaseOrderClaudeProcessor;
/** @var CanETLPurchaseOrder */
private $canETLPurchaseOrder;
/** @var CreatesDocument */
private $createsDocument;
/** @var DeletesDocument */
private $deletesDocument;
/** @var CreatesFiles */
private $createsFile;
/**
* ImportPurchaseOrderTransactionLogic constructor.
* @param FetchesBooking $fetchesBooking
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
* @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
* @param ETLPDFPurchaseOrderClaudeProcessor $eTLPDFPurchaseOrderClaudeProcessor
* @param CanETLPurchaseOrder $canETLPurchaseOrder
* @param CreatesDocument $createsDocument
* @param DeletesDocument $deletesDocument
* @param CreatesFiles $createsFile
*/
public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, ETLPDFPurchaseOrderClaudeProcessor $eTLPDFPurchaseOrderClaudeProcessor, CanETLPurchaseOrder $canETLPurchaseOrder, CreatesDocument $createsDocument, DeletesDocument $deletesDocument, CreatesFiles $createsFile)
{
$this->fetchesBooking = $fetchesBooking;
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
$this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
$this->eTLPDFPurchaseOrderClaudeProcessor = $eTLPDFPurchaseOrderClaudeProcessor;
$this->canETLPurchaseOrder = $canETLPurchaseOrder;
$this->createsDocument = $createsDocument;
$this->deletesDocument = $deletesDocument;
$this->createsFile = $createsFile;
}
/**
* @param Request $request
* @param string $id
* @return JsonResponse
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function logic(Request $request, $id = ''): JsonResponse
{
$products = [];
/** @var Booking $booking */
$booking = $this->fetchesBooking->execute(['id' => $request->route('id') ?? $id]);
if ($request->has('products') && is_array($request->input('products'))) {
$this->canETLPurchaseOrder->passes();
$products = $request->input('products');
if(empty($products)){
$files = $request->input('files', []);
$maxSize = 150 * 1024; // 150 KB
$totalSize = 0;
$base64FilesEN = []; // files with "EN" in the name
$base64FilesOther = []; // files without "EN"
foreach ($files as $index => $file) {
if (!isset($file['base64'])) {
Log::info("Missing base64 for file index {$index}");
continue;
}
$fileBase64 = $file['base64'];
$fileName = $file['name'] ?? "file_{$index}";
if (stripos($fileName, 'EN') !== false) {
$base64FilesEN[] = $fileBase64;
if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) {
$cleanBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64);
}
else{
$cleanBase64 = $fileBase64;
}
$fileContent = base64_decode($cleanBase64, true);
if ($fileContent === false) {
Log::info("Invalid Base64 for file index {$index}");
}
$totalSize += strlen($fileContent);
} else {
$base64FilesOther[] = $fileBase64;
}
}
//Keep a copy of all PDF files uploaded include pdf file with 'EN' as part of its filename
$allBase64Files = array_merge($base64FilesEN, $base64FilesOther);
$deleteDocument = $booking->documents()->where('document_type', DocumentType::ECOMMERCE_PURCHASE_ORDER)->latest()->first();
if($deleteDocument){
$this->deletesDocument->execute($deleteDocument);
}
$object = new DocumentObject(DocumentType::ECOMMERCE_PURCHASE_ORDER, $allBase64Files, '', ApprovalStatus::APPROVED, '1688_purchase_orders');
/** @var Document $document */
$document = $this->createsDocument->execute($booking, $object);
$this->createsFile->execute($document, $object);
//ETL only applicable for pdf file with 'EN' as part of its filename
if ($totalSize > 0) {
if ($totalSize > $maxSize) {
// ETLPurchaseOrderTransactionV2CommandJob::dispatch($base64FilesEN, $request->route('id'));
$this->runAsJob($base64FilesEN, $request->route('id'));
$this->notificationMessage = "Please dont refresh the page. The page will automatically update when the process is done.";
// $this->notificationMessage = "Please refresh page in a few minutes";
return $this->response([]);
}
else{
$products = $this->eTLPDFPurchaseOrderClaudeProcessor->execute($base64FilesEN);
}
}
else{
return $this->response([]);
}
}
}
else {
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
foreach ($object->getFiles() as $file) {
$collection = Excel::toCollection(null, json_decode($file)->file_info->original->file, null, null, true);
$sheet = $collection->first()->skip(1);
$products = $sheet->map(function ($row) {
Log::info('ImportPurchaseOrderTransactionLogic Row data: ' . json_encode($row));
$stockCode = $row[0];
$description = $row[1];
$quantity = $row[2];
$unit_price = $row[3];
return [
'stockCode' => $stockCode,
'description' => $description,
'quantity' => $quantity,
'unit_price' => $unit_price
];
})->all();
}
}
$billNumber = $this->generatesTransactionBillNumber->execute('PO-');
$total = collect($products)->sum(function($product){
return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price']));
});
$object = new TransactionObject($billNumber, TransactionType::PURCHASE_ORDER, $booking->company->id, 1,
1, PaymentMethodType::CASH,
$total, $total, $booking->fix_currency_id, $booking->fix_currency_id,
1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $products);
$transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);
return $this->resourceResponse(new TransactionResource($transaction));
}
private function runAsJob($files, $bookingId){
$maxFilesPerBatch = 4;
$maxBytesPerBatch = 350 * 1024; // 350 KB
$currentBatch = [];
$currentBatchSize = 0;
$fileBatches = [];
foreach ($files as $index => $fileBase64) {
if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) {
$cleanBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64);
}
else{
$cleanBase64 = $fileBase64;
}
$fileContent = base64_decode($cleanBase64, true);
if ($fileContent === false) {
Log::info("Invalid Base64 for file index {$index}");
continue; // skip invalid files
}
$fileSize = strlen($fileContent); // size in bytes
// If adding this file would exceed limits, start a new batch
$wouldExceedFileCount = count($currentBatch) >= $maxFilesPerBatch;
$wouldExceedTotalSize = ($currentBatchSize + $fileSize) > $maxBytesPerBatch;
if ($wouldExceedFileCount || $wouldExceedTotalSize) {
// save current batch
$fileBatches[] = $currentBatch;
// reset
$currentBatch = [];
$currentBatchSize = 0;
}
// Add file to batch
$currentBatch[] = $fileBase64;
$currentBatchSize += $fileSize;
}
// push final remaining batch
if (!empty($currentBatch)) {
$fileBatches[] = $currentBatch;
}
$totalBatches = count($fileBatches);
$groupId = (string) Str::uuid();
foreach ($fileBatches as $index => $batch) {
$isLast = ($index === $totalBatches - 1);
// Calculate total size of this batch
$batchSize = 0;
foreach ($batch as $fileBase64) {
if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) {
$cleanBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64);
}
else{
$cleanBase64 = $fileBase64;
}
$fileContent = base64_decode($cleanBase64, true);
if ($fileContent !== false) {
$batchSize += strlen($fileContent);
}
}
// Log batch info before dispatching job
Log::info("Dispatching Job for Batch #" . ($index + 1), [
'group_id' => $groupId,
'batch_index' => $index + 1,
'total_batches' => $totalBatches,
'is_last' => $isLast,
'files_in_batch' => count($batch),
'batch_size_bytes' => $batchSize,
]);
ETLPurchaseOrderTransactionV2CommandJob::dispatch(
$batch,
$bookingId,
$groupId,
$index + 1,
$totalBatches,
$isLast
)->delay(now()->addSeconds($index * 10));
}
}
}