feat: API調整訂單與販賣機訂單同步強制使用warehouse_code,更新API對接文件,及優化生產與配方模組UI顯示
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 55s

This commit is contained in:
2026-03-03 14:28:15 +08:00
parent 58bd995cd8
commit 183583c739
19 changed files with 486 additions and 89 deletions

View File

@@ -93,14 +93,16 @@ class SyncOrderAction
'source_label' => $data['source_label'] ?? null,
]);
// 2. 查找或建立倉庫
$warehouseId = $data['warehouse_id'] ?? null;
if (empty($warehouseId)) {
$warehouseName = $data['warehouse'] ?? '銷售倉庫';
$warehouse = $this->inventoryService->findOrCreateWarehouseByName($warehouseName);
$warehouseId = $warehouse->id;
// 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;

View File

@@ -90,14 +90,16 @@ class SyncVendingOrderAction
'source_label' => $data['machine_id'] ?? null,
]);
// 2. 查找或建立倉庫
$warehouseId = $data['warehouse_id'] ?? null;
if (empty($warehouseId)) {
$warehouseName = $data['warehouse'] ?? '販賣機倉庫';
$warehouse = $this->inventoryService->findOrCreateWarehouseByName($warehouseName);
$warehouseId = $warehouse->id;
// 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;

View File

@@ -23,8 +23,7 @@ class SyncOrderRequest extends FormRequest
{
return [
'external_order_id' => 'required|string',
'warehouse' => 'nullable|string',
'warehouse_id' => 'nullable|integer',
'warehouse_code' => 'required|string',
'payment_method' => 'nullable|string|in:cash,credit_card,line_pay,ecpay,transfer,other',
'sold_at' => 'nullable|date',
'items' => 'required|array|min:1',

View File

@@ -24,8 +24,7 @@ class SyncVendingOrderRequest extends FormRequest
return [
'external_order_id' => 'required|string',
'machine_id' => 'nullable|string',
'warehouse' => 'nullable|string',
'warehouse_id' => 'nullable|integer',
'warehouse_code' => 'required|string',
'payment_method' => 'nullable|string|in:cash,electronic,line_pay,other',
'sold_at' => 'nullable|date',
'items' => 'required|array|min:1',

View File

@@ -67,21 +67,46 @@ class ProductionOrderController extends Controller
if (!in_array((int)$perPage, [10, 20, 50, 100])) {
$perPage = $defaultPerPage;
}
$productionOrders = $query->paginate($perPage)->withQueryString();
$productionOrders = $query->with('items')->paginate($perPage)->withQueryString();
// --- 手動資料水和 (Manual Hydration) ---
$productIds = $productionOrders->pluck('product_id')->unique()->filter()->toArray();
$warehouseIds = $productionOrders->pluck('warehouse_id')->unique()->filter()->toArray();
$userIds = $productionOrders->pluck('user_id')->unique()->filter()->toArray();
$productIds = collect();
$warehouseIds = collect();
$userIds = collect();
$inventoryIds = collect();
$products = $this->inventoryService->getProductsByIds($productIds)->keyBy('id');
$warehouses = $this->inventoryService->getAllWarehouses()->whereIn('id', $warehouseIds)->keyBy('id');
$users = $this->coreService->getUsersByIds($userIds)->keyBy('id');
foreach ($productionOrders as $order) {
$productIds->push($order->product_id);
$warehouseIds->push($order->warehouse_id);
$userIds->push($order->user_id);
if ($order->items) {
$inventoryIds = $inventoryIds->merge($order->items->pluck('inventory_id'));
}
}
$productionOrders->getCollection()->transform(function ($order) use ($products, $warehouses, $users) {
$products = $this->inventoryService->getProductsByIds($productIds->unique()->filter()->toArray())->keyBy('id');
$warehouses = $this->inventoryService->getAllWarehouses()->whereIn('id', $warehouseIds->unique()->filter()->toArray())->keyBy('id');
$users = $this->coreService->getUsersByIds($userIds->unique()->filter()->toArray())->keyBy('id');
$inventories = $this->inventoryService->getInventoriesByIds($inventoryIds->unique()->filter()->toArray())->keyBy('id');
$productionOrders->getCollection()->transform(function ($order) use ($products, $warehouses, $users, $inventories) {
$order->product = $products->get($order->product_id);
$order->warehouse = $warehouses->get($order->warehouse_id);
$order->user = $users->get($order->user_id);
$totalCost = 0;
if ($order->items) {
foreach ($order->items as $item) {
$inventory = $inventories->get($item->inventory_id);
if ($inventory) {
$totalCost += $item->quantity_used * ($inventory->unit_cost ?? 0);
}
}
}
$order->estimated_total_cost = $totalCost;
$order->estimated_unit_cost = $order->output_quantity > 0 ? $totalCost / $order->output_quantity : 0;
unset($order->items);
return $order;
});
@@ -231,7 +256,9 @@ class ProductionOrderController extends Controller
'unit_name' => $inv->product->baseUnit->name ?? '',
'base_unit_id' => $inv->product->base_unit_id ?? null,
'large_unit_id' => $inv->product->large_unit_id ?? null,
'purchase_unit_id' => $inv->product->purchase_unit_id ?? null,
'conversion_rate' => $inv->product->conversion_rate ?? 1,
'unit_cost' => (float) $inv->unit_cost,
];
});
@@ -258,7 +285,10 @@ class ProductionOrderController extends Controller
'expiry_date' => $inv->expiry_date ? $inv->expiry_date->format('Y-m-d') : null,
'unit_name' => $inv->product->baseUnit->name ?? '',
'base_unit_id' => $inv->product->base_unit_id ?? null,
'large_unit_id' => $inv->product->large_unit_id ?? null,
'purchase_unit_id' => $inv->product->purchase_unit_id ?? null,
'conversion_rate' => $inv->product->conversion_rate ?? 1,
'unit_cost' => (float) $inv->unit_cost,
];
});

View File

@@ -46,14 +46,51 @@ class RecipeController extends Controller
$perPage = $defaultPerPage;
}
$recipes = $query->paginate($perPage)->withQueryString();
$recipes = $query->with('items')->paginate($perPage)->withQueryString();
// Manual Hydration
$productIds = $recipes->pluck('product_id')->unique()->filter()->toArray();
$products = $this->inventoryService->getProductsByIds($productIds)->keyBy('id');
$productIds = collect();
$itemProductIds = collect();
foreach ($recipes as $recipe) {
$productIds->push($recipe->product_id);
if ($recipe->items) {
$itemProductIds = $itemProductIds->merge($recipe->items->pluck('product_id'));
}
}
$allProductIds = array_unique(array_merge(
$productIds->unique()->filter()->toArray(),
$itemProductIds->unique()->filter()->toArray()
));
$products = $this->inventoryService->getProductsByIds($allProductIds)->keyBy('id');
$recipes->getCollection()->transform(function ($recipe) use ($products) {
$recipe->product = $products->get($recipe->product_id);
$totalCost = 0;
if ($recipe->items) {
foreach ($recipe->items as $item) {
$itemProduct = $products->get($item->product_id);
if ($itemProduct) {
$baseCost = $itemProduct->cost_price ?? 0;
$conversionRate = 1;
if ($item->unit_id == $itemProduct->large_unit_id && !is_null($itemProduct->conversion_rate)) {
$conversionRate = $itemProduct->conversion_rate;
} elseif ($item->unit_id == $itemProduct->purchase_unit_id && !is_null($itemProduct->conversion_rate_purchase)) {
$conversionRate = $itemProduct->conversion_rate_purchase;
}
$totalCost += ($item->quantity * $baseCost * $conversionRate);
}
}
}
$recipe->estimated_total_cost = $totalCost;
$recipe->estimated_unit_cost = $recipe->yield_quantity > 0 ? $totalCost / $recipe->yield_quantity : 0;
unset($recipe->items);
return $recipe;
});