2026-01-21 17:19:36 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 生產工單詳情頁面
|
|
|
|
|
|
* 含追溯資訊:成品批號 → 原物料批號 → 來源採購單
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { Factory, ArrowLeft, Package, Calendar, User, Warehouse, FileText, Link2 } from 'lucide-react';
|
|
|
|
|
|
import { Button } from "@/Components/ui/button";
|
|
|
|
|
|
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
|
2026-01-22 15:39:35 +08:00
|
|
|
|
import { Head, Link } from "@inertiajs/react";
|
2026-01-21 17:19:36 +08:00
|
|
|
|
import { getBreadcrumbs } from "@/utils/breadcrumb";
|
|
|
|
|
|
import { Badge } from "@/Components/ui/badge";
|
|
|
|
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/Components/ui/table";
|
|
|
|
|
|
|
|
|
|
|
|
interface ProductionOrderItem {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
quantity_used: number;
|
|
|
|
|
|
unit?: { id: number; name: string } | null;
|
|
|
|
|
|
inventory: {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
batch_number: string;
|
|
|
|
|
|
box_number: string | null;
|
|
|
|
|
|
arrival_date: string | null;
|
|
|
|
|
|
origin_country: string | null;
|
|
|
|
|
|
product: { id: number; name: string; code: string } | null;
|
|
|
|
|
|
source_purchase_order?: {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
code: string;
|
|
|
|
|
|
vendor?: { id: number; name: string } | null;
|
|
|
|
|
|
} | null;
|
|
|
|
|
|
} | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ProductionOrder {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
code: string;
|
|
|
|
|
|
product: { id: number; name: string; code: string; base_unit?: { name: string } | null } | null;
|
|
|
|
|
|
warehouse: { id: number; name: string } | null;
|
|
|
|
|
|
user: { id: number; name: string } | null;
|
|
|
|
|
|
output_batch_number: string;
|
|
|
|
|
|
output_box_count: string | null;
|
|
|
|
|
|
output_quantity: number;
|
|
|
|
|
|
production_date: string;
|
|
|
|
|
|
expiry_date: string | null;
|
|
|
|
|
|
status: 'draft' | 'completed' | 'cancelled';
|
|
|
|
|
|
remark: string | null;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
items: ProductionOrderItem[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
productionOrder: ProductionOrder;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const statusConfig: Record<string, { label: string; variant: "default" | "secondary" | "destructive" | "outline" }> = {
|
|
|
|
|
|
draft: { label: "草稿", variant: "secondary" },
|
|
|
|
|
|
completed: { label: "已完成", variant: "default" },
|
|
|
|
|
|
cancelled: { label: "已取消", variant: "destructive" },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default function ProductionShow({ productionOrder }: Props) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<AuthenticatedLayout breadcrumbs={getBreadcrumbs("productionOrdersShow")}>
|
|
|
|
|
|
<Head title={`生產單 ${productionOrder.code}`} />
|
2026-01-22 15:39:35 +08:00
|
|
|
|
<div className="container mx-auto p-6 max-w-7xl">
|
|
|
|
|
|
<div className="mb-6">
|
|
|
|
|
|
<Link href={route('production-orders.index')}>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
className="gap-2 button-outlined-primary mb-6"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ArrowLeft className="h-4 w-4" />
|
|
|
|
|
|
返回生產單
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
|
|
|
|
|
<Factory className="h-6 w-6 text-primary-main" />
|
|
|
|
|
|
{productionOrder.code}
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<p className="text-gray-500 mt-1">
|
|
|
|
|
|
生產工單詳情與追溯資訊
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Badge variant={statusConfig[productionOrder.status]?.variant || "secondary"} className="text-sm">
|
|
|
|
|
|
{statusConfig[productionOrder.status]?.label || productionOrder.status}
|
|
|
|
|
|
</Badge>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 成品資訊 */}
|
|
|
|
|
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 mb-6">
|
|
|
|
|
|
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
|
|
|
|
|
<Package className="h-5 w-5 text-gray-500" />
|
|
|
|
|
|
成品資訊
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-6">
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<p className="text-xs font-medium text-grey-2">成品商品</p>
|
|
|
|
|
|
<p className="font-medium text-grey-0">
|
|
|
|
|
|
{productionOrder.product?.name || '-'}
|
|
|
|
|
|
<span className="text-gray-400 ml-2 text-sm font-normal">
|
|
|
|
|
|
({productionOrder.product?.code || '-'})
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<p className="text-xs font-medium text-grey-2">成品批號</p>
|
|
|
|
|
|
<p className="font-mono font-medium text-primary-main">
|
|
|
|
|
|
{productionOrder.output_batch_number}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<p className="text-xs font-medium text-grey-2">生產數量</p>
|
|
|
|
|
|
<p className="font-medium text-grey-0">
|
|
|
|
|
|
{productionOrder.output_quantity.toLocaleString()}
|
|
|
|
|
|
{productionOrder.product?.base_unit?.name && (
|
|
|
|
|
|
<span className="text-gray-400 ml-1 font-normal">{productionOrder.product.base_unit.name}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{productionOrder.output_box_count && (
|
|
|
|
|
|
<span className="text-gray-400 ml-2 font-normal">({productionOrder.output_box_count} 箱)</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<p className="text-xs font-medium text-grey-2">入庫倉庫</p>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Warehouse className="h-4 w-4 text-gray-400" />
|
|
|
|
|
|
<p className="font-medium text-grey-0">{productionOrder.warehouse?.name || '-'}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<p className="text-xs font-medium text-grey-2">生產日期</p>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Calendar className="h-4 w-4 text-gray-400" />
|
|
|
|
|
|
<p className="font-medium text-grey-0">{productionOrder.production_date}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<p className="text-xs font-medium text-grey-2">成品效期</p>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Calendar className="h-4 w-4 text-gray-400" />
|
|
|
|
|
|
<p className="font-medium text-grey-0">{productionOrder.expiry_date || '-'}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<p className="text-xs font-medium text-grey-2">操作人員</p>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<User className="h-4 w-4 text-gray-400" />
|
|
|
|
|
|
<p className="font-medium text-grey-0">{productionOrder.user?.name || '-'}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{productionOrder.remark && (
|
|
|
|
|
|
<div className="mt-4 pt-4 border-t border-gray-200">
|
|
|
|
|
|
<div className="flex items-start gap-2">
|
|
|
|
|
|
<FileText className="h-4 w-4 text-gray-400 mt-1" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-sm text-gray-500">備註</p>
|
|
|
|
|
|
<p className="text-gray-700">{productionOrder.remark}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 原物料使用明細 (BOM) */}
|
|
|
|
|
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
|
|
|
|
|
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
|
|
|
|
|
<Link2 className="h-5 w-5 text-gray-500" />
|
|
|
|
|
|
原物料使用明細 (BOM) - 追溯資訊
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
|
|
{productionOrder.items.length === 0 ? (
|
|
|
|
|
|
<p className="text-center text-gray-500 py-8">無原物料記錄</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
|
|
|
|
|
<Table>
|
|
|
|
|
|
<TableHeader className="bg-gray-50">
|
|
|
|
|
|
<TableRow>
|
|
|
|
|
|
<TableHead className="px-4 py-3 text-left text-xs font-medium text-grey-2">
|
|
|
|
|
|
原物料
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
<TableHead className="px-4 py-3 text-left text-xs font-medium text-grey-2">
|
|
|
|
|
|
批號
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
<TableHead className="px-4 py-3 text-left text-xs font-medium text-grey-2">
|
|
|
|
|
|
來源國家
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
<TableHead className="px-4 py-3 text-left text-xs font-medium text-grey-2">
|
|
|
|
|
|
入庫日期
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
<TableHead className="px-4 py-3 text-left text-xs font-medium text-grey-2">
|
|
|
|
|
|
使用量
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
<TableHead className="px-4 py-3 text-left text-xs font-medium text-grey-2">
|
|
|
|
|
|
來源採購單
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
</TableHeader>
|
|
|
|
|
|
<TableBody>
|
|
|
|
|
|
{productionOrder.items.map((item) => (
|
|
|
|
|
|
<TableRow key={item.id} className="hover:bg-gray-50/50">
|
|
|
|
|
|
<TableCell className="px-4 py-4 text-sm">
|
|
|
|
|
|
<div className="font-medium text-grey-0">{item.inventory?.product?.name || '-'}</div>
|
|
|
|
|
|
<div className="text-gray-400 text-xs">
|
|
|
|
|
|
{item.inventory?.product?.code || '-'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="px-4 py-4 text-sm font-mono text-primary-main">
|
|
|
|
|
|
{item.inventory?.batch_number || '-'}
|
|
|
|
|
|
{item.inventory?.box_number && (
|
|
|
|
|
|
<span className="text-gray-300 ml-1">#{item.inventory.box_number}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="px-4 py-4 text-sm text-grey-1">
|
|
|
|
|
|
{item.inventory?.origin_country || '-'}
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="px-4 py-4 text-sm text-grey-1">
|
|
|
|
|
|
{item.inventory?.arrival_date || '-'}
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="px-4 py-4 text-sm font-medium text-grey-0">
|
|
|
|
|
|
{item.quantity_used.toLocaleString()}
|
|
|
|
|
|
{item.unit?.name && (
|
|
|
|
|
|
<span className="text-gray-400 ml-1 font-normal text-xs">{item.unit.name}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="px-4 py-4 text-sm">
|
|
|
|
|
|
{item.inventory?.source_purchase_order ? (
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href={route('purchase-orders.show', item.inventory.source_purchase_order.id)}
|
|
|
|
|
|
className="text-primary-main hover:underline font-medium"
|
|
|
|
|
|
>
|
|
|
|
|
|
{item.inventory.source_purchase_order.code}
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
{item.inventory.source_purchase_order.vendor && (
|
|
|
|
|
|
<span className="text-[11px] text-gray-400 mt-0.5">
|
|
|
|
|
|
{item.inventory.source_purchase_order.vendor.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<span className="text-gray-400">-</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</TableBody>
|
|
|
|
|
|
</Table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</AuthenticatedLayout>
|
|
|
|
|
|
);
|
2026-01-26 14:59:24 +08:00
|
|
|
|
}
|