first commit

This commit is contained in:
2025-12-30 15:03:19 +08:00
commit c735c36009
902 changed files with 83591 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
/**
* 驗收流程管理 Hook
*/
import { useState, useEffect } from "react";
import type { PurchaseOrder, InspectionItem } from "../types/purchase-order";
interface UseInspectionProps {
order?: PurchaseOrder;
}
export function useInspection({ order }: UseInspectionProps) {
const [inspectionItems, setInspectionItems] = useState<InspectionItem[]>([]);
// 極速驗收模式:預設所有實際到貨數量 = 應到貨數量
useEffect(() => {
if (order) {
setInspectionItems(
order.items.map((item) => ({
...item,
receivedQuantity: item.quantity,
damagedQuantity: 0,
shortageQuantity: 0,
issueType: "none",
}))
);
}
}, [order]);
// 更新實際收貨數量
const updateReceivedQuantity = (index: number, value: number) => {
const newItems = [...inspectionItems];
const item = newItems[index];
const expectedQty = item.quantity;
item.receivedQuantity = value;
// 自動計算短缺數量
item.shortageQuantity = expectedQty - value;
// 判斷是否有異常(實際收貨 < 應到貨)
item.issueType = value < expectedQty ? "shortage" : "none";
setInspectionItems(newItems);
};
// 更新異常說明
const updateIssueNote = (index: number, note: string) => {
const newItems = [...inspectionItems];
newItems[index].issueNote = note;
setInspectionItems(newItems);
};
// 計算統計資訊
const statistics = {
hasIssues: inspectionItems.some((item) => item.issueType !== "none"),
issueItems: inspectionItems.filter((item) => item.issueType !== "none"),
damagedItems: 0, // 不再追蹤損壞數量
shortageItems: inspectionItems.filter((item) => item.shortageQuantity > 0).length,
totalExpectedAmount: order?.totalAmount || 0,
totalReceivedAmount: inspectionItems.reduce(
(sum, item) => sum + item.receivedQuantity * item.unitPrice,
0
),
};
return {
inspectionItems,
statistics,
updateReceivedQuantity,
updateIssueNote,
};
}

View File

@@ -0,0 +1,126 @@
/**
* 採購單表單管理 Hook
*/
import { useState, useEffect } from "react";
import type { PurchaseOrder, PurchaseOrderItem, Supplier, PurchaseOrderStatus, RequesterType } from "../types/purchase-order";
import { calculateSubtotal } from "../utils/purchase-order";
interface UsePurchaseOrderFormProps {
order?: PurchaseOrder;
suppliers: Supplier[];
}
export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormProps) {
const [supplierId, setSupplierId] = useState("");
const [expectedDate, setExpectedDate] = useState("");
const [items, setItems] = useState<PurchaseOrderItem[]>([]);
const [notes, setNotes] = useState("");
const [status, setStatus] = useState<PurchaseOrderStatus>("draft");
const [requesterType, setRequesterType] = useState<RequesterType>("store");
const [requesterId, setRequesterId] = useState("");
// 載入編輯訂單資料
useEffect(() => {
if (order) {
setSupplierId(order.supplierId);
setExpectedDate(order.expectedDate);
setItems(order.items);
setNotes(order.notes || "");
setStatus(order.status);
setRequesterType(order.requesterType || "store");
setRequesterId(order.requesterId || "");
} else {
resetForm();
}
}, [order]);
const resetForm = () => {
setSupplierId("");
setExpectedDate("");
setItems([]);
setNotes("");
setStatus("draft");
setRequesterType("store");
setRequesterId("");
};
const selectedSupplier = suppliers.find((s) => s.id === supplierId);
const isOrderSent = order && order.status !== "draft";
// 新增商品項目
const addItem = () => {
if (!selectedSupplier) return;
setItems([
...items,
{
productId: "",
productName: "",
quantity: 0,
unit: "",
unitPrice: 0,
subtotal: 0,
},
]);
};
// 移除商品項目
const removeItem = (index: number) => {
setItems(items.filter((_, i) => i !== index));
};
// 更新商品項目
const updateItem = (index: number, field: keyof PurchaseOrderItem, value: string | number) => {
const newItems = [...items];
newItems[index] = { ...newItems[index], [field]: value };
// 當選擇商品時,自動填入商品資訊
if (field === "productId" && selectedSupplier) {
const product = selectedSupplier.commonProducts.find((p) => p.productId === value);
if (product) {
newItems[index].productName = product.productName;
newItems[index].unit = product.unit;
newItems[index].unitPrice = product.lastPrice;
newItems[index].previousPrice = product.lastPrice;
}
}
// 計算小計
if (field === "quantity" || field === "unitPrice") {
newItems[index].subtotal = calculateSubtotal(
newItems[index].quantity,
newItems[index].unitPrice
);
}
setItems(newItems);
};
return {
// State
supplierId,
expectedDate,
items,
notes,
status,
selectedSupplier,
isOrderSent,
requesterType,
requesterId,
// Setters
setSupplierId,
setExpectedDate,
setNotes,
setStatus,
setRequesterType,
setRequesterId,
// Methods
addItem,
removeItem,
updateItem,
resetForm,
};
}