Files
star-erp/app/Modules/Integration/Actions/SyncVendingOrderAction.php
2026-03-03 14:28:15 +08:00

155 lines
5.6 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\Modules\Integration\Actions;
use App\Modules\Integration\Models\SalesOrder;
use App\Modules\Integration\Models\SalesOrderItem;
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
use App\Modules\Inventory\Contracts\ProductServiceInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\ValidationException;
class SyncVendingOrderAction
{
protected $inventoryService;
protected $productService;
public function __construct(
InventoryServiceInterface $inventoryService,
ProductServiceInterface $productService
) {
$this->inventoryService = $inventoryService;
$this->productService = $productService;
}
/**
* 執行販賣機訂單同步
*
* @param array $data
* @return array 包含訂單建立結果的資訊
* @throws ValidationException
* @throws \Exception
*/
public function execute(array $data)
{
$externalOrderId = $data['external_order_id'];
// 使用 Cache::lock 防護高併發
$lock = Cache::lock("sync_order_{$externalOrderId}", 10);
if (!$lock->get()) {
throw ValidationException::withMessages([
'external_order_id' => ["The order {$externalOrderId} is currently being processed by another transaction. Please try again later."]
]);
}
try {
// 冪等性處理:若訂單已存在,回傳已建立的訂單資訊
$existingOrder = SalesOrder::where('external_order_id', $externalOrderId)->first();
if ($existingOrder) {
return [
'status' => 'exists',
'message' => 'Order already exists',
'order_id' => $existingOrder->id,
];
}
// --- 預檢:以 ERP 商品代碼查詢 ---
$items = $data['items'];
$productCodes = array_column($items, 'product_code');
// 一次性查出所有相關的 Product以 code 查詢)
$products = $this->productService->findByCodes($productCodes)->keyBy('code');
$missingCodes = [];
foreach ($productCodes as $code) {
if (!$products->has($code)) {
$missingCodes[] = $code;
}
}
if (!empty($missingCodes)) {
throw ValidationException::withMessages([
'items' => ["The following products are not found by code: " . implode(', ', $missingCodes) . ". Please ensure these products exist in the system."]
]);
}
// --- 執行寫入交易 ---
$result = DB::transaction(function () use ($data, $items, $products) {
// 1. 建立訂單
$order = SalesOrder::create([
'external_order_id' => $data['external_order_id'],
'status' => 'completed',
'payment_method' => $data['payment_method'] ?? 'electronic',
'total_amount' => 0,
'sold_at' => $data['sold_at'] ?? now(),
'raw_payload' => $data,
'source' => 'vending',
'source_label' => $data['machine_id'] ?? null,
]);
// 2. 查找倉庫
$warehouseCode = $data['warehouse_code'];
$warehouses = $this->inventoryService->getWarehousesByCodes([$warehouseCode]);
if ($warehouses->isEmpty()) {
throw ValidationException::withMessages([
'warehouse_code' => ["Warehouse with code {$warehouseCode} not found."]
]);
}
$warehouseId = $warehouses->first()->id;
$totalAmount = 0;
// 3. 處理訂單明細
$orderItemsData = [];
foreach ($items as $itemData) {
$product = $products->get($itemData['product_code']);
$qty = $itemData['qty'];
$price = $itemData['price'];
$lineTotal = $qty * $price;
$totalAmount += $lineTotal;
$orderItemsData[] = [
'sales_order_id' => $order->id,
'product_id' => $product->id,
'product_name' => $product->name,
'quantity' => $qty,
'price' => $price,
'total' => $lineTotal,
'created_at' => now(),
'updated_at' => now(),
];
// 4. 扣除庫存(強制模式,允許負庫存)
$this->inventoryService->decreaseStock(
$product->id,
$warehouseId,
$qty,
"Vending Order: " . $order->external_order_id,
true
);
}
// Batch insert order items
SalesOrderItem::insert($orderItemsData);
$order->update(['total_amount' => $totalAmount]);
return [
'status' => 'created',
'message' => 'Vending order synced and stock deducted successfully',
'order_id' => $order->id,
];
});
return $result;
} finally {
$lock->release();
}
}
}