Files
star-erp/resources/js/hooks/usePurchaseOrderForm.ts

198 lines
7.1 KiB
TypeScript
Raw Normal View History

2025-12-30 15:03:19 +08:00
/**
* Hook
*/
import { useState, useEffect } from "react";
import type { PurchaseOrder, PurchaseOrderItem, Supplier, PurchaseOrderStatus } from "@/types/purchase-order";
import { calculateSubtotal, getTodayDate } from "@/utils/purchase-order";
2025-12-30 15:03:19 +08:00
interface UsePurchaseOrderFormProps {
order?: PurchaseOrder;
suppliers: Supplier[];
}
export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormProps) {
const [supplierId, setSupplierId] = useState(order?.supplierId || "");
const [expectedDate, setExpectedDate] = useState(order?.expectedDate || "");
const [orderDate, setOrderDate] = useState(order?.orderDate || getTodayDate());
const [items, setItems] = useState<PurchaseOrderItem[]>(order?.items || []);
const [notes, setNotes] = useState(order?.remark || "");
const [status, setStatus] = useState<PurchaseOrderStatus>(order?.status || "draft");
const [warehouseId, setWarehouseId] = useState<string | number>(order?.warehouse_id || "");
const [invoiceNumber, setInvoiceNumber] = useState(order?.invoiceNumber || "");
const [invoiceDate, setInvoiceDate] = useState(order?.invoiceDate || "");
const [invoiceAmount, setInvoiceAmount] = useState(order?.invoiceAmount ? String(order.invoiceAmount) : "");
const [taxAmount, setTaxAmount] = useState<string | number>(
order?.taxAmount !== undefined && order.taxAmount !== null ? order.taxAmount :
(order?.tax_amount !== undefined && order.tax_amount !== null ? order.tax_amount : "")
);
const [isTaxManual, setIsTaxManual] = useState(!!(order?.taxAmount !== undefined || order?.tax_amount !== undefined));
// 同步外部傳入的 order 更新 (例如重新執行 edit 路由)
2025-12-30 15:03:19 +08:00
useEffect(() => {
if (order) {
setSupplierId(order.supplierId);
setExpectedDate(order.expectedDate);
setOrderDate(order.orderDate || getTodayDate());
setItems(order.items || []);
2025-12-30 15:03:19 +08:00
setNotes(order.remark || "");
setStatus(order.status);
setWarehouseId(order.warehouse_id || "");
setInvoiceNumber(order.invoiceNumber || "");
setInvoiceDate(order.invoiceDate || "");
setInvoiceAmount(order.invoiceAmount ? String(order.invoiceAmount) : "");
const val = order.taxAmount !== undefined && order.taxAmount !== null ? order.taxAmount :
(order.tax_amount !== undefined && order.tax_amount !== null ? order.tax_amount : "");
setTaxAmount(val);
if (val !== "") {
setIsTaxManual(true);
}
2025-12-30 15:03:19 +08:00
}
}, [order]);
const resetForm = () => {
setSupplierId("");
setExpectedDate("");
setOrderDate(getTodayDate());
2025-12-30 15:03:19 +08:00
setItems([]);
setNotes("");
setStatus("draft");
setWarehouseId("");
setInvoiceNumber("");
setInvoiceDate("");
setInvoiceAmount("");
setTaxAmount("");
setIsTaxManual(false);
2025-12-30 15:03:19 +08:00
};
const selectedSupplier = suppliers.find((s) => String(s.id) === String(supplierId));
const isOrderSent = order && order.status !== "draft";
// 新增商品項目
const addItem = () => {
if (!selectedSupplier) return;
setItems([
...items,
{
productId: "",
productName: "",
2026-01-08 16:32:10 +08:00
quantity: 1,
2025-12-30 15:03:19 +08:00
unitPrice: 0,
subtotal: 0,
2026-01-08 16:32:10 +08:00
selectedUnit: "base",
2025-12-30 15:03:19 +08:00
},
]);
};
// 移除商品項目
const removeItem = (index: number) => {
setItems(items.filter((_, i) => i !== index));
};
// 更新商品項目
2026-01-08 16:32:10 +08:00
const updateItem = (index: number, field: keyof PurchaseOrderItem, value: any) => {
2025-12-30 15:03:19 +08:00
const newItems = [...items];
2026-01-08 16:32:10 +08:00
const item = { ...newItems[index] };
2025-12-30 15:03:19 +08:00
if (field === "productId" && selectedSupplier) {
2026-01-08 16:32:10 +08:00
// value is productId string
2025-12-30 15:03:19 +08:00
const product = selectedSupplier.commonProducts.find((p) => p.productId === value);
if (product) {
2026-01-08 16:32:10 +08:00
// @ts-ignore
item.productId = value;
item.productName = product.productName;
item.base_unit_id = product.base_unit_id;
item.base_unit_name = product.base_unit_name;
item.large_unit_id = product.large_unit_id;
item.large_unit_name = product.large_unit_name;
item.purchase_unit_id = product.purchase_unit_id;
item.conversion_rate = product.conversion_rate;
item.unitPrice = product.lastPrice;
item.previousPrice = product.lastPrice;
// 決定預設單位
const isPurchaseUnitLarge = product.purchase_unit_id && product.large_unit_id && product.purchase_unit_id === product.large_unit_id;
if (isPurchaseUnitLarge) {
item.selectedUnit = 'large';
item.unitId = product.large_unit_id;
} else {
item.selectedUnit = 'base';
item.unitId = product.base_unit_id;
}
2026-01-08 17:51:06 +08:00
// 初始小計 = 數量 * 單價
item.subtotal = calculateSubtotal(Number(item.quantity), Number(item.unitPrice));
2025-12-30 15:03:19 +08:00
}
2026-01-08 16:32:10 +08:00
} else if (field === "selectedUnit") {
// @ts-ignore
item.selectedUnit = value;
if (value === 'large') {
item.unitId = item.large_unit_id;
} else {
item.unitId = item.base_unit_id;
}
2026-01-08 17:51:06 +08:00
// Switch unit doesn't change Total Amount (Subtotal), but implies Unit Price changes?
// Actually if I switch unit, the Quantity is usually for that unit.
// If I have 1 Box ($100), and switch to Pc. Quantity is still 1.
// Total is $100. So Unit Price (per Pc) becomes $100.
// This seems safely consistent with "Total Amount" anchor.
2026-01-08 16:32:10 +08:00
} else {
// @ts-ignore
item[field] = value;
2025-12-30 15:03:19 +08:00
}
2026-01-08 17:51:06 +08:00
// 重新計算 (Always derive UnitPrice from Subtotal and Quantity)
// 除了剛剛已經算過 subtotal 的 productId case
if (field !== "productId") {
if (item.quantity > 0) {
item.unitPrice = Number(item.subtotal) / Number(item.quantity);
} else {
item.unitPrice = 0;
}
2025-12-30 15:03:19 +08:00
}
2026-01-08 16:32:10 +08:00
newItems[index] = item;
2025-12-30 15:03:19 +08:00
setItems(newItems);
};
return {
// State
supplierId,
expectedDate,
orderDate,
2025-12-30 15:03:19 +08:00
items,
notes,
status,
selectedSupplier,
isOrderSent,
warehouseId,
invoiceNumber,
invoiceDate,
invoiceAmount,
taxAmount,
isTaxManual,
2025-12-30 15:03:19 +08:00
// Setters
setSupplierId,
setExpectedDate,
setOrderDate,
2025-12-30 15:03:19 +08:00
setNotes,
setStatus,
setWarehouseId,
setInvoiceNumber,
setInvoiceDate,
setInvoiceAmount,
setTaxAmount,
setIsTaxManual,
2025-12-30 15:03:19 +08:00
// Methods
addItem,
removeItem,
updateItem,
resetForm,
};
}