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

@@ -48,7 +48,9 @@ interface InventoryOption {
base_unit_name?: string;
large_unit_id?: number;
large_unit_name?: string;
purchase_unit_id?: number;
conversion_rate?: number;
unit_cost?: number;
}
interface BomItem {
@@ -73,6 +75,8 @@ interface BomItem {
ui_large_unit_name?: string;
ui_base_unit_id?: number;
ui_large_unit_id?: number;
ui_purchase_unit_id?: number;
ui_unit_cost?: number;
}
interface Props {
@@ -203,7 +207,10 @@ export default function Create({ products, warehouses }: Props) {
// 單位與轉換率
item.ui_base_unit_name = inv.unit_name || '';
item.ui_base_unit_id = inv.base_unit_id;
item.ui_large_unit_id = inv.large_unit_id;
item.ui_purchase_unit_id = inv.purchase_unit_id;
item.ui_conversion_rate = inv.conversion_rate || 1;
item.ui_unit_cost = inv.unit_cost || 0;
// 預設單位
item.ui_selected_unit = 'base';
@@ -249,7 +256,7 @@ export default function Create({ products, warehouses }: Props) {
const yieldQty = parseFloat(recipe.yield_quantity || "0") || 1;
// 自動帶入配方標準產量
setData('output_quantity', String(yieldQty));
setData('output_quantity', formatQuantity(yieldQty));
const newBomItems: BomItem[] = recipe.items.map((item: any) => {
const baseQty = parseFloat(item.quantity || "0");
@@ -269,7 +276,7 @@ export default function Create({ products, warehouses }: Props) {
ui_product_name: item.product_name,
ui_batch_number: "",
ui_available_qty: 0,
ui_input_quantity: String(calculatedQty),
ui_input_quantity: formatQuantity(calculatedQty),
ui_selected_unit: 'base',
ui_base_unit_name: item.unit_name,
ui_base_unit_id: item.unit_id,
@@ -279,7 +286,7 @@ export default function Create({ products, warehouses }: Props) {
setBomItems(newBomItems);
toast.success(`已自動載入配方: ${recipe.name}`, {
description: `標準產量: ${yieldQty}`
description: `標準產量: ${formatQuantity(yieldQty)}`
});
};
@@ -380,6 +387,24 @@ export default function Create({ products, warehouses }: Props) {
submit('completed');
};
const getBomItemUnitCost = (item: BomItem) => {
if (!item.ui_unit_cost) return 0;
let cost = Number(item.ui_unit_cost);
// Check if selected unit is large_unit or purchase_unit
if (item.unit_id && (String(item.unit_id) === String(item.ui_large_unit_id) || String(item.unit_id) === String(item.ui_purchase_unit_id))) {
cost = cost * Number(item.ui_conversion_rate || 1);
}
return cost;
};
const totalEstimatedCost = bomItems.reduce((sum, item) => {
if (!item.ui_input_quantity || !item.ui_unit_cost) return sum;
const inputQty = parseFloat(item.ui_input_quantity || '0');
const unitCost = getBomItemUnitCost(item);
return sum + (unitCost * inputQty);
}, 0);
return (
<AuthenticatedLayout breadcrumbs={getBreadcrumbs("productionOrdersCreate")}>
<Head title="建立生產單" />
@@ -529,10 +554,12 @@ export default function Create({ products, warehouses }: Props) {
<TableRow className="bg-gray-50/50">
<TableHead className="w-[18%]"> <span className="text-red-500">*</span></TableHead>
<TableHead className="w-[15%]"> <span className="text-red-500">*</span></TableHead>
<TableHead className="w-[30%]"> <span className="text-red-500">*</span></TableHead>
<TableHead className="w-[20%]"> <span className="text-red-500">*</span></TableHead>
<TableHead className="w-[12%]"> <span className="text-red-500">*</span></TableHead>
<TableHead className="w-[10%]"></TableHead>
<TableHead className="w-[10%]"></TableHead>
<TableHead className="w-[10%] text-right"></TableHead>
<TableHead className="w-[10%] text-right"></TableHead>
<TableHead className="w-[5%]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -560,7 +587,7 @@ export default function Create({ products, warehouses }: Props) {
.map((inv: InventoryOption) => ({
label: inv.batch_number,
value: String(inv.id),
sublabel: `(存:${inv.quantity} | 效:${inv.expiry_date || '無'})`
sublabel: `(存:${formatQuantity(inv.quantity)} | 效:${inv.expiry_date || '無'})`
}));
return (
@@ -638,6 +665,19 @@ export default function Create({ products, warehouses }: Props) {
</div>
</TableCell>
{/* 5. 預估單價 */}
<TableCell className="align-top text-right">
<div className="h-9 flex items-center justify-end px-1 text-sm text-gray-600 font-medium">
{getBomItemUnitCost(item).toLocaleString(undefined, { maximumFractionDigits: 2 })}
</div>
</TableCell>
{/* 6. 成本小計 */}
<TableCell className="align-top text-right">
<div className="h-9 flex items-center justify-end px-1 text-sm font-bold text-gray-900">
{(getBomItemUnitCost(item) * parseFloat(item.ui_input_quantity || '0')).toLocaleString(undefined, { maximumFractionDigits: 2 })}
</div>
</TableCell>
<TableCell className="align-top">
<Button
@@ -660,9 +700,27 @@ export default function Create({ products, warehouses }: Props) {
)}
{errors.items && <p className="text-red-500 text-sm mt-2">{errors.items}</p>}
{/* 生產單預估總成本區塊 */}
<div className="mt-6 flex justify-end">
<div className="bg-gray-50 p-4 rounded-lg border border-gray-200 min-w-[300px]">
<div className="flex justify-between items-center mb-2">
<span className="text-sm text-gray-600"></span>
<span className="text-lg font-bold text-gray-900">{totalEstimatedCost.toLocaleString(undefined, { maximumFractionDigits: 2 })} </span>
</div>
<div className="flex justify-between items-center pt-2 border-t border-gray-200">
<span className="text-sm font-medium text-gray-700">
<span className="text-xs text-gray-500 ml-1">( {Number(data.output_quantity || 0).toLocaleString(undefined, { maximumFractionDigits: 4 })} )</span>
</span>
<span className="text-md font-bold text-primary-main">
{(parseFloat(data.output_quantity) > 0 ? totalEstimatedCost / parseFloat(data.output_quantity) : 0).toLocaleString(undefined, { maximumFractionDigits: 2 })}
</span>
</div>
</div>
</div>
</div>
</form>
</div>
</AuthenticatedLayout>
</AuthenticatedLayout >
);
}