完成工單/完成 API 改為僅 ADMIN;「工單生產流程」tab 僅 ADMIN 可見;「已提料」改為全部 POL completed;文案「未提料」→「未完成提料」/Pick incomplete。fix負數倉
| @@ -905,7 +905,7 @@ export const fetchAllJoborderProductProcessInfo = cache(async (type?: string | n | |||
| ); | |||
| }); | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.5 | 2026-08-06 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */ | |||
| export const fetchJoborderProductProcessesPage = cache(async (params: { | |||
| /** Job order / process date(YYYY-MM-DD) */ | |||
| date?: string | null; | |||
| @@ -20,7 +20,7 @@ export const AUTH = { | |||
| JOB_MAT: "JOB_MAT", | |||
| JOB_PROD: "JOB_PROD", | |||
| /** | |||
| * FP-MTMS Version Checklist | Functions Ref. No. 51 | v1.0.0 | 2026-08-05 | |||
| * FP-MTMS Version Checklist | Functions Ref. No. 51 | v1.0.1 | 2026-08-06 | |||
| * 工單 生產流程 完成工單 | |||
| */ | |||
| PRODUCT_PROCESS: "PRODUCT_PROCESS", | |||
| @@ -28,7 +28,7 @@ export const AUTH = { | |||
| } as const; | |||
| /** | |||
| * FP-MTMS Version Checklist | Functions Ref. No. 51 | v1.0.0 | 2026-08-05 | |||
| * FP-MTMS Version Checklist | Functions Ref. No. 51 | v1.0.1 | 2026-08-06 | |||
| * Match session ability codes (exact, trimmed). | |||
| */ | |||
| export function hasAbility( | |||
| @@ -223,7 +223,7 @@ interface JobOrderOpsTableProps { | |||
| printerCombo?: PrinterCombo[]; | |||
| } | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 47 | v1.0.4 | 2026-08-06 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 47 | v1.0.5 | 2026-08-06 */ | |||
| const JobOrderOpsTable: React.FC<JobOrderOpsTableProps> = ({ | |||
| onSelectProcess, | |||
| printerCombo = [], | |||
| @@ -232,10 +232,9 @@ const JobOrderOpsTable: React.FC<JobOrderOpsTableProps> = ({ | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| const sessionToken = session as SessionWithTokens | null; | |||
| const abilities = session?.abilities ?? session?.user?.abilities ?? []; | |||
| /** 取消工單:僅 ADMIN */ | |||
| /** 取消工單 / 完成工單:僅 ADMIN */ | |||
| const canCancel = hasAbility(abilities, AUTH.ADMIN); | |||
| /** 完成工單:僅 PRODUCT_PROCESS(工單 生產流程 完成工單) */ | |||
| const canComplete = hasAbility(abilities, AUTH.PRODUCT_PROCESS); | |||
| const canComplete = hasAbility(abilities, AUTH.ADMIN); | |||
| const labelPrinterCombo = useMemo( | |||
| () => (printerCombo || []).filter((p) => p.type === "Label"), | |||
| @@ -53,7 +53,7 @@ interface ProductProcessJobOrderDetailProps { | |||
| initialTabIndex?: number; | |||
| } | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.5 | 2026-08-06 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */ | |||
| const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProps> = ({ | |||
| jobOrderId, | |||
| onBack, | |||
| @@ -178,7 +178,7 @@ function isWaitingQcPutAway( | |||
| return s !== "completed" && s !== "rejected"; | |||
| } | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.5 | 2026-08-06 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.7 | 2026-08-06 */ | |||
| const ProductProcessList: React.FC<ProductProcessListProps> = ({ | |||
| onSelectProcess, | |||
| printerCombo, | |||
| @@ -209,8 +209,8 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ | |||
| const [modalInfo, setModalInfo] = useState<StockInLineInput>(); | |||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | |||
| const abilities = session?.abilities ?? session?.user?.abilities ?? []; | |||
| /** 完成工單:僅 PRODUCT_PROCESS(工單 生產流程 完成工單);不含 ADMIN 以免群組 ADMIN 繞過勾選 */ | |||
| const canManageUpdateJo = hasAbility(abilities, AUTH.PRODUCT_PROCESS); | |||
| /** 完成工單:僅 ADMIN */ | |||
| const canManageUpdateJo = hasAbility(abilities, AUTH.ADMIN); | |||
| type ProcessFilter = "all" | "drink" | "Powder_Mixture" | "other"; | |||
| const listTab = normalizeListTab(listPersistedState.pickBucket); | |||
| @@ -1230,20 +1230,17 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ | |||
| {t("Matching Stock")} | |||
| </Button> | |||
| {statusLower !== "completed" && ( | |||
| {canManageUpdateJo && statusLower !== "completed" && ( | |||
| <Button | |||
| variant="contained" | |||
| size="small" | |||
| disabled={!canManageUpdateJo} | |||
| onClick={() => | |||
| canManageUpdateJo | |||
| ? openConfirm( | |||
| t("Confirm to update this Job Order?"), | |||
| async () => { | |||
| await handleUpdateJo(process); | |||
| }, | |||
| ) | |||
| : undefined | |||
| openConfirm( | |||
| t("Confirm to update this Job Order?"), | |||
| async () => { | |||
| await handleUpdateJo(process); | |||
| }, | |||
| ) | |||
| } | |||
| > | |||
| {t("Update Job Order")} | |||
| @@ -17,6 +17,7 @@ import DrinkProductionQtyDashboard from "@/components/ProductionProcess/DrinkPro | |||
| import JobOrderOpsTable from "@/components/ProductionProcess/JobOrderOpsTable"; | |||
| import type { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { AUTH, hasAbility } from "@/authorities"; | |||
| interface ProductionProcessPageProps { | |||
| printerCombo: PrinterCombo[]; | |||
| @@ -24,9 +25,17 @@ interface ProductionProcessPageProps { | |||
| const STORAGE_KEY = 'productionProcess_selectedMatchingStock'; | |||
| type PageTab = | |||
| | "list" | |||
| | "ops" | |||
| | "jobProcessStatus" | |||
| | "operatorKpi" | |||
| | "equipmentStatus" | |||
| | "drinkQty"; | |||
| /** | |||
| * FP-MTMS Version Checklist | Functions Ref. No. 26 | v1.0.0 | 2026-07-20 | |||
| * FP-MTMS Version Checklist | Functions Ref. No. 47 | v1.0.4 | 2026-08-06 | |||
| * FP-MTMS Version Checklist | Functions Ref. No. 47 | v1.0.5 | 2026-08-06 | |||
| */ | |||
| const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCombo }) => { | |||
| const { t } = useTranslation(["common", "productionProcess"]); | |||
| @@ -36,19 +45,27 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||
| productProcessId: number; | |||
| pickOrderId: number; | |||
| } | null>(null); | |||
| /** 0 = Production Process list; 1 = JO ops table; 2..5 = dashboards */ | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const [pageTab, setPageTab] = useState<PageTab>("list"); | |||
| const [productionListState, setProductionListState] = useState(() => ({ | |||
| ...createDefaultProductionProcessListPersistedState(), | |||
| })); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| const sessionToken = session as SessionWithTokens | null; | |||
| const abilities = session?.abilities ?? session?.user?.abilities ?? []; | |||
| /** 「查看工單流程情況」僅 ADMIN */ | |||
| const canSeeOpsTable = hasAbility(abilities, AUTH.ADMIN); | |||
| const searchParams = useSearchParams(); | |||
| const pathname = usePathname(); | |||
| const router = useRouter(); | |||
| const [linkQcOpen, setLinkQcOpen] = useState(false); | |||
| const [linkQcSilId, setLinkQcSilId] = useState<number | null>(null); | |||
| useEffect(() => { | |||
| if (!canSeeOpsTable && pageTab === "ops") { | |||
| setPageTab("list"); | |||
| } | |||
| }, [canSeeOpsTable, pageTab]); | |||
| useEffect(() => { | |||
| if (typeof window !== 'undefined') { | |||
| try { | |||
| @@ -83,8 +100,8 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||
| } | |||
| }, []); | |||
| const handleTabChange = useCallback((event: React.SyntheticEvent, newValue: number) => { | |||
| setTabIndex(newValue); | |||
| const handleTabChange = useCallback((_: React.SyntheticEvent, newValue: PageTab) => { | |||
| setPageTab(newValue); | |||
| }, []); | |||
| const openStockInLineIdQ = searchParams.get("openStockInLineId"); | |||
| @@ -100,7 +117,7 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||
| if (!Number.isFinite(id) || id <= 0) return; | |||
| setSelectedProcessId(null); | |||
| setSelectedMatchingStock(null); | |||
| setTabIndex(0); | |||
| setPageTab("list"); | |||
| setProductionListState((prev) => ({ | |||
| ...prev, | |||
| pickBucket: "pending_qc", | |||
| @@ -144,16 +161,18 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||
| return ( | |||
| <> | |||
| <Box> | |||
| <Tabs value={tabIndex} onChange={handleTabChange} sx={{ mb: 2 }}> | |||
| <Tab label={t("Production Process")} /> | |||
| <Tab label={t("Job Order Ops Table")} /> | |||
| <Tab label={t("Job Process Status Dashboard")} /> | |||
| <Tab label={t("Operator KPI Dashboard")} /> | |||
| <Tab label={t("Production Equipment Status Dashboard")} /> | |||
| <Tab label={t("Drink Production Qty Dashboard")} /> | |||
| <Tabs value={pageTab} onChange={handleTabChange} sx={{ mb: 2 }}> | |||
| <Tab value="list" label={t("Production Process")} /> | |||
| {canSeeOpsTable && ( | |||
| <Tab value="ops" label={t("Job Order Ops Table")} /> | |||
| )} | |||
| <Tab value="jobProcessStatus" label={t("Job Process Status Dashboard")} /> | |||
| <Tab value="operatorKpi" label={t("Operator KPI Dashboard")} /> | |||
| <Tab value="equipmentStatus" label={t("Production Equipment Status Dashboard")} /> | |||
| <Tab value="drinkQty" label={t("Drink Production Qty Dashboard")} /> | |||
| </Tabs> | |||
| {tabIndex === 0 && ( | |||
| {pageTab === "list" && ( | |||
| <ProductionProcessList | |||
| printerCombo={printerCombo} | |||
| listPersistedState={productionListState} | |||
| @@ -174,7 +193,7 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||
| /> | |||
| )} | |||
| {tabIndex === 1 && ( | |||
| {pageTab === "ops" && canSeeOpsTable && ( | |||
| <JobOrderOpsTable | |||
| printerCombo={printerCombo} | |||
| onSelectProcess={(jobOrderId) => { | |||
| @@ -183,16 +202,16 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||
| /> | |||
| )} | |||
| {tabIndex === 2 && ( | |||
| {pageTab === "jobProcessStatus" && ( | |||
| <JobProcessStatus /> | |||
| )} | |||
| {tabIndex === 3 && ( | |||
| {pageTab === "operatorKpi" && ( | |||
| <OperatorKpiDashboard /> | |||
| )} | |||
| {tabIndex === 4 && ( | |||
| {pageTab === "equipmentStatus" && ( | |||
| <EquipmentStatusDashboard /> | |||
| )} | |||
| {tabIndex === 5 && ( | |||
| {pageTab === "drinkQty" && ( | |||
| <DrinkProductionQtyDashboard /> | |||
| )} | |||
| </Box> | |||
| @@ -79,11 +79,11 @@ | |||
| "Issue": "Issue", | |||
| "Job Order Ops Table": "Job Order Ops Table", | |||
| "Pending (picked)": "Picked", | |||
| "Pending (not picked)": "Not picked", | |||
| "Pending (not picked)": "Pick incomplete", | |||
| "Processing (picked)": "Picked", | |||
| "Processing (not picked)": "Not picked", | |||
| "Processing (not picked)": "Pick incomplete", | |||
| "Picked": "Picked", | |||
| "Not picked": "Not picked", | |||
| "Not picked": "Pick incomplete", | |||
| "Stop (paused)": "Stop (paused)", | |||
| "Cancelled": "Cancelled", | |||
| "Reload data": "Reload data", | |||
| @@ -224,10 +224,10 @@ | |||
| "Off-plan unfinished": "Off-plan unfinished", | |||
| "All unfinished": "Needs action", | |||
| "Needs action": "Needs action", | |||
| "Not picked · Not started": "Not picked · Not started", | |||
| "Not picked · Not started": "Pick incomplete · Not started", | |||
| "Picked · Not started": "Picked · Not started", | |||
| "Picked · In production": "Picked · In production", | |||
| "Not picked · In production": "Not picked · In production", | |||
| "Not picked · In production": "Pick incomplete · In production", | |||
| "Powder_Mixture": "Powder Mixture", | |||
| "Processing": "Processing", | |||
| "Total lines: ": "Total lines: ", | |||
| @@ -79,11 +79,11 @@ | |||
| "Issue": "異常", | |||
| "Job Order Ops Table": "工單生產流程", | |||
| "Pending (picked)": "已提料", | |||
| "Pending (not picked)": "未提料", | |||
| "Pending (not picked)": "未完成提料", | |||
| "Processing (picked)": "已提料", | |||
| "Processing (not picked)": "未提料", | |||
| "Processing (not picked)": "未完成提料", | |||
| "Picked": "已提料", | |||
| "Not picked": "未提料", | |||
| "Not picked": "未完成提料", | |||
| "Stop (paused)": "暫停中", | |||
| "Cancelled": "已取消", | |||
| "Reload data": "重新載入", | |||
| @@ -224,10 +224,10 @@ | |||
| "Off-plan unfinished": "未按規劃完成工單", | |||
| "All unfinished": "需處理", | |||
| "Needs action": "需處理", | |||
| "Not picked · Not started": "未提料 · 未開工", | |||
| "Not picked · Not started": "未完成提料 · 未開工", | |||
| "Picked · Not started": "已提料 · 未開工", | |||
| "Picked · In production": "已提料 · 未完成生產", | |||
| "Not picked · In production": "未提料 · 未完成生產", | |||
| "Not picked · In production": "未完成提料 · 未完成生產", | |||
| "Powder_Mixture": "箱料粉", | |||
| "Processing": "生產中", | |||
| "Total lines: ": "總數量:", | |||