Files
exchange-2.0/app/Classes/Modules/Transactions/Processors/ETLPDFPurchaseOrderTransactionProcessor.php
T
2025-12-30 16:56:17 +08:00

193 lines
7.2 KiB
PHP

<?php
namespace App\Classes\Modules\Transactions\Processors;
use App\Classes\Modules\OpenAI\Services\UploadsOpenAIFiles;
use App\Classes\Modules\OpenAI\Services\CreatesChatGPTResponseWithFiles;
use App\Classes\Exceptions\ResourceNotFoundException;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\ValueObjects\Constants\KVPKey;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\KeyValuePair;
use App\Models\Booking;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
class ETLPDFPurchaseOrderTransactionProcessor
{
/** @var FetchesBooking */
private $fetchesBooking;
/** @var GeneratesTransactionBillNumber */
private $generatesTransactionBillNumber;
/** @var CreatePurchaseOrderTransactionProcessor */
private $createPurchaseOrderTransactionProcessor;
/** @var UploadsOpenAIFiles */
private $uploadsOpenAIFiles;
/** @var CreatesChatGPTResponseWithFiles */
private $createsChatGPTResponseWithFiles;
/**
* ETLPDFPurchaseOrderTransactionProcessor constructor.
* @param FetchesBooking $fetchesBooking
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
* @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
* @param UploadsOpenAIFiles $uploadsOpenAIFiles
* @param CreatesChatGPTResponseWithFiles $createsChatGPTResponseWithFiles
*/
public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, UploadsOpenAIFiles $uploadsOpenAIFiles,
CreatesChatGPTResponseWithFiles $createsChatGPTResponseWithFiles)
{
$this->fetchesBooking = $fetchesBooking;
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
$this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
$this->uploadsOpenAIFiles = $uploadsOpenAIFiles;
$this->createsChatGPTResponseWithFiles = $createsChatGPTResponseWithFiles;
}
/**
* @param UploadedFile[] $files
* @param int $bookingId
* @return array|null
*/
public function execute(array $files, ?int $bookingId = null): ?array
{
$tempPaths = [];
try {
foreach ($files as $index => $fileBase64) {
// Strip prefix if present
if (preg_match('/^data:application\/pdf;base64,/', $fileBase64)) {
$fileBase64 = preg_replace('/^data:application\/pdf;base64,/', '', $fileBase64);
}
// Decode Base64
$fileContent = base64_decode($fileBase64);
if ($fileContent === false) {
Log::warning("Invalid Base64 for file index $index");
continue;
}
// Ensure temp directory exists
$tempDir = storage_path('app/documents');
if (!file_exists($tempDir)) {
mkdir($tempDir, 0755, true);
}
// Generate unique temp file path
$fileName = uniqid("file_{$index}_") . '.pdf';
$filePath = $tempDir . '/' . $fileName;
// Save file to disk
file_put_contents($filePath, $fileContent);
// Track temp path
$tempPaths[] = $filePath;
}
Log::info('tempPaths: ' . json_encode($tempPaths));
// Upload to OpenAI
$fileIds = $this->uploadsOpenAIFiles->execute($tempPaths);
$kvp = KeyValuePair::where(
'key',
KVPKey::CHATGPT_PROMPT_PREFIX . 'EXTRACT_1'
)->first();
if (! $kvp) {
throw new ResourceNotFoundException('AI Setup incomplete!');
}
$userPrompt = $kvp->value;
$response = $this->createsChatGPTResponseWithFiles->execute($userPrompt, $fileIds);
// Extract the text content from the first output message
$textContent = $response['output'][0]['content'][0]['text'] ?? '[]';
// Remove ```json fences if present
if (preg_match('/```json\s*(.*?)\s*```/s', $textContent, $matches)) {
$jsonString = $matches[1];
} else {
$jsonString = $textContent;
}
// Decode JSON
$data = json_decode($jsonString, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::error('Failed to decode OpenAI JSON response', [
'error' => json_last_error_msg(),
'textContent' => $textContent
]);
$data = [];
}
// Optional: convert keys to snake_case
$data = array_map(function($item) {
$newItem = [];
foreach ($item as $key => $value) {
$newKey = strtolower(str_replace(' ', '_', $key));
$newItem[$newKey] = $value;
}
return $newItem;
}, $data);
if($bookingId){
/** @var Booking $booking */
$booking = $this->fetchesBooking->execute(['id' => $bookingId]);
$billNumber = $this->generatesTransactionBillNumber->execute('PO-');
$total = collect($data)->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, $data);
$transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);
}
return $data;
} catch (\Exception $e) {
Log::error('Error processing PDF files with ChatGPT', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
} finally {
// Clean up
foreach ($tempPaths as $path) {
if (file_exists($path)) {
unlink($path);
}
}
}
}
function convertKeysToSnakeCase(array $items): array {
return array_map(function($item) {
$newItem = [];
foreach ($item as $key => $value) {
$newKey = strtolower(str_replace(' ', '_', $key));
$newItem[$newKey] = $value;
}
return $newItem;
}, $items);
}
}