Register filters, inventory category, Excel download lock, and no-data dialog. Includes extra inbound-trace (rep-004) location/PP-PF filters already staged. Co-authored-by: Cursor <[email protected]>fix負數倉
| @@ -25,6 +25,7 @@ const REPORT_ICON_MAP: Record<string, SvgIconComponent> = { | |||||
| "rep-011": Inventory2OutlinedIcon, | "rep-011": Inventory2OutlinedIcon, | ||||
| "rep-007": MonetizationOnOutlinedIcon, | "rep-007": MonetizationOnOutlinedIcon, | ||||
| "rep-012": LayersOutlinedIcon, | "rep-012": LayersOutlinedIcon, | ||||
| "rep-021": Inventory2OutlinedIcon, | |||||
| "rep-010": SearchOutlinedIcon, | "rep-010": SearchOutlinedIcon, | ||||
| "rep-004": LocalShippingOutlinedIcon, | "rep-004": LocalShippingOutlinedIcon, | ||||
| "rep-014": LocalShippingOutlinedIcon, | "rep-014": LocalShippingOutlinedIcon, | ||||
| @@ -170,6 +171,7 @@ function CategoryColumn({ | |||||
| ); | ); | ||||
| } | } | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||||
| export default function ReportSelectionDashboard({ | export default function ReportSelectionDashboard({ | ||||
| selectedReportId, | selectedReportId, | ||||
| onSelectReport, | onSelectReport, | ||||
| @@ -1,6 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import React, { useState, useMemo, useEffect } from 'react'; | |||||
| import React, { useState, useMemo, useEffect, useRef } from 'react'; | |||||
| import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
| import { SessionWithTokens } from "@/config/authConfig"; | import { SessionWithTokens } from "@/config/authConfig"; | ||||
| import { AUTH } from "@/authorities"; | import { AUTH } from "@/authorities"; | ||||
| @@ -18,8 +18,13 @@ import { | |||||
| Autocomplete, | Autocomplete, | ||||
| Checkbox, | Checkbox, | ||||
| FormControlLabel, | FormControlLabel, | ||||
| Dialog, | |||||
| DialogTitle, | |||||
| DialogContent, | |||||
| DialogActions, | |||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import DownloadIcon from '@mui/icons-material/Download'; | import DownloadIcon from '@mui/icons-material/Download'; | ||||
| import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; | |||||
| import { REPORTS } from '@/config/reportConfig'; | import { REPORTS } from '@/config/reportConfig'; | ||||
| import { NEXT_PUBLIC_API_URL } from '@/config/api'; | import { NEXT_PUBLIC_API_URL } from '@/config/api'; | ||||
| import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; | import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; | ||||
| @@ -46,6 +51,7 @@ interface ItemCodeWithName { | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 60 | v1.0.1 | 2026-08-11 */ | /** FP-MTMS Version Checklist | Functions Ref. No. 60 | v1.0.1 | 2026-08-11 */ | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||||
| export default function ReportPage() { | export default function ReportPage() { | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const { t, reportTitle, fieldLabel, optionLabel } = useReportLabels(); | const { t, reportTitle, fieldLabel, optionLabel } = useReportLabels(); | ||||
| @@ -55,8 +61,10 @@ export default function ReportPage() { | |||||
| const [selectedReportId, setSelectedReportId] = useState<string>(''); | const [selectedReportId, setSelectedReportId] = useState<string>(''); | ||||
| const [criteria, setCriteria] = useState<Record<string, string>>({}); | const [criteria, setCriteria] = useState<Record<string, string>>({}); | ||||
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| const excelInFlightRef = useRef(false); | |||||
| const [dynamicOptions, setDynamicOptions] = useState<Record<string, { label: string; value: string }[]>>({}); | const [dynamicOptions, setDynamicOptions] = useState<Record<string, { label: string; value: string }[]>>({}); | ||||
| const [showConfirmDialog, setShowConfirmDialog] = useState(false); | const [showConfirmDialog, setShowConfirmDialog] = useState(false); | ||||
| const [showNoDataDialog, setShowNoDataDialog] = useState(false); | |||||
| // Find the configuration for the currently selected report | // Find the configuration for the currently selected report | ||||
| const rep012RoundIds = useMemo(() => { | const rep012RoundIds = useMemo(() => { | ||||
| @@ -76,13 +84,27 @@ export default function ReportPage() { | |||||
| const handleSelectReport = (reportId: string) => { | const handleSelectReport = (reportId: string) => { | ||||
| if (reportId === selectedReportId) return; | if (reportId === selectedReportId) return; | ||||
| setSelectedReportId(reportId); | setSelectedReportId(reportId); | ||||
| // rep-010: default QC item scope to all (show label, not empty select) | |||||
| setCriteria(reportId === 'rep-010' ? { qcType: 'all', qcItemScope: 'all' } : {}); | |||||
| if (reportId === 'rep-010') { | |||||
| setCriteria({ qcType: 'all', qcItemScope: 'all' }); | |||||
| } else if (reportId === 'rep-004') { | |||||
| setCriteria({ storeId: 'All', poPrefix: 'All' }); | |||||
| } else if (reportId === 'rep-021') { | |||||
| setCriteria({ storeId: 'All', stockTakeSectionDescription: 'All', lotOrigin: 'All' }); | |||||
| } else { | |||||
| setCriteria({}); | |||||
| } | |||||
| }; | }; | ||||
| const handleFieldChange = (name: string, value: string | string[]) => { | const handleFieldChange = (name: string, value: string | string[]) => { | ||||
| const stringValue = Array.isArray(value) ? value.join(',') : value; | const stringValue = Array.isArray(value) ? value.join(',') : value; | ||||
| setCriteria((prev) => ({ ...prev, [name]: stringValue })); | |||||
| setCriteria((prev) => { | |||||
| const next = { ...prev, [name]: stringValue }; | |||||
| if (currentReport?.id === 'rep-021' && name === 'warehouse') { | |||||
| const m = stringValue.trim().match(/^w(\d)/i); | |||||
| if (m) next.storeId = `${m[1]}F`; | |||||
| } | |||||
| return next; | |||||
| }); | |||||
| // If this is stockCategory and there's a field that depends on it, fetch dynamic options | // If this is stockCategory and there's a field that depends on it, fetch dynamic options | ||||
| if (name === 'stockCategory' && currentReport) { | if (name === 'stockCategory' && currentReport) { | ||||
| @@ -139,8 +161,27 @@ export default function ReportPage() { | |||||
| if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | ||||
| const data = await response.json(); | const data = await response.json(); | ||||
| const options = Array.isArray(data) | |||||
| ? data.map((item: any) => ({ label: item.label || item.name || item.code || String(item), value: item.value || item.code || String(item) })) | |||||
| const options = Array.isArray(data) | |||||
| ? field.name === 'stockTakeSectionDescription' | |||||
| ? (() => { | |||||
| const seen = new Set<string>(); | |||||
| const mapped: { label: string; value: string }[] = [{ label: '全部', value: 'All' }]; | |||||
| data.forEach((item: { stockTakeSectionDescription?: string; stockTakeSection?: string }) => { | |||||
| const desc = (item.stockTakeSectionDescription || '').trim(); | |||||
| if (!desc || seen.has(desc)) return; | |||||
| seen.add(desc); | |||||
| const section = (item.stockTakeSection || '').trim(); | |||||
| mapped.push({ | |||||
| label: section ? `${desc} (${section})` : desc, | |||||
| value: desc, | |||||
| }); | |||||
| }); | |||||
| return mapped; | |||||
| })() | |||||
| : data.map((item: any) => ({ | |||||
| label: item.label || item.name || item.code || String(item), | |||||
| value: item.value || item.code || String(item), | |||||
| })) | |||||
| : []; | : []; | ||||
| setDynamicOptions((prev) => ({ ...prev, [field.name]: options })); | setDynamicOptions((prev) => ({ ...prev, [field.name]: options })); | ||||
| @@ -303,6 +344,8 @@ export default function ReportPage() { | |||||
| const executeExcelReport = async () => { | const executeExcelReport = async () => { | ||||
| if (!currentReport) return; | if (!currentReport) return; | ||||
| if (excelInFlightRef.current) return; | |||||
| excelInFlightRef.current = true; | |||||
| setLoading(true); | setLoading(true); | ||||
| try { | try { | ||||
| if (currentReport.id === 'rep-014') { | if (currentReport.id === 'rep-014') { | ||||
| @@ -342,6 +385,10 @@ export default function ReportPage() { | |||||
| }); | }); | ||||
| if (response.status === 401 || response.status === 403) return; | if (response.status === 401 || response.status === 403) return; | ||||
| if (response.status === 204) { | |||||
| setShowNoDataDialog(true); | |||||
| return; | |||||
| } | |||||
| if (!response.ok) { | if (!response.ok) { | ||||
| const errorText = await response.text(); | const errorText = await response.text(); | ||||
| console.error("Response error:", errorText); | console.error("Response error:", errorText); | ||||
| @@ -378,6 +425,7 @@ export default function ReportPage() { | |||||
| alert(t('generateError')); | alert(t('generateError')); | ||||
| } finally { | } finally { | ||||
| setLoading(false); | setLoading(false); | ||||
| excelInFlightRef.current = false; | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -386,7 +434,7 @@ export default function ReportPage() { | |||||
| setLoading(true); | setLoading(true); | ||||
| try { | try { | ||||
| const queryParams = | |||||
| let queryParams = | |||||
| currentReport.id === 'rep-012' | currentReport.id === 'rep-012' | ||||
| ? buildRep012QueryString() | ? buildRep012QueryString() | ||||
| : currentReport.id === 'rep-010' | : currentReport.id === 'rep-010' | ||||
| @@ -439,6 +487,7 @@ export default function ReportPage() { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <> | |||||
| <Box sx={{ p: 4, maxWidth: 1280, margin: '0 auto' }}> | <Box sx={{ p: 4, maxWidth: 1280, margin: '0 auto' }}> | ||||
| <Typography variant="h4" gutterBottom fontWeight="bold"> | <Typography variant="h4" gutterBottom fontWeight="bold"> | ||||
| {t('title')} | {t('title')} | ||||
| @@ -460,12 +509,13 @@ export default function ReportPage() { | |||||
| <Grid container spacing={3}> | <Grid container spacing={3}> | ||||
| {currentReport.fields.map((field) => { | {currentReport.fields.map((field) => { | ||||
| const translatedLabel = fieldLabel(currentReport.id, field); | const translatedLabel = fieldLabel(currentReport.id, field); | ||||
| const options = field.dynamicOptions | |||||
| ? (dynamicOptions[field.name] || []) | |||||
| : (field.options || []).map((opt) => ({ | |||||
| ...opt, | |||||
| label: optionLabel(currentReport.id, field.name, opt), | |||||
| })); | |||||
| const rawOptions = field.dynamicOptions | |||||
| ? (dynamicOptions[field.name] || field.options || []) | |||||
| : (field.options || []); | |||||
| const options = rawOptions.map((opt) => ({ | |||||
| ...opt, | |||||
| label: optionLabel(currentReport.id, field.name, opt), | |||||
| })); | |||||
| const currentValue = criteria[field.name] || ''; | const currentValue = criteria[field.name] || ''; | ||||
| const valueForSelect = field.multiple | const valueForSelect = field.multiple | ||||
| ? (currentValue ? currentValue.split(',').map(v => v.trim()).filter(v => v) : []) | ? (currentValue ? currentValue.split(',').map(v => v.trim()).filter(v => v) : []) | ||||
| @@ -602,7 +652,7 @@ export default function ReportPage() { | |||||
| label={translatedLabel} | label={translatedLabel} | ||||
| type={field.type} | type={field.type} | ||||
| placeholder={field.placeholder} | placeholder={field.placeholder} | ||||
| disabled={disabledByCheckedCheckbox || disabledRep012Status} | |||||
| disabled={disabledByCheckedCheckbox || disabledRep012Status || !!field.disabled} | |||||
| InputLabelProps={field.type === 'date' ? { shrink: true } : {}} | InputLabelProps={field.type === 'date' ? { shrink: true } : {}} | ||||
| inputProps={ | inputProps={ | ||||
| field.type === 'date' && field.minDate === 'today' | field.type === 'date' && field.minDate === 'today' | ||||
| @@ -778,5 +828,49 @@ export default function ReportPage() { | |||||
| </Card> | </Card> | ||||
| )} | )} | ||||
| </Box> | </Box> | ||||
| <Dialog | |||||
| open={showNoDataDialog} | |||||
| onClose={() => setShowNoDataDialog(false)} | |||||
| maxWidth="sm" | |||||
| fullWidth | |||||
| PaperProps={{ | |||||
| sx: { | |||||
| borderRadius: 3, | |||||
| px: 1, | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1.5, pt: 3, px: 4, pb: 1.5 }}> | |||||
| <Box | |||||
| sx={{ | |||||
| width: 36, | |||||
| height: 36, | |||||
| borderRadius: '50%', | |||||
| bgcolor: 'warning.light', | |||||
| color: 'warning.dark', | |||||
| display: 'flex', | |||||
| alignItems: 'center', | |||||
| justifyContent: 'center', | |||||
| flexShrink: 0, | |||||
| }} | |||||
| > | |||||
| <InfoOutlinedIcon fontSize="small" /> | |||||
| </Box> | |||||
| <Typography component="span" variant="h6" fontWeight="bold"> | |||||
| {t('noDataFoundTitle')} | |||||
| </Typography> | |||||
| </DialogTitle> | |||||
| <DialogContent sx={{ px: 4, pt: 0.5 }}> | |||||
| <Typography color="text.secondary" sx={{ pl: 6.5 }}> | |||||
| {t('noDataFoundHint')} | |||||
| </Typography> | |||||
| </DialogContent> | |||||
| <DialogActions sx={{ justifyContent: 'center', px: 4, pb: 3, pt: 2 }}> | |||||
| <Button variant="contained" onClick={() => setShowNoDataDialog(false)} autoFocus sx={{ minWidth: 96 }}> | |||||
| {t('ok')} | |||||
| </Button> | |||||
| </DialogActions> | |||||
| </Dialog> | |||||
| </> | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -9,7 +9,7 @@ export interface ReportCategoryConfig { | |||||
| reportIds: string[]; | reportIds: string[]; | ||||
| } | } | ||||
| /** Display order and grouping for the report management dashboard. */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||||
| export const REPORT_CATEGORIES: ReportCategoryConfig[] = [ | export const REPORT_CATEGORIES: ReportCategoryConfig[] = [ | ||||
| { | { | ||||
| id: "inventory", | id: "inventory", | ||||
| @@ -17,7 +17,7 @@ export const REPORT_CATEGORIES: ReportCategoryConfig[] = [ | |||||
| headerBg: "#b8e0b8", | headerBg: "#b8e0b8", | ||||
| bodyBg: "#eef8ee", | bodyBg: "#eef8ee", | ||||
| accent: "#2e7d32", | accent: "#2e7d32", | ||||
| reportIds: ["rep-011", "rep-007", "rep-012", "rep-010"], | |||||
| reportIds: ["rep-011", "rep-007", "rep-012", "rep-021", "rep-010"], | |||||
| }, | }, | ||||
| { | { | ||||
| id: "inbound-outbound", | id: "inbound-outbound", | ||||
| @@ -19,6 +19,8 @@ export interface ReportField { | |||||
| disablesFieldsWhenChecked?: string[]; | disablesFieldsWhenChecked?: string[]; | ||||
| /** For date fields: restrict picker so value cannot be before today */ | /** For date fields: restrict picker so value cannot be before today */ | ||||
| minDate?: 'today'; | minDate?: 'today'; | ||||
| /** Disable the input (e.g. date locked to today) */ | |||||
| disabled?: boolean; | |||||
| } | } | ||||
| export type ReportResponseType = 'pdf' | 'excel'; | export type ReportResponseType = 'pdf' | 'excel'; | ||||
| @@ -32,6 +34,7 @@ export interface ReportDefinition { | |||||
| fields: ReportField[]; | fields: ReportField[]; | ||||
| } | } | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||||
| export const REPORTS: ReportDefinition[] = [ | export const REPORTS: ReportDefinition[] = [ | ||||
| //{ | //{ | ||||
| // id: "rep-001", | // id: "rep-001", | ||||
| @@ -81,10 +84,37 @@ export const REPORTS: ReportDefinition[] = [ | |||||
| title: "入倉追蹤報告", | title: "入倉追蹤報告", | ||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-in-traceability`, | apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-in-traceability`, | ||||
| fields: [ | fields: [ | ||||
| { label: "入倉日期:由 Last In Date Start", name: "lastInDateStart", type: "date", required: false }, | { label: "入倉日期:由 Last In Date Start", name: "lastInDateStart", type: "date", required: false }, | ||||
| { label: "入倉日期:至 Last In Date End", name: "lastInDateEnd", type: "date", required: false }, | { label: "入倉日期:至 Last In Date End", name: "lastInDateEnd", type: "date", required: false }, | ||||
| { label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false}, | { label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false}, | ||||
| { | |||||
| label: "樓層 Store ID", | |||||
| name: "storeId", | |||||
| type: "select", | |||||
| required: false, | |||||
| options: [ | |||||
| { label: "全部", value: "All" }, | |||||
| { label: "1F", value: "1F" }, | |||||
| { label: "2F", value: "2F" }, | |||||
| { label: "3F", value: "3F" }, | |||||
| { label: "4F", value: "4F" }, | |||||
| ], | |||||
| }, | |||||
| { label: "倉庫 Warehouse", name: "warehouse", type: "text", required: false, placeholder: "e.g. W201" }, | |||||
| { label: "區域 Area", name: "area", type: "text", required: false, placeholder: "e.g. #A" }, | |||||
| { label: "儲位 Slot", name: "slot", type: "text", required: false, placeholder: "e.g. 01" }, | |||||
| { label: "批號 Lot No", name: "lotNo", type: "text", required: false, placeholder: "e.g. LT-202608" }, | |||||
| { | |||||
| label: "PP/PF 分類", | |||||
| name: "poPrefix", | |||||
| type: "select", | |||||
| required: false, | |||||
| options: [ | |||||
| { label: "全部", value: "All" }, | |||||
| { label: "PP", value: "PP" }, | |||||
| { label: "PF", value: "PF" }, | |||||
| ], | |||||
| }, | |||||
| ] | ] | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -169,6 +199,80 @@ export const REPORTS: ReportDefinition[] = [ | |||||
| }, | }, | ||||
| ] | ] | ||||
| }, | }, | ||||
| /* Hidden for now: 庫存批次結餘報告 (rep-019) | |||||
| { | |||||
| id: "rep-019", | |||||
| title: "庫存批次結餘報告", | |||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-lot-balance`, | |||||
| responseType: "excel", | |||||
| fields: [ | |||||
| { label: "庫存日期 Stock Date(僅今天)", name: "stockDate", type: "date", required: true, disabled: true }, | |||||
| { label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false }, | |||||
| { label: "倉位 Warehouse Code", name: "warehouseCode", type: "text", required: false, placeholder: "e.g. W200 or 2F-W200-#A-00" }, | |||||
| { | |||||
| label: "樓層 Store ID", | |||||
| name: "storeId", | |||||
| type: "select", | |||||
| required: false, | |||||
| options: [ | |||||
| { label: "全部", value: "All" }, | |||||
| { label: "1F", value: "1F" }, | |||||
| { label: "2F", value: "2F" }, | |||||
| { label: "3F", value: "3F" }, | |||||
| { label: "4F", value: "4F" }, | |||||
| ], | |||||
| }, | |||||
| { label: "批號 Lot No", name: "lotNo", type: "text", required: false, placeholder: "e.g. LT-202608" }, | |||||
| ], | |||||
| }, | |||||
| */ | |||||
| { | |||||
| id: "rep-021", | |||||
| title: "庫存批次現況報告", | |||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-lot-onhand`, | |||||
| responseType: "excel", | |||||
| fields: [ | |||||
| { label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false }, | |||||
| { | |||||
| label: "樓層 Store ID", | |||||
| name: "storeId", | |||||
| type: "select", | |||||
| required: false, | |||||
| options: [ | |||||
| { label: "全部", value: "All" }, | |||||
| { label: "1F", value: "1F" }, | |||||
| { label: "2F", value: "2F" }, | |||||
| { label: "3F", value: "3F" }, | |||||
| { label: "4F", value: "4F" }, | |||||
| ], | |||||
| }, | |||||
| { label: "倉庫 Warehouse", name: "warehouse", type: "text", required: false, placeholder: "e.g. W201" }, | |||||
| { label: "區域 Area", name: "area", type: "text", required: false, placeholder: "e.g. #A" }, | |||||
| { label: "儲位 Slot", name: "slot", type: "text", required: false, placeholder: "e.g. 02" }, | |||||
| { | |||||
| label: "盤點區域說明 Stock Take Section", | |||||
| name: "stockTakeSectionDescription", | |||||
| type: "select", | |||||
| required: false, | |||||
| dynamicOptions: true, | |||||
| dynamicOptionsEndpoint: `${NEXT_PUBLIC_API_URL}/warehouse/stockTakeSections`, | |||||
| options: [{ label: "全部", value: "All" }], | |||||
| }, | |||||
| { label: "批號 Lot No", name: "lotNo", type: "text", required: false, placeholder: "e.g. LT-202608" }, | |||||
| { | |||||
| label: "來源 Lot Origin", | |||||
| name: "lotOrigin", | |||||
| type: "select", | |||||
| required: false, | |||||
| options: [ | |||||
| { label: "全部", value: "All" }, | |||||
| { label: "PP", value: "PP" }, | |||||
| { label: "PF", value: "PF" }, | |||||
| { label: "其他", value: "other" }, | |||||
| ], | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { id: "rep-011", | { id: "rep-011", | ||||
| title: "庫存明細報告", | title: "庫存明細報告", | ||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-ledger`, | apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-ledger`, | ||||
| @@ -178,6 +282,19 @@ export const REPORTS: ReportDefinition[] = [ | |||||
| { label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false}, | { label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false}, | ||||
| ] | ] | ||||
| }, | }, | ||||
| /* Hidden for now: 庫存流水帳報告 (rep-020) | |||||
| { | |||||
| id: "rep-020", | |||||
| title: "庫存流水帳報告", | |||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-lot-ledger`, | |||||
| responseType: "excel", | |||||
| fields: [ | |||||
| { label: "期間起 Period From(永遠該月1日)", name: "lastInDateStart", type: "date", required: true }, | |||||
| { label: "期間迄 Period To", name: "lastInDateEnd", type: "date", required: true }, | |||||
| { label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false }, | |||||
| ], | |||||
| }, | |||||
| */ | |||||
| /* | /* | ||||
| { | { | ||||
| id: "rep-007", | id: "rep-007", | ||||
| @@ -236,6 +353,7 @@ export const REPORTS: ReportDefinition[] = [ | |||||
| ] | ] | ||||
| }, | }, | ||||
| { id: "rep-010", | { id: "rep-010", | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 64 | v1.0.0 | 2026-08-11 */ | /** FP-MTMS Version Checklist | Functions Ref. No. 64 | v1.0.0 | 2026-08-11 */ | ||||
| title: "庫存品質檢測報告", | title: "庫存品質檢測報告", | ||||
| @@ -11,6 +11,10 @@ | |||||
| "generatingExcel": "Generating Excel...", | "generatingExcel": "Generating Excel...", | ||||
| "generatingReport": "Generating report...", | "generatingReport": "Generating report...", | ||||
| "generateError": "An error occurred while generating the report. Please try again.", | "generateError": "An error occurred while generating the report. Please try again.", | ||||
| "noDataFound": "No data found", | |||||
| "noDataFoundTitle": "No Data Found / 查無資料", | |||||
| "noDataFoundHint": "No inventory records match your search. Please try adjusting the filters.", | |||||
| "ok": "OK", | |||||
| "missingRequired": "Missing required fields:\n- {{fields}}", | "missingRequired": "Missing required fields:\n- {{fields}}", | ||||
| "dateNotBeforeToday": "Date cannot be before today:\n- {{fields}}", | "dateNotBeforeToday": "Date cannot be before today:\n- {{fields}}", | ||||
| "selectOrEnterItemCode": "Select or enter item code", | "selectOrEnterItemCode": "Select or enter item code", | ||||
| @@ -43,7 +47,23 @@ | |||||
| "fields": { | "fields": { | ||||
| "lastInDateStart": "Last In Date Start", | "lastInDateStart": "Last In Date Start", | ||||
| "lastInDateEnd": "Last In Date End", | "lastInDateEnd": "Last In Date End", | ||||
| "itemCode": "Item Code" | |||||
| "itemCode": "Item Code", | |||||
| "storeId": "Floor", | |||||
| "warehouse": "Warehouse", | |||||
| "area": "Area", | |||||
| "slot": "Slot", | |||||
| "lotNo": "Lot No", | |||||
| "poPrefix": "PP/PF" | |||||
| }, | |||||
| "options": { | |||||
| "storeId": { | |||||
| "All": "All" | |||||
| }, | |||||
| "poPrefix": { | |||||
| "All": "All", | |||||
| "PP": "PP", | |||||
| "PF": "PF" | |||||
| } | |||||
| } | } | ||||
| }, | }, | ||||
| "rep-008": { | "rep-008": { | ||||
| @@ -213,6 +233,33 @@ | |||||
| "All": "All" | "All": "All" | ||||
| } | } | ||||
| } | } | ||||
| }, | |||||
| "rep-021": { | |||||
| "title": "Stock Lot On-hand Report", | |||||
| "fields": { | |||||
| "itemCode": "Item Code", | |||||
| "storeId": "Floor", | |||||
| "warehouse": "Warehouse", | |||||
| "area": "Area", | |||||
| "slot": "Slot", | |||||
| "stockTakeSectionDescription": "Stock Take Section", | |||||
| "lotNo": "Lot No", | |||||
| "lotOrigin": "Lot Origin" | |||||
| }, | |||||
| "options": { | |||||
| "storeId": { | |||||
| "All": "All" | |||||
| }, | |||||
| "stockTakeSectionDescription": { | |||||
| "All": "All" | |||||
| }, | |||||
| "lotOrigin": { | |||||
| "All": "All", | |||||
| "PP": "PP", | |||||
| "PF": "PF", | |||||
| "other": "Other" | |||||
| } | |||||
| } | |||||
| } | } | ||||
| }, | }, | ||||
| "excel": { | "excel": { | ||||
| @@ -11,6 +11,10 @@ | |||||
| "generatingExcel": "生成 Excel...", | "generatingExcel": "生成 Excel...", | ||||
| "generatingReport": "生成報告...", | "generatingReport": "生成報告...", | ||||
| "generateError": "產生報告時發生錯誤,請再試一次。", | "generateError": "產生報告時發生錯誤,請再試一次。", | ||||
| "noDataFound": "查無資料", | |||||
| "noDataFoundTitle": "查無資料", | |||||
| "noDataFoundHint": "找不到符合您搜尋條件的庫存資料,請嘗試調整篩選條件。", | |||||
| "ok": "確定", | |||||
| "missingRequired": "缺少必填條件:\n- {{fields}}", | "missingRequired": "缺少必填條件:\n- {{fields}}", | ||||
| "dateNotBeforeToday": "日期不可早於今天:\n- {{fields}}", | "dateNotBeforeToday": "日期不可早於今天:\n- {{fields}}", | ||||
| "selectOrEnterItemCode": "選擇或輸入物料編號", | "selectOrEnterItemCode": "選擇或輸入物料編號", | ||||
| @@ -43,7 +47,23 @@ | |||||
| "fields": { | "fields": { | ||||
| "lastInDateStart": "入倉日期:由 Last In Date Start", | "lastInDateStart": "入倉日期:由 Last In Date Start", | ||||
| "lastInDateEnd": "入倉日期:至 Last In Date End", | "lastInDateEnd": "入倉日期:至 Last In Date End", | ||||
| "itemCode": "貨品編號 Item Code" | |||||
| "itemCode": "貨品編號 Item Code", | |||||
| "storeId": "樓層 Store ID", | |||||
| "warehouse": "倉庫 Warehouse", | |||||
| "area": "區域 Area", | |||||
| "slot": "儲位 Slot", | |||||
| "lotNo": "批號 Lot No", | |||||
| "poPrefix": "PP/PF 分類" | |||||
| }, | |||||
| "options": { | |||||
| "storeId": { | |||||
| "All": "全部" | |||||
| }, | |||||
| "poPrefix": { | |||||
| "All": "全部", | |||||
| "PP": "PP", | |||||
| "PF": "PF" | |||||
| } | |||||
| } | } | ||||
| }, | }, | ||||
| "rep-008": { | "rep-008": { | ||||
| @@ -213,6 +233,33 @@ | |||||
| "All": "全部" | "All": "全部" | ||||
| } | } | ||||
| } | } | ||||
| }, | |||||
| "rep-021": { | |||||
| "title": "庫存批次現況報告", | |||||
| "fields": { | |||||
| "itemCode": "貨品編號 Item Code", | |||||
| "storeId": "樓層 Store ID", | |||||
| "warehouse": "倉庫 Warehouse", | |||||
| "area": "區域 Area", | |||||
| "slot": "儲位 Slot", | |||||
| "stockTakeSectionDescription": "盤點區域說明 Stock Take Section", | |||||
| "lotNo": "批號 Lot No", | |||||
| "lotOrigin": "來源 Lot Origin" | |||||
| }, | |||||
| "options": { | |||||
| "storeId": { | |||||
| "All": "全部" | |||||
| }, | |||||
| "stockTakeSectionDescription": { | |||||
| "All": "全部" | |||||
| }, | |||||
| "lotOrigin": { | |||||
| "All": "全部", | |||||
| "PP": "PP", | |||||
| "PF": "PF", | |||||
| "other": "其他" | |||||
| } | |||||
| } | |||||
| } | } | ||||
| }, | }, | ||||
| "excel": { | "excel": { | ||||