feat: API調整訂單與販賣機訂單同步強制使用warehouse_code,更新API對接文件,及優化生產與配方模組UI顯示
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 55s
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 55s
This commit is contained in:
@@ -51,7 +51,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 {
|
||||
@@ -76,7 +78,9 @@ interface BomItem {
|
||||
ui_large_unit_name?: string;
|
||||
ui_base_unit_id?: number;
|
||||
ui_large_unit_id?: number;
|
||||
ui_purchase_unit_id?: number;
|
||||
ui_product_code?: string;
|
||||
ui_unit_cost?: number;
|
||||
}
|
||||
|
||||
interface ProductionOrderItem {
|
||||
@@ -165,6 +169,7 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
ui_batch_number: item.inventory?.batch_number,
|
||||
ui_available_qty: item.inventory?.quantity,
|
||||
ui_expiry_date: item.inventory?.expiry_date,
|
||||
ui_unit_cost: 0,
|
||||
}));
|
||||
const [bomItems, setBomItems] = useState<BomItem[]>(initialBomItems);
|
||||
|
||||
@@ -203,7 +208,9 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
ui_large_unit_name: inv.large_unit_name || '',
|
||||
ui_base_unit_id: inv.base_unit_id,
|
||||
ui_large_unit_id: inv.large_unit_id,
|
||||
ui_purchase_unit_id: inv.purchase_unit_id,
|
||||
ui_conversion_rate: inv.conversion_rate || 1,
|
||||
ui_unit_cost: inv.unit_cost || 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -277,7 +284,9 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
item.ui_large_unit_name = inv.large_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';
|
||||
item.unit_id = String(inv.base_unit_id || '');
|
||||
@@ -365,6 +374,24 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
submit('draft');
|
||||
};
|
||||
|
||||
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={`編輯生產單 - ${productionOrder.code}`} />
|
||||
@@ -492,10 +519,12 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
<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-[15%]">數量 <span className="text-red-500">*</span></TableHead>
|
||||
<TableHead className="w-[12%]">單位</TableHead>
|
||||
<TableHead className="w-[10%]"></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%] text-right">預估單價</TableHead>
|
||||
<TableHead className="w-[10%] text-right">成本小計</TableHead>
|
||||
<TableHead className="w-[5%]"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -523,7 +552,7 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
.map((inv: InventoryOption) => ({
|
||||
label: inv.batch_number,
|
||||
value: String(inv.id),
|
||||
sublabel: `(存:${inv.quantity} | 效:${inv.expiry_date || '無'})`
|
||||
sublabel: `(存:${formatQuantity(inv.quantity)} | 效:${inv.expiry_date || '無'})`
|
||||
}));
|
||||
|
||||
const displayBatchOptions = batchOptions.length > 0 ? batchOptions : (item.inventory_id && item.ui_batch_number ? [{ label: item.ui_batch_number, value: item.inventory_id }] : []);
|
||||
@@ -598,6 +627,18 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<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>
|
||||
|
||||
<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
|
||||
type="button"
|
||||
@@ -621,8 +662,24 @@ export default function ProductionEdit({ productionOrder, products, warehouses }
|
||||
{errors.items && <p className="text-red-500 text-sm mt-2">{errors.items}</p>}
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
</AuthenticatedLayout >
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user