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:
@@ -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 >
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user