first commit
This commit is contained in:
609
source-code/ERP(B-aa)-管理採購單/src/App.tsx
Normal file
609
source-code/ERP(B-aa)-管理採購單/src/App.tsx
Normal file
@@ -0,0 +1,609 @@
|
||||
import { useState } from "react";
|
||||
import { Toaster, toast } from "sonner@2.0.3";
|
||||
import PurchaseOrderManagement from "./components/PurchaseOrderManagement";
|
||||
import CreatePurchaseOrderPage from "./components/CreatePurchaseOrderPage";
|
||||
import InspectionPage from "./components/InspectionPage";
|
||||
import ViewPurchaseOrderPage from "./components/ViewPurchaseOrderPage";
|
||||
import NavigationSidebar from "./components/NavigationSidebar";
|
||||
import type { PurchaseOrder, PurchaseOrderStatus, Supplier, PurchaseOrderItem, PaymentInfo } from "./types/purchase-order";
|
||||
import type { Store, Warehouse } from "./types/requester";
|
||||
import { STATUS_CONFIG } from "./constants/purchase-order";
|
||||
|
||||
// Mock suppliers data
|
||||
const mockSuppliers: Supplier[] = [
|
||||
{
|
||||
id: "sup-1",
|
||||
name: "美食材料供應商",
|
||||
contact: "王小明",
|
||||
phone: "02-2345-6789",
|
||||
email: "supplier1@example.com",
|
||||
commonProducts: [
|
||||
{ productId: "prod-1", productName: "高筋麵粉", unit: "公斤", lastPrice: 45 },
|
||||
{ productId: "prod-2", productName: "無鹽奶油", unit: "公斤", lastPrice: 280 },
|
||||
{ productId: "prod-3", productName: "細砂糖", unit: "公斤", lastPrice: 35 },
|
||||
{ productId: "prod-4", productName: "雞蛋", unit: "打", lastPrice: 65 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "sup-2",
|
||||
name: "優質乳製品公司",
|
||||
contact: "李美玲",
|
||||
phone: "02-3456-7890",
|
||||
email: "dairy@example.com",
|
||||
commonProducts: [
|
||||
{ productId: "prod-5", productName: "鮮奶油", unit: "公升", lastPrice: 180 },
|
||||
{ productId: "prod-6", productName: "馬斯卡彭起司", unit: "公斤", lastPrice: 450 },
|
||||
{ productId: "prod-7", productName: "鮮奶", unit: "公升", lastPrice: 55 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "sup-3",
|
||||
name: "進口巧克力專賣店",
|
||||
contact: "張大華",
|
||||
phone: "02-4567-8901",
|
||||
email: "chocolate@example.com",
|
||||
commonProducts: [
|
||||
{ productId: "prod-8", productName: "70%黑巧克力", unit: "公斤", lastPrice: 520 },
|
||||
{ productId: "prod-9", productName: "白巧克力", unit: "公斤", lastPrice: 480 },
|
||||
{ productId: "prod-10", productName: "可可粉", unit: "公斤", lastPrice: 380 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Mock stores data
|
||||
const mockStores: Store[] = [
|
||||
{
|
||||
id: "store-1",
|
||||
name: "總店",
|
||||
address: "台北市大安區信義路四段100號",
|
||||
manager: "王小華",
|
||||
phone: "02-2700-1000",
|
||||
},
|
||||
{
|
||||
id: "store-2",
|
||||
name: "A 店(東區店)",
|
||||
address: "台北市大安區忠孝東路四段200號",
|
||||
manager: "李美玲",
|
||||
phone: "02-2771-2000",
|
||||
},
|
||||
{
|
||||
id: "store-3",
|
||||
name: "B 店(西門店)",
|
||||
address: "台北市萬華區成都路50號",
|
||||
manager: "張大明",
|
||||
phone: "02-2388-3000",
|
||||
},
|
||||
{
|
||||
id: "store-4",
|
||||
name: "C 店(天母店)",
|
||||
address: "台北市士林區中山北路七段150號",
|
||||
manager: "周小姐",
|
||||
phone: "02-2876-4000",
|
||||
},
|
||||
];
|
||||
|
||||
// Mock warehouses data
|
||||
const mockWarehouses: Warehouse[] = [
|
||||
{
|
||||
id: "warehouse-1",
|
||||
name: "中央倉庫",
|
||||
address: "新北市新莊區中正路500號",
|
||||
manager: "劉主管",
|
||||
phone: "02-2990-5000",
|
||||
},
|
||||
{
|
||||
id: "warehouse-2",
|
||||
name: "南區倉庫",
|
||||
address: "台中市西屯區工業區一路200號",
|
||||
manager: "黃主管",
|
||||
phone: "04-2350-6000",
|
||||
},
|
||||
];
|
||||
|
||||
const initialPurchaseOrders: PurchaseOrder[] = [
|
||||
{
|
||||
id: "po-1",
|
||||
poNumber: "PO202411001",
|
||||
supplierId: "sup-1",
|
||||
supplierName: "美食材料供應商",
|
||||
expectedDate: "2024-11-25",
|
||||
status: "pending_confirm",
|
||||
createdBy: "王小華",
|
||||
requesterType: "store",
|
||||
requesterId: "store-1",
|
||||
requesterName: "總店",
|
||||
department: "總店",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-1",
|
||||
productName: "高筋麵粉",
|
||||
quantity: 50,
|
||||
unit: "公斤",
|
||||
unitPrice: 45,
|
||||
previousPrice: 45,
|
||||
subtotal: 2250,
|
||||
},
|
||||
{
|
||||
productId: "prod-3",
|
||||
productName: "細砂糖",
|
||||
quantity: 30,
|
||||
unit: "公斤",
|
||||
unitPrice: 35,
|
||||
previousPrice: 35,
|
||||
subtotal: 1050,
|
||||
},
|
||||
],
|
||||
totalAmount: 3300,
|
||||
createdAt: "2024-11-18",
|
||||
reviewInfo: {
|
||||
reviewedBy: "張主管",
|
||||
reviewedAt: "2024-11-19 10:30",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "po-2",
|
||||
poNumber: "PO202411002",
|
||||
supplierId: "sup-2",
|
||||
supplierName: "優質乳製品公司",
|
||||
expectedDate: "2024-11-26",
|
||||
status: "shipping",
|
||||
createdBy: "李美玲",
|
||||
requesterType: "store",
|
||||
requesterId: "store-2",
|
||||
requesterName: "A 店(東區店)",
|
||||
department: "A 店",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-5",
|
||||
productName: "鮮奶油",
|
||||
quantity: 20,
|
||||
unit: "公升",
|
||||
unitPrice: 180,
|
||||
previousPrice: 180,
|
||||
subtotal: 3600,
|
||||
},
|
||||
],
|
||||
totalAmount: 3600,
|
||||
createdAt: "2024-11-19",
|
||||
reviewInfo: {
|
||||
reviewedBy: "陳主管",
|
||||
reviewedAt: "2024-11-20 14:15",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "po-3",
|
||||
poNumber: "PO202411003",
|
||||
supplierId: "sup-3",
|
||||
supplierName: "進口巧克力專賣店",
|
||||
expectedDate: "2024-11-28",
|
||||
status: "draft",
|
||||
createdBy: "張大明",
|
||||
requesterType: "store",
|
||||
requesterId: "store-3",
|
||||
requesterName: "B 店(西門店)",
|
||||
department: "B 店",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-8",
|
||||
productName: "70%黑巧克力",
|
||||
quantity: 10,
|
||||
unit: "公斤",
|
||||
unitPrice: 520,
|
||||
previousPrice: 500,
|
||||
subtotal: 5200,
|
||||
},
|
||||
],
|
||||
totalAmount: 5200,
|
||||
createdAt: "2024-11-20",
|
||||
},
|
||||
{
|
||||
id: "po-4",
|
||||
poNumber: "PO202411004",
|
||||
supplierId: "sup-1",
|
||||
supplierName: "美食材料供應商",
|
||||
expectedDate: "2024-11-30",
|
||||
status: "review_pending",
|
||||
createdBy: "陳雅婷",
|
||||
requesterType: "store",
|
||||
requesterId: "store-1",
|
||||
requesterName: "總店",
|
||||
department: "總店",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-2",
|
||||
productName: "無鹽奶油",
|
||||
quantity: 15,
|
||||
unit: "公斤",
|
||||
unitPrice: 280,
|
||||
previousPrice: 280,
|
||||
subtotal: 4200,
|
||||
},
|
||||
{
|
||||
productId: "prod-4",
|
||||
productName: "雞蛋",
|
||||
quantity: 10,
|
||||
unit: "打",
|
||||
unitPrice: 65,
|
||||
previousPrice: 65,
|
||||
subtotal: 650,
|
||||
},
|
||||
],
|
||||
totalAmount: 4850,
|
||||
createdAt: "2024-11-21",
|
||||
},
|
||||
{
|
||||
id: "po-5",
|
||||
poNumber: "PO202411005",
|
||||
supplierId: "sup-2",
|
||||
supplierName: "優質乳製品公司",
|
||||
expectedDate: "2024-12-02",
|
||||
status: "completed",
|
||||
createdBy: "林志豪",
|
||||
requesterType: "store",
|
||||
requesterId: "store-2",
|
||||
requesterName: "A 店(東區店)",
|
||||
department: "A 店",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-6",
|
||||
productName: "馬斯卡彭起司",
|
||||
quantity: 5,
|
||||
unit: "公斤",
|
||||
unitPrice: 450,
|
||||
previousPrice: 450,
|
||||
subtotal: 2250,
|
||||
},
|
||||
],
|
||||
totalAmount: 2250,
|
||||
createdAt: "2024-11-15",
|
||||
reviewInfo: {
|
||||
reviewedBy: "王主管",
|
||||
reviewedAt: "2024-11-16 09:00",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "po-6",
|
||||
poNumber: "PO202411006",
|
||||
supplierId: "sup-1",
|
||||
supplierName: "美食材料供應商",
|
||||
expectedDate: "2024-11-27",
|
||||
status: "processing",
|
||||
createdBy: "劉主管",
|
||||
requesterType: "warehouse",
|
||||
requesterId: "warehouse-1",
|
||||
requesterName: "中央倉庫",
|
||||
department: "中央倉庫",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-1",
|
||||
productName: "高筋麵粉",
|
||||
quantity: 200,
|
||||
unit: "公斤",
|
||||
unitPrice: 45,
|
||||
previousPrice: 45,
|
||||
subtotal: 9000,
|
||||
},
|
||||
{
|
||||
productId: "prod-3",
|
||||
productName: "細砂糖",
|
||||
quantity: 100,
|
||||
unit: "公斤",
|
||||
unitPrice: 35,
|
||||
previousPrice: 35,
|
||||
subtotal: 3500,
|
||||
},
|
||||
],
|
||||
totalAmount: 12500,
|
||||
createdAt: "2024-11-19",
|
||||
reviewInfo: {
|
||||
reviewedBy: "李主管",
|
||||
reviewedAt: "2024-11-20 11:30",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "po-7",
|
||||
poNumber: "PO202411007",
|
||||
supplierId: "sup-3",
|
||||
supplierName: "進口巧克力專賣店",
|
||||
expectedDate: "2024-11-29",
|
||||
status: "rejected",
|
||||
createdBy: "黃主管",
|
||||
requesterType: "warehouse",
|
||||
requesterId: "warehouse-2",
|
||||
requesterName: "南區倉庫",
|
||||
department: "南區倉庫",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-8",
|
||||
productName: "70%黑巧克力",
|
||||
quantity: 30,
|
||||
unit: "公斤",
|
||||
unitPrice: 520,
|
||||
previousPrice: 500,
|
||||
subtotal: 15600,
|
||||
},
|
||||
{
|
||||
productId: "prod-10",
|
||||
productName: "可可粉",
|
||||
quantity: 20,
|
||||
unit: "公斤",
|
||||
unitPrice: 380,
|
||||
previousPrice: 380,
|
||||
subtotal: 7600,
|
||||
},
|
||||
],
|
||||
totalAmount: 23200,
|
||||
createdAt: "2024-11-20",
|
||||
reviewInfo: {
|
||||
reviewedBy: "陳主管",
|
||||
reviewedAt: "2024-11-21 16:45",
|
||||
rejectionReason: "數量過多,超出本月預算",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "po-8",
|
||||
poNumber: "PO202411008",
|
||||
supplierId: "sup-2",
|
||||
supplierName: "優質乳製品公司",
|
||||
expectedDate: "2024-12-01",
|
||||
status: "pending_confirm",
|
||||
createdBy: "周小姐",
|
||||
requesterType: "store",
|
||||
requesterId: "store-4",
|
||||
requesterName: "C 店(天母店)",
|
||||
department: "C 店",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-5",
|
||||
productName: "鮮奶油",
|
||||
quantity: 10,
|
||||
unit: "公升",
|
||||
unitPrice: 180,
|
||||
previousPrice: 180,
|
||||
subtotal: 1800,
|
||||
},
|
||||
],
|
||||
totalAmount: 1800,
|
||||
createdAt: "2024-11-22",
|
||||
reviewInfo: {
|
||||
reviewedBy: "張主管",
|
||||
reviewedAt: "2024-11-23 10:00",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "po-9",
|
||||
poNumber: "PO202411009",
|
||||
supplierId: "sup-1",
|
||||
supplierName: "美食材料供應商",
|
||||
expectedDate: "2024-12-03",
|
||||
status: "completed",
|
||||
createdBy: "吳經理",
|
||||
requesterType: "warehouse",
|
||||
requesterId: "warehouse-1",
|
||||
requesterName: "中央倉庫",
|
||||
department: "中央倉庫",
|
||||
items: [
|
||||
{
|
||||
productId: "prod-2",
|
||||
productName: "無鹽奶油",
|
||||
quantity: 25,
|
||||
unit: "公斤",
|
||||
unitPrice: 280,
|
||||
previousPrice: 280,
|
||||
subtotal: 7000,
|
||||
},
|
||||
],
|
||||
totalAmount: 7000,
|
||||
createdAt: "2024-11-16",
|
||||
reviewInfo: {
|
||||
reviewedBy: "林主管",
|
||||
reviewedAt: "2024-11-17 09:30",
|
||||
},
|
||||
paymentInfo: {
|
||||
paymentMethod: "bank_transfer",
|
||||
paymentDate: "2024-11-28",
|
||||
actualAmount: 7000,
|
||||
paidBy: "財務部 陳小姐",
|
||||
paidAt: "2024-11-28 14:30",
|
||||
hasInvoice: true,
|
||||
invoice: {
|
||||
invoiceNumber: "AB-12345678",
|
||||
invoiceAmount: 7000,
|
||||
invoiceDate: "2024-11-28",
|
||||
invoiceType: "triplicate",
|
||||
companyName: "甜點連鎖股份有限公司",
|
||||
taxId: "12345678",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [currentPage, setCurrentPage] = useState("purchase-order-management");
|
||||
const [purchaseOrders, setPurchaseOrders] = useState<PurchaseOrder[]>(initialPurchaseOrders);
|
||||
const [inspectingOrder, setInspectingOrder] = useState<PurchaseOrder | undefined>();
|
||||
const [editingOrder, setEditingOrder] = useState<PurchaseOrder | undefined>();
|
||||
const [viewingOrder, setViewingOrder] = useState<PurchaseOrder | undefined>();
|
||||
|
||||
const handleNavigate = (path: string) => {
|
||||
setCurrentPage(path);
|
||||
};
|
||||
|
||||
const handleNavigateToInspection = (order: PurchaseOrder) => {
|
||||
setInspectingOrder(order);
|
||||
setCurrentPage("inspection");
|
||||
};
|
||||
|
||||
const handleNavigateToCreateOrder = () => {
|
||||
setEditingOrder(undefined);
|
||||
setCurrentPage("create-purchase-order");
|
||||
};
|
||||
|
||||
const handleNavigateToEditOrder = (order: PurchaseOrder) => {
|
||||
setEditingOrder(order);
|
||||
setCurrentPage("create-purchase-order");
|
||||
};
|
||||
|
||||
const handleNavigateToViewOrder = (order: PurchaseOrder) => {
|
||||
setViewingOrder(order);
|
||||
setCurrentPage("view-purchase-order");
|
||||
};
|
||||
|
||||
const handleSaveOrder = (order: PurchaseOrder) => {
|
||||
if (editingOrder) {
|
||||
setPurchaseOrders((prev) =>
|
||||
prev.map((o) => (o.id === order.id ? order : o))
|
||||
);
|
||||
toast.success("採購單已更新");
|
||||
} else {
|
||||
setPurchaseOrders((prev) => [...prev, order]);
|
||||
toast.success("採購單已建立");
|
||||
}
|
||||
setCurrentPage("purchase-order-management");
|
||||
setEditingOrder(undefined);
|
||||
};
|
||||
|
||||
const handleDeleteOrder = (id: string) => {
|
||||
setPurchaseOrders((prev) => prev.filter((o) => o.id !== id));
|
||||
toast.success("採購單已刪除");
|
||||
};
|
||||
|
||||
const handleStatusChange = (id: string, newStatus: PurchaseOrderStatus, reviewInfo?: any) => {
|
||||
setPurchaseOrders((prev) =>
|
||||
prev.map((o) => {
|
||||
if (o.id === id) {
|
||||
const updatedOrder = { ...o, status: newStatus };
|
||||
if (reviewInfo) {
|
||||
updatedOrder.reviewInfo = reviewInfo;
|
||||
}
|
||||
return updatedOrder;
|
||||
}
|
||||
return o;
|
||||
})
|
||||
);
|
||||
toast.success(`採購單狀態已更新為:${STATUS_CONFIG[newStatus].label}`);
|
||||
};
|
||||
|
||||
const handleCancelCreateOrder = () => {
|
||||
setCurrentPage("purchase-order-management");
|
||||
setEditingOrder(undefined);
|
||||
};
|
||||
|
||||
const handleCompleteInspection = (orderId: string, items: PurchaseOrderItem[]) => {
|
||||
// 驗收完成後自動轉為「待確認」狀態
|
||||
setPurchaseOrders((prev) =>
|
||||
prev.map((o) =>
|
||||
o.id === orderId ? { ...o, status: "pending_confirm" as const, items } : o
|
||||
)
|
||||
);
|
||||
|
||||
toast.success("驗收完成!已自動產生應付帳款記錄,請進行付款作業");
|
||||
|
||||
setCurrentPage("purchase-order-management");
|
||||
setInspectingOrder(undefined);
|
||||
};
|
||||
|
||||
const handlePayment = (orderId: string, paymentInfo: PaymentInfo) => {
|
||||
setPurchaseOrders((prev) =>
|
||||
prev.map((o) => {
|
||||
if (o.id === orderId) {
|
||||
// 付款完成後自動轉為「已完成」狀態
|
||||
return {
|
||||
...o,
|
||||
status: "completed" as const,
|
||||
paymentInfo,
|
||||
};
|
||||
}
|
||||
return o;
|
||||
})
|
||||
);
|
||||
|
||||
toast.success("付款完成!採購單已完成所有作業");
|
||||
};
|
||||
|
||||
const handleCancelInspection = () => {
|
||||
setCurrentPage("purchase-order-management");
|
||||
setInspectingOrder(undefined);
|
||||
};
|
||||
|
||||
const renderPage = () => {
|
||||
switch (currentPage) {
|
||||
case "purchase-order-management":
|
||||
return (
|
||||
<PurchaseOrderManagement
|
||||
orders={purchaseOrders}
|
||||
onNavigateToInspection={handleNavigateToInspection}
|
||||
onNavigateToCreateOrder={handleNavigateToCreateOrder}
|
||||
onNavigateToEditOrder={handleNavigateToEditOrder}
|
||||
onNavigateToViewOrder={handleNavigateToViewOrder}
|
||||
onDeleteOrder={handleDeleteOrder}
|
||||
onStatusChange={handleStatusChange}
|
||||
onPayment={handlePayment}
|
||||
/>
|
||||
);
|
||||
case "create-purchase-order":
|
||||
return (
|
||||
<CreatePurchaseOrderPage
|
||||
order={editingOrder}
|
||||
onSave={handleSaveOrder}
|
||||
onCancel={handleCancelCreateOrder}
|
||||
onDelete={handleDeleteOrder}
|
||||
suppliers={mockSuppliers}
|
||||
stores={mockStores}
|
||||
warehouses={mockWarehouses}
|
||||
/>
|
||||
);
|
||||
case "inspection":
|
||||
return (
|
||||
<InspectionPage
|
||||
order={inspectingOrder}
|
||||
onComplete={handleCompleteInspection}
|
||||
onCancel={handleCancelInspection}
|
||||
/>
|
||||
);
|
||||
case "view-purchase-order":
|
||||
return (
|
||||
<ViewPurchaseOrderPage
|
||||
order={viewingOrder}
|
||||
onBack={() => setCurrentPage("purchase-order-management")}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<PurchaseOrderManagement
|
||||
orders={purchaseOrders}
|
||||
onNavigateToInspection={handleNavigateToInspection}
|
||||
onNavigateToCreateOrder={handleNavigateToCreateOrder}
|
||||
onNavigateToEditOrder={handleNavigateToEditOrder}
|
||||
onNavigateToViewOrder={handleNavigateToViewOrder}
|
||||
onDeleteOrder={handleDeleteOrder}
|
||||
onStatusChange={handleStatusChange}
|
||||
onPayment={handlePayment}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const hidesSidebarOnMobile =
|
||||
currentPage === "inspection" ||
|
||||
currentPage === "create-purchase-order";
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen" style={{ backgroundColor: 'var(--bg-page)' }}>
|
||||
{/* Sidebar Navigation - 某些頁面在手機版隱藏 */}
|
||||
<div className={hidesSidebarOnMobile ? "hidden md:block" : ""}>
|
||||
<NavigationSidebar
|
||||
currentPath={currentPage}
|
||||
onNavigate={handleNavigate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 overflow-auto" style={{ backgroundColor: 'var(--bg-page)' }}>
|
||||
{renderPage()}
|
||||
</main>
|
||||
|
||||
<Toaster />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user