2026-01-21 17:19:36 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 生產工單詳情頁面
|
|
|
|
|
|
* 含追溯資訊:成品批號 → 原物料批號 → 來源採購單
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-02-12 16:30:34 +08:00
|
|
|
|
import { Factory, ArrowLeft, Package, Calendar, User, Warehouse, FileText, Link2, Send, CheckCircle2, PlayCircle, Ban, ArrowRightCircle } from 'lucide-react';
|
|
|
|
|
|
import { formatQuantity } from "@/lib/utils";
|
2026-01-21 17:19:36 +08:00
|
|
|
|
import { Button } from "@/Components/ui/button";
|
|
|
|
|
|
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
|
2026-02-12 16:30:34 +08:00
|
|
|
|
import { Head, Link, useForm, router } from "@inertiajs/react";
|
2026-01-21 17:19:36 +08:00
|
|
|
|
import { getBreadcrumbs } from "@/utils/breadcrumb";
|
|
|
|
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/Components/ui/table";
|
2026-02-13 13:16:05 +08:00
|
|
|
|
import { StatusBadge } from "@/Components/shared/StatusBadge";
|
2026-02-12 16:30:34 +08:00
|
|
|
|
import ProductionOrderStatusBadge from '@/Components/ProductionOrder/ProductionOrderStatusBadge';
|
|
|
|
|
|
import { ProductionStatusProgressBar } from '@/Components/ProductionOrder/ProductionStatusProgressBar';
|
|
|
|
|
|
import { PRODUCTION_ORDER_STATUS, ProductionOrderStatus } from '@/constants/production-order';
|
|
|
|
|
|
import WarehouseSelectionModal from '@/Components/ProductionOrder/WarehouseSelectionModal';
|
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
|
import { formatDate } from '@/lib/date';
|
|
|
|
|
|
|
|
|
|
|
|
interface Warehouse {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
2026-01-21 17:19:36 +08:00
|
|
|
|
|
|
|
|
|
|
interface ProductionOrderItem {
|
2026-02-12 16:30:34 +08:00
|
|
|
|
// ... (後面保持不變)
|
2026-01-21 17:19:36 +08:00
|
|
|
|
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;
|
2026-02-12 16:30:34 +08:00
|
|
|
|
warehouse?: { id: number; name: string } | null;
|
2026-01-21 17:19:36 +08:00
|
|
|
|
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;
|
2026-02-12 16:30:34 +08:00
|
|
|
|
product_id: number;
|
2026-01-21 17:19:36 +08:00
|
|
|
|
warehouse: { id: number; name: string } | null;
|
2026-02-12 16:30:34 +08:00
|
|
|
|
warehouse_id: number | null;
|
2026-01-21 17:19:36 +08:00
|
|
|
|
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;
|
2026-02-12 16:30:34 +08:00
|
|
|
|
status: ProductionOrderStatus;
|
2026-01-21 17:19:36 +08:00
|
|
|
|
remark: string | null;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
items: ProductionOrderItem[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
productionOrder: ProductionOrder;
|
2026-02-12 16:30:34 +08:00
|
|
|
|
warehouses: Warehouse[];
|
|
|
|
|
|
auth: {
|
|
|
|
|
|
user: {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
roles: string[];
|
|
|
|
|
|
permissions: string[];
|
|
|
|
|
|
} | null;
|
|
|
|
|
|
};
|
2026-01-21 17:19:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 16:30:34 +08:00
|
|
|
|
export default function ProductionShow({ productionOrder, warehouses, auth }: Props) {
|
|
|
|
|
|
const [isWarehouseModalOpen, setIsWarehouseModalOpen] = useState(false);
|
|
|
|
|
|
const { processing } = useForm({
|
|
|
|
|
|
status: '' as ProductionOrderStatus,
|
|
|
|
|
|
warehouse_id: null as number | null,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const handleStatusUpdate = (newStatus: string, extraData?: {
|
|
|
|
|
|
warehouseId?: number;
|
|
|
|
|
|
batchNumber?: string;
|
|
|
|
|
|
expiryDate?: string;
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
router.patch(route('production-orders.update-status', productionOrder.id), {
|
|
|
|
|
|
status: newStatus,
|
|
|
|
|
|
warehouse_id: extraData?.warehouseId,
|
|
|
|
|
|
output_batch_number: extraData?.batchNumber,
|
|
|
|
|
|
expiry_date: extraData?.expiryDate,
|
|
|
|
|
|
}, {
|
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
|
setIsWarehouseModalOpen(false);
|
|
|
|
|
|
},
|
|
|
|
|
|
preserveScroll: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const userPermissions = auth.user?.permissions || [];
|
|
|
|
|
|
const hasPermission = (permission: string) => auth.user?.roles?.includes('super-admin') || userPermissions.includes(permission);
|
|
|
|
|
|
|
|
|
|
|
|
// 權限判斷
|
|
|
|
|
|
const canApprove = hasPermission('production_orders.approve');
|
|
|
|
|
|
const canCancel = hasPermission('production_orders.cancel');
|
|
|
|
|
|
const canEdit = hasPermission('production_orders.edit');
|
2026-01-21 17:19:36 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<AuthenticatedLayout breadcrumbs={getBreadcrumbs("productionOrdersShow")}>
|
|
|
|
|
|
<Head title={`生產單 ${productionOrder.code}`} />
|
2026-02-12 16:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
<WarehouseSelectionModal
|
|
|
|
|
|
isOpen={isWarehouseModalOpen}
|
|
|
|
|
|
onClose={() => setIsWarehouseModalOpen(false)}
|
|
|
|
|
|
onConfirm={(data) => handleStatusUpdate(PRODUCTION_ORDER_STATUS.COMPLETED, data)}
|
|
|
|
|
|
warehouses={warehouses}
|
|
|
|
|
|
processing={processing}
|
|
|
|
|
|
productCode={productionOrder.product?.code}
|
|
|
|
|
|
productId={productionOrder.product?.id}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="container mx-auto p-6 max-w-7xl animate-in fade-in duration-500">
|
|
|
|
|
|
{/* Header 區塊 */}
|
2026-01-22 15:39:35 +08:00
|
|
|
|
<div className="mb-6">
|
2026-02-12 16:30:34 +08:00
|
|
|
|
{/* 返回按鈕 (統一規範:標題上方,mb-4) */}
|
2026-01-22 15:39:35 +08:00
|
|
|
|
<Link href={route('production-orders.index')}>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
2026-02-12 16:30:34 +08:00
|
|
|
|
className="gap-2 button-outlined-primary mb-4"
|
2026-01-22 15:39:35 +08:00
|
|
|
|
>
|
|
|
|
|
|
<ArrowLeft className="h-4 w-4" />
|
2026-02-12 16:30:34 +08:00
|
|
|
|
返回列表
|
2026-01-22 15:39:35 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
</Link>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
2026-01-22 15:39:35 +08:00
|
|
|
|
<div>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
|
<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>
|
|
|
|
|
|
<ProductionOrderStatusBadge status={productionOrder.status} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-gray-500 text-sm mt-1">
|
|
|
|
|
|
建立人員:{productionOrder.user?.name || '-'} | 建立時間:{formatDate(productionOrder.created_at)}
|
2026-01-22 15:39:35 +08:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 操作按鈕區 (統一規範樣式類別) */}
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
{/* 草稿 -> 提交審核 */}
|
|
|
|
|
|
{productionOrder.status === PRODUCTION_ORDER_STATUS.DRAFT && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{canEdit && (
|
|
|
|
|
|
<Link href={route('production-orders.edit', productionOrder.id)}>
|
|
|
|
|
|
<Button variant="outline" className="gap-2 button-outlined-primary">
|
|
|
|
|
|
<FileText className="h-4 w-4" />
|
|
|
|
|
|
編輯工單
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Button
|
|
|
|
|
|
onClick={() => handleStatusUpdate(PRODUCTION_ORDER_STATUS.PENDING)}
|
|
|
|
|
|
className="gap-2 button-filled-primary"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Send className="h-4 w-4" />
|
|
|
|
|
|
送審工單
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 待審核 -> 核准 / 駁回 */}
|
|
|
|
|
|
{productionOrder.status === PRODUCTION_ORDER_STATUS.PENDING && canApprove && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
onClick={() => handleStatusUpdate(PRODUCTION_ORDER_STATUS.DRAFT)}
|
|
|
|
|
|
className="gap-2 button-outlined-error"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ArrowLeft className="h-4 w-4" />
|
|
|
|
|
|
退回草稿
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
onClick={() => handleStatusUpdate(PRODUCTION_ORDER_STATUS.APPROVED)}
|
|
|
|
|
|
className="gap-2 button-filled-success"
|
|
|
|
|
|
>
|
|
|
|
|
|
<CheckCircle2 className="h-4 w-4" />
|
|
|
|
|
|
核准工單
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 已核准 -> 開始製作 */}
|
|
|
|
|
|
{productionOrder.status === PRODUCTION_ORDER_STATUS.APPROVED && (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
onClick={() => handleStatusUpdate(PRODUCTION_ORDER_STATUS.IN_PROGRESS)}
|
|
|
|
|
|
className="gap-2 button-filled-primary"
|
|
|
|
|
|
>
|
|
|
|
|
|
<PlayCircle className="h-4 w-4" />
|
|
|
|
|
|
開始製作 (扣除原料庫存)
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 製作中 -> 完成製作 */}
|
|
|
|
|
|
{productionOrder.status === PRODUCTION_ORDER_STATUS.IN_PROGRESS && (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
onClick={() => setIsWarehouseModalOpen(true)}
|
|
|
|
|
|
className="gap-2 button-filled-primary"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ArrowRightCircle className="h-4 w-4" />
|
|
|
|
|
|
製作完成 (成品入庫)
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 可作廢狀態 (非已完成/已作廢/草稿之外) */}
|
|
|
|
|
|
{!([PRODUCTION_ORDER_STATUS.COMPLETED, PRODUCTION_ORDER_STATUS.CANCELLED, PRODUCTION_ORDER_STATUS.DRAFT] as ProductionOrderStatus[]).includes(productionOrder.status) && canCancel && (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
if (confirm('確定要作廢此生產工單嗎?此動作無法復原。')) {
|
|
|
|
|
|
handleStatusUpdate(PRODUCTION_ORDER_STATUS.CANCELLED);
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="gap-2 button-outlined-error"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Ban className="h-4 w-4" />
|
|
|
|
|
|
作廢工單
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
|
|
|
|
{/* 狀態進度條 */}
|
|
|
|
|
|
<div className="lg:col-span-3">
|
|
|
|
|
|
<ProductionStatusProgressBar currentStatus={productionOrder.status} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 成品資訊 (統一規範:bg-white rounded-xl border border-gray-200 shadow-sm p-6) */}
|
|
|
|
|
|
<div className="lg:col-span-2 space-y-6">
|
|
|
|
|
|
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-200 h-full">
|
|
|
|
|
|
<h2 className="text-lg font-semibold mb-6 flex items-center gap-2 text-grey-0">
|
|
|
|
|
|
<Package className="h-5 w-5 text-primary-main" />
|
|
|
|
|
|
成品資訊
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-8">
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<p className="text-xs font-semibold text-grey-2 uppercase tracking-wider">成品商品</p>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="font-bold text-grey-0 text-lg">
|
|
|
|
|
|
{productionOrder.product?.name || '-'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-gray-400 text-sm font-mono mt-0.5">
|
|
|
|
|
|
{productionOrder.product?.code || '-'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<p className="text-xs font-semibold text-grey-2 uppercase tracking-wider">生產批號</p>
|
|
|
|
|
|
<p className="font-mono font-bold text-primary-main text-lg py-1 px-2 bg-primary-lightest rounded-md inline-block">
|
|
|
|
|
|
{productionOrder.output_batch_number}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<p className="text-xs font-semibold text-grey-2 uppercase tracking-wider">預計/實際產量</p>
|
|
|
|
|
|
<div className="flex items-baseline gap-1.5">
|
|
|
|
|
|
<p className="font-bold text-grey-0 text-xl">
|
|
|
|
|
|
{formatQuantity(productionOrder.output_quantity)}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
{productionOrder.product?.base_unit?.name && (
|
|
|
|
|
|
<span className="text-grey-2 font-medium">{productionOrder.product.base_unit.name}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{productionOrder.output_box_count && (
|
|
|
|
|
|
<span className="text-grey-3 ml-2 text-sm">({productionOrder.output_box_count} 箱)</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<p className="text-xs font-semibold text-grey-2 uppercase tracking-wider">入庫倉庫</p>
|
|
|
|
|
|
<div className="flex items-center gap-2 bg-grey-5 p-2 rounded-lg border border-grey-4">
|
|
|
|
|
|
<Warehouse className="h-4 w-4 text-grey-3" />
|
|
|
|
|
|
<p className="font-semibold text-grey-0">{productionOrder.warehouse?.name || (productionOrder.status === PRODUCTION_ORDER_STATUS.COMPLETED ? '系統錯誤' : '待選取')}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</div>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
|
|
|
|
|
|
{productionOrder.remark && (
|
|
|
|
|
|
<div className="mt-8 pt-6 border-t border-grey-4 transition-all hover:bg-grey-5 p-2 rounded-lg">
|
|
|
|
|
|
<div className="flex items-start gap-3">
|
|
|
|
|
|
<FileText className="h-5 w-5 text-grey-3 mt-0.5" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-xs font-semibold text-grey-2 uppercase tracking-wider mb-1">備註資訊</p>
|
|
|
|
|
|
<p className="text-grey-1 leading-relaxed">{productionOrder.remark}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-12 16:30:34 +08:00
|
|
|
|
{/* 次要資訊 */}
|
|
|
|
|
|
<div className="lg:col-span-1">
|
|
|
|
|
|
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-200 h-full space-y-8">
|
|
|
|
|
|
<h2 className="text-lg font-semibold flex items-center gap-2 text-grey-0">
|
|
|
|
|
|
<Calendar className="h-5 w-5 text-primary-main" />
|
|
|
|
|
|
時間與人員
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
<div className="flex items-start gap-4 p-3 rounded-lg bg-primary-lightest border border-primary-light/20">
|
|
|
|
|
|
<Calendar className="h-5 w-5 text-primary-main mt-1" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-xs font-bold text-primary-main/60 uppercase">生產日期</p>
|
|
|
|
|
|
<p className="font-bold text-grey-0 text-lg">{formatDate(productionOrder.production_date)}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-start gap-4 p-3 rounded-lg bg-orange-50 border border-orange-100">
|
|
|
|
|
|
<Calendar className="h-5 w-5 text-orange-600 mt-1" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-xs font-bold text-orange-900/50 uppercase">成品效期</p>
|
|
|
|
|
|
<p className="font-bold text-orange-900 text-lg">{formatDate(productionOrder.expiry_date)}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-start gap-4 p-3 rounded-lg bg-grey-5 border border-grey-4">
|
|
|
|
|
|
<User className="h-5 w-5 text-grey-2 mt-1" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-xs font-bold text-grey-3 uppercase">執行人員</p>
|
|
|
|
|
|
<p className="font-bold text-grey-1 text-lg">{productionOrder.user?.name || '-'}</p>
|
|
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-12 16:30:34 +08:00
|
|
|
|
{/* 原物料使用明細 (BOM) (統一規範:bg-white rounded-xl border border-gray-200 shadow-sm p-6) */}
|
|
|
|
|
|
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-6">
|
|
|
|
|
|
<h2 className="text-lg font-semibold flex items-center gap-2 text-grey-0">
|
|
|
|
|
|
<Link2 className="h-5 w-5 text-primary-main" />
|
|
|
|
|
|
原料耗用與追溯
|
|
|
|
|
|
</h2>
|
2026-02-13 13:16:05 +08:00
|
|
|
|
<StatusBadge variant="neutral" className="text-grey-3 font-medium">
|
2026-02-12 16:30:34 +08:00
|
|
|
|
共 {productionOrder.items.length} 項物料
|
2026-02-13 13:16:05 +08:00
|
|
|
|
</StatusBadge>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
|
|
|
|
|
|
{productionOrder.items.length === 0 ? (
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<div className="flex flex-col items-center justify-center py-16 text-grey-3 bg-grey-5 rounded-xl border-2 border-dashed border-grey-4">
|
|
|
|
|
|
<Package className="h-10 w-10 mb-4 opacity-20 text-grey-2" />
|
|
|
|
|
|
<p>無原物料消耗記錄</p>
|
|
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
) : (
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<div className="rounded-xl border border-grey-4 overflow-hidden shadow-sm">
|
2026-01-21 17:19:36 +08:00
|
|
|
|
<Table>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<TableHeader className="bg-grey-5/80 backdrop-blur-sm transition-colors">
|
|
|
|
|
|
<TableRow className="hover:bg-transparent border-b-grey-4">
|
|
|
|
|
|
<TableHead className="px-6 py-4 text-xs font-bold text-grey-2 uppercase tracking-widest leading-none">原物料</TableHead>
|
|
|
|
|
|
<TableHead className="px-6 py-4 text-xs font-bold text-grey-2 uppercase tracking-widest leading-none">來源倉庫</TableHead>
|
|
|
|
|
|
<TableHead className="px-6 py-4 text-xs font-bold text-grey-2 uppercase tracking-widest leading-none">使用批號</TableHead>
|
|
|
|
|
|
<TableHead className="px-6 py-4 text-xs font-bold text-grey-2 uppercase tracking-widest leading-none text-center">來源國家</TableHead>
|
|
|
|
|
|
<TableHead className="px-6 py-4 text-xs font-bold text-grey-2 uppercase tracking-widest leading-none">使用數量</TableHead>
|
|
|
|
|
|
<TableHead className="px-6 py-4 text-xs font-bold text-grey-2 uppercase tracking-widest leading-none">來源單據</TableHead>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</TableRow>
|
|
|
|
|
|
</TableHeader>
|
|
|
|
|
|
<TableBody>
|
|
|
|
|
|
{productionOrder.items.map((item) => (
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<TableRow key={item.id} className="hover:bg-grey-5/80 transition-colors border-b-grey-4 last:border-0">
|
|
|
|
|
|
<TableCell className="px-6 py-5">
|
|
|
|
|
|
<div className="font-bold text-grey-0">{item.inventory?.product?.name || '-'}</div>
|
|
|
|
|
|
<div className="text-grey-3 text-xs font-mono mt-1 px-1.5 py-0.5 bg-grey-5 border border-grey-4 rounded inline-block">
|
2026-01-21 17:19:36 +08:00
|
|
|
|
{item.inventory?.product?.code || '-'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TableCell>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<TableCell className="px-6 py-5">
|
|
|
|
|
|
<div className="text-grey-0 font-medium">{item.inventory?.warehouse?.name || '-'}</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</TableCell>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<TableCell className="px-6 py-5">
|
|
|
|
|
|
<div className="font-mono font-bold text-primary-main bg-primary-lightest border border-primary-light/10 px-2 py-1 rounded inline-flex items-center gap-2">
|
|
|
|
|
|
{item.inventory?.batch_number || '-'}
|
|
|
|
|
|
{item.inventory?.box_number && (
|
|
|
|
|
|
<span className="text-primary-main/60 text-[10px] bg-white px-1 rounded shadow-sm">#{item.inventory.box_number}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</TableCell>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<TableCell className="px-6 py-5 text-center">
|
|
|
|
|
|
<span className="px-3 py-1 bg-grey-5 border border-grey-4 rounded-full text-xs font-bold text-grey-2">
|
|
|
|
|
|
{item.inventory?.origin_country || '-'}
|
|
|
|
|
|
</span>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</TableCell>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<TableCell className="px-6 py-5">
|
|
|
|
|
|
<div className="flex items-baseline gap-1">
|
|
|
|
|
|
<span className="font-bold text-grey-0 text-base">{formatQuantity(item.quantity_used)}</span>
|
|
|
|
|
|
{item.unit?.name && (
|
|
|
|
|
|
<span className="text-grey-3 text-xs font-medium uppercase">{item.unit.name}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</TableCell>
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<TableCell className="px-6 py-5">
|
2026-01-21 17:19:36 +08:00
|
|
|
|
{item.inventory?.source_purchase_order ? (
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<div className="group flex flex-col">
|
2026-01-21 17:19:36 +08:00
|
|
|
|
<Link
|
|
|
|
|
|
href={route('purchase-orders.show', item.inventory.source_purchase_order.id)}
|
2026-02-12 16:30:34 +08:00
|
|
|
|
className="text-primary-main hover:text-primary-dark font-bold inline-flex items-center gap-1 group-hover:underline transition-all"
|
2026-01-21 17:19:36 +08:00
|
|
|
|
>
|
|
|
|
|
|
{item.inventory.source_purchase_order.code}
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<ArrowLeft className="h-3 w-3 rotate-180 opacity-0 group-hover:opacity-100 transition-opacity" />
|
2026-01-21 17:19:36 +08:00
|
|
|
|
</Link>
|
|
|
|
|
|
{item.inventory.source_purchase_order.vendor && (
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<span className="text-[10px] text-grey-3 font-bold uppercase tracking-tight mt-0.5 whitespace-nowrap overflow-hidden text-ellipsis max-w-[150px]">
|
2026-01-21 17:19:36 +08:00
|
|
|
|
{item.inventory.source_purchase_order.vendor.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
2026-02-12 16:30:34 +08:00
|
|
|
|
<span className="text-grey-4">—</span>
|
2026-01-21 17:19:36 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</TableBody>
|
|
|
|
|
|
</Table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</AuthenticatedLayout>
|
|
|
|
|
|
);
|
2026-01-26 14:59:24 +08:00
|
|
|
|
}
|