mirror of
https://gitlab.com/CIEFWorldwideSdnBhd/exchange-2.0.git
synced 2026-08-28 17:03:59 +00:00
1688 PO Automation Project
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\Transactions\Processors;
|
||||
|
||||
|
||||
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\Modules\OpenAI\Services\CreatesChatGPTResponse;
|
||||
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;
|
||||
|
||||
class ETLPurchaseOrderTransactionProcessor
|
||||
{
|
||||
/** @var CreatesChatGPTResponse */
|
||||
private $createsChatGPTResponse;
|
||||
|
||||
/** @var FetchesBooking */
|
||||
private $fetchesBooking;
|
||||
|
||||
/** @var GeneratesTransactionBillNumber */
|
||||
private $generatesTransactionBillNumber;
|
||||
|
||||
/** @var CreatePurchaseOrderTransactionProcessor */
|
||||
private $createPurchaseOrderTransactionProcessor;
|
||||
|
||||
/**
|
||||
* ETLPurchaseOrderTransactionProcessor constructor.
|
||||
* @param FetchesBooking $fetchesBooking
|
||||
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
|
||||
* @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
|
||||
* @param CreatesChatGPTResponse $createsChatGPTResponse
|
||||
*/
|
||||
public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, CreatesChatGPTResponse $createsChatGPTResponse)
|
||||
{
|
||||
$this->fetchesBooking = $fetchesBooking;
|
||||
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
|
||||
$this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
|
||||
$this->createsChatGPTResponse = $createsChatGPTResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $products
|
||||
* @param array $productsMetadata
|
||||
* @param int|null $bookingId
|
||||
* @return array
|
||||
* @throws \App\Classes\Exceptions\MalformedRequestException
|
||||
* @throws \App\Classes\Exceptions\ResourceNotFoundException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function execute(array $products, array $productsMetadata, ?int $bookingId = null)
|
||||
{
|
||||
foreach ($products as &$product) {
|
||||
$product['quantity'] = (int) $product['quantity'];
|
||||
$product['unit_price'] = (float) $product['unit_price'];
|
||||
}
|
||||
|
||||
// Translate descriptions if they contain Chinese characters
|
||||
$products = collect($products)->map(function ($product) {
|
||||
if (isset($product['description']) && preg_match("/\p{Han}+/u", $product['description'])) {
|
||||
$kvp = KeyValuePair::where('key', KVPKey::CHATGPT_PROMPT_PREFIX . "TRANSLATE")->first();
|
||||
$userPrompt = '';
|
||||
if($kvp){
|
||||
$templateFromDb = $kvp->value;
|
||||
$productsMetadataJson = json_encode($product['description'], JSON_UNESCAPED_UNICODE);
|
||||
$userPrompt = str_replace('{{productDescription}}', $productsMetadataJson, $templateFromDb);
|
||||
}
|
||||
else{
|
||||
throw new ResourceNotFoundException('AI Setup incomplete!');
|
||||
}
|
||||
|
||||
$result = $this->createsChatGPTResponse->execute($userPrompt);
|
||||
$content = $result['choices'][0]['message']['content'] ?? null;
|
||||
|
||||
// 1. Try raw JSON
|
||||
$decoded = json_decode($content, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$json = $content;
|
||||
}
|
||||
// 2. Fallback to fenced ```json block
|
||||
elseif (preg_match('/```json\s*(\[[\s\S]*?\]|\{[\s\S]*?\})\s*```/i', $content, $matches)) {
|
||||
$json = $matches[1];
|
||||
}
|
||||
else {
|
||||
Log::warning('No JSON found in ChatGPT response', ['content' => $content]);
|
||||
return;
|
||||
}
|
||||
|
||||
$translations = json_decode($json, true);
|
||||
if($translations){
|
||||
$translatedText = $translations[0];
|
||||
$product['description'] = $translatedText;
|
||||
}
|
||||
}
|
||||
return $product;
|
||||
})->toArray();
|
||||
|
||||
if (is_array($productsMetadata) && count($productsMetadata) > 0) {
|
||||
$kvp = KeyValuePair::where('key', KVPKey::CHATGPT_PROMPT_PREFIX . "EXTRACT1")->first();
|
||||
$prompt = '';
|
||||
if($kvp){
|
||||
$templateFromDb = $kvp->value;
|
||||
$productsMetadataJson = json_encode($productsMetadata, JSON_UNESCAPED_UNICODE);
|
||||
$prompt = str_replace('{{productsMetadata}}', $productsMetadataJson, $templateFromDb);
|
||||
}
|
||||
else{
|
||||
throw new ResourceNotFoundException('AI Setup incomplete!');
|
||||
}
|
||||
|
||||
$result = $this->createsChatGPTResponse->execute($prompt);
|
||||
$content = $result['choices'][0]['message']['content'] ?? null;
|
||||
|
||||
$freight = 0;
|
||||
$preferential = 0;
|
||||
|
||||
if ($content) {
|
||||
if (preg_match('/```json\s*(\{.*?\})\s*```/s', $content, $matches)) { //If wrapped in ```json, extract it
|
||||
$json = $matches[1];
|
||||
} else {
|
||||
$json = trim($content);
|
||||
}
|
||||
|
||||
$fees = json_decode($json);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$freight = $fees->Freight ?? 0;
|
||||
$preferential = $fees->Preferential ?? 0;
|
||||
} else {
|
||||
Log::error('JSON decode failed', [
|
||||
'error' => json_last_error_msg(),
|
||||
'content' => $content
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Freight as a separate product
|
||||
if($freight){
|
||||
$products[] = [
|
||||
'stockCode' => '',
|
||||
'description' => 'First-Mile Delivery',
|
||||
'quantity' => 1,
|
||||
'unit_price' => $freight,
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
// Add Preferential as a separate product (as negative price if needed)
|
||||
if($preferential){
|
||||
$products[] = [
|
||||
'stockCode' => '',
|
||||
'description' => 'Discount',
|
||||
'quantity' => 1,
|
||||
'unit_price' => $preferential,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if($bookingId){
|
||||
/** @var Booking $booking */
|
||||
$booking = $this->fetchesBooking->execute(['id' => $bookingId]);
|
||||
|
||||
$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 $products;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user