| @@ -26,6 +26,7 @@ import { | |||
| generateSemiFGProductionAnalysisReportExcel, | |||
| ItemCodeWithCategory, | |||
| } from './semiFGProductionAnalysisApi'; | |||
| import { parseItemCodeTokens } from './parseItemCodeTokens'; | |||
| interface SemiFGProductionAnalysisReportProps { | |||
| criteria: Record<string, string>; | |||
| @@ -76,16 +77,15 @@ export default function SemiFGProductionAnalysisReport({ | |||
| return; | |||
| } | |||
| // If no itemCode is selected, export directly without confirmation | |||
| if (!criteria.itemCode) { | |||
| const selectedCodes = parseItemCodeTokens( | |||
| [criteria.itemCode, criteria.itemCodePaste].filter(Boolean).join(' '), | |||
| ); | |||
| if (selectedCodes.length === 0) { | |||
| await executeExport(format); | |||
| return; | |||
| } | |||
| // If itemCode is selected, show confirmation dialog | |||
| const selectedCodes = criteria.itemCode.split(',').filter((code) => code.trim()); | |||
| const itemCodesInfo: ItemCodeWithCategory[] = selectedCodes.map((code) => { | |||
| const codeTrimmed = code.trim(); | |||
| const itemCodesInfo: ItemCodeWithCategory[] = selectedCodes.map((codeTrimmed) => { | |||
| const categoryInfo = itemCodesWithCategory[codeTrimmed]; | |||
| return { | |||
| code: codeTrimmed, | |||
| @@ -2,6 +2,7 @@ | |||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; | |||
| import { buildItemCodePasteRequestBody } from "./parseItemCodeTokens"; | |||
| import { | |||
| exportMultiSheetToXlsx, | |||
| } from "@/app/(main)/chart/_components/exportChartToXlsx"; | |||
| @@ -124,12 +125,15 @@ function emptyMaterialSheetRow( | |||
| export async function fetchBomShopSyncReportData( | |||
| criteria: Record<string, string>, | |||
| ): Promise<BomShopSyncReportResponse> { | |||
| const queryParams = new URLSearchParams(criteria).toString(); | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history?${queryParams}`; | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history`; | |||
| const response = await clientAuthFetch(url, { | |||
| method: "GET", | |||
| headers: { Accept: "application/json" }, | |||
| method: "POST", | |||
| headers: { | |||
| Accept: "application/json", | |||
| "Content-Type": "application/json", | |||
| }, | |||
| body: JSON.stringify(buildItemCodePasteRequestBody(criteria, "finishedItemCode")), | |||
| }); | |||
| if (response.status === 401 || response.status === 403) | |||
| @@ -1,6 +1,7 @@ | |||
| "use client"; | |||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| import { buildItemCodePasteRequestBody } from './parseItemCodeTokens'; | |||
| import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; | |||
| import { | |||
| exportChartToXlsx, | |||
| @@ -64,17 +65,20 @@ export interface GrnReportResponse { | |||
| /** | |||
| * Fetch GRN (Goods Received Note) report data by date range. | |||
| * Backend: GET /report/grn-report?receiptDateStart=&receiptDateEnd=&itemCode= | |||
| * Backend: POST /report/grn-report { receiptDateStart, receiptDateEnd, itemCodes } | |||
| */ | |||
| export async function fetchGrnReportData( | |||
| criteria: Record<string, string> | |||
| ): Promise<{ rows: GrnReportRow[]; listedPoAmounts?: ListedPoAmounts }> { | |||
| const queryParams = new URLSearchParams(criteria).toString(); | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/grn-report?${queryParams}`; | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/grn-report`; | |||
| const response = await clientAuthFetch(url, { | |||
| method: "GET", | |||
| headers: { Accept: "application/json" }, | |||
| method: "POST", | |||
| headers: { | |||
| Accept: "application/json", | |||
| "Content-Type": "application/json", | |||
| }, | |||
| body: JSON.stringify(buildItemCodePasteRequestBody(criteria, "itemCode")), | |||
| }); | |||
| if (response.status === 401 || response.status === 403) | |||
| @@ -26,7 +26,7 @@ import { | |||
| import DownloadIcon from '@mui/icons-material/Download'; | |||
| import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; | |||
| import { REPORTS } from '@/config/reportConfig'; | |||
| import { mergePastedItemCodes, buildStockBalanceRequestBody } from './parseItemCodeTokens'; | |||
| import { mergePastedItemCodes, buildItemCodePasteRequestBody } from './parseItemCodeTokens'; | |||
| import { NEXT_PUBLIC_API_URL } from '@/config/api'; | |||
| import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; | |||
| import SemiFGProductionAnalysisReport from './SemiFGProductionAnalysisReport'; | |||
| @@ -75,6 +75,15 @@ const FIELD_ERROR_SX = { | |||
| }, | |||
| }; | |||
| const ITEM_CODE_PASTE_POST_REPORTS = new Set([ | |||
| 'rep-004', | |||
| 'rep-006', | |||
| 'rep-007', | |||
| 'rep-008', | |||
| 'rep-011', | |||
| 'rep-013', | |||
| ]); | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.3 | 2026-09-10 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 60 | v1.0.1 | 2026-08-11 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.2 | 2026-09-10 */ | |||
| @@ -359,11 +368,19 @@ export default function ReportPage() { | |||
| return p.toString(); | |||
| }; | |||
| const itemCodeFieldName = (): string => | |||
| currentReport?.fields.some((f) => f.name === 'finishedItemCode') | |||
| ? 'finishedItemCode' | |||
| : 'itemCode'; | |||
| const isItemCodePastePostReport = (): boolean => | |||
| !!currentReport && ITEM_CODE_PASTE_POST_REPORTS.has(currentReport.id); | |||
| const buildCriteriaQueryString = (): string => { | |||
| if (!currentReport) return ''; | |||
| if (currentReport.id === 'rep-012') return buildRep012QueryString(); | |||
| if (currentReport.id === 'rep-010') return buildRep010QueryString(); | |||
| const merged = mergePastedItemCodes(criteria); | |||
| const merged = mergePastedItemCodes(criteria, itemCodeFieldName()); | |||
| const p = new URLSearchParams(merged); | |||
| if (currentReport.id === 'rep-016') { | |||
| const day = (merged.dateStart || '').trim(); | |||
| @@ -416,19 +433,19 @@ export default function ReportPage() { | |||
| await generateShopOrderReplenishmentReportExcel(criteria, reportTitle(currentReport), t); | |||
| } else { | |||
| // Backend returns actual .xlsx bytes for this Excel endpoint. | |||
| const isStockBalance = currentReport.id === 'rep-007'; | |||
| const excelUrl = isStockBalance | |||
| const usesItemCodePastePost = isItemCodePastePostReport(); | |||
| const excelUrl = usesItemCodePastePost | |||
| ? `${currentReport.apiEndpoint}-excel` | |||
| : `${currentReport.apiEndpoint}-excel?${buildCriteriaQueryString()}`; | |||
| const response = await clientAuthFetch(excelUrl, isStockBalance | |||
| const response = await clientAuthFetch(excelUrl, usesItemCodePastePost | |||
| ? { | |||
| method: 'POST', | |||
| headers: { | |||
| Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| body: JSON.stringify(buildStockBalanceRequestBody(criteria)), | |||
| body: JSON.stringify(buildItemCodePasteRequestBody(criteria, itemCodeFieldName())), | |||
| } | |||
| : { | |||
| method: 'GET', | |||
| @@ -485,19 +502,19 @@ export default function ReportPage() { | |||
| setLoading(true); | |||
| try { | |||
| const isStockBalance = currentReport.id === 'rep-007'; | |||
| const url = isStockBalance | |||
| const usesItemCodePastePost = isItemCodePastePostReport(); | |||
| const url = usesItemCodePastePost | |||
| ? currentReport.apiEndpoint | |||
| : `${currentReport.apiEndpoint}?${buildCriteriaQueryString()}`; | |||
| const response = await clientAuthFetch(url, isStockBalance | |||
| const response = await clientAuthFetch(url, usesItemCodePastePost | |||
| ? { | |||
| method: 'POST', | |||
| headers: { | |||
| Accept: 'application/pdf', | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| body: JSON.stringify(buildStockBalanceRequestBody(criteria)), | |||
| body: JSON.stringify(buildItemCodePasteRequestBody(criteria, itemCodeFieldName())), | |||
| } | |||
| : { | |||
| method: 'GET', | |||
| @@ -16,32 +16,47 @@ export function parseItemCodeTokens(raw: string | undefined | null): string[] { | |||
| return tokens; | |||
| } | |||
| /** Merge multi-select `itemCode` with pasted `itemCodePaste`; drop the paste field from API params. */ | |||
| export function mergePastedItemCodes(criteria: Record<string, string>): Record<string, string> { | |||
| export function resolveItemCodeFieldName(criteria: Record<string, string>): string { | |||
| return Object.prototype.hasOwnProperty.call(criteria, "finishedItemCode") | |||
| ? "finishedItemCode" | |||
| : "itemCode"; | |||
| } | |||
| /** Merge multi-select item codes with pasted `itemCodePaste`; drop the paste field from API params. */ | |||
| export function mergePastedItemCodes( | |||
| criteria: Record<string, string>, | |||
| codeField = resolveItemCodeFieldName(criteria), | |||
| ): Record<string, string> { | |||
| const next: Record<string, string> = { ...criteria }; | |||
| delete next.itemCodePaste; | |||
| const merged = parseItemCodeTokens( | |||
| [criteria.itemCode, criteria.itemCodePaste].filter(Boolean).join(" "), | |||
| [criteria[codeField], criteria.itemCodePaste].filter(Boolean).join(" "), | |||
| ); | |||
| if (merged.length > 0) { | |||
| next.itemCode = merged.join(","); | |||
| next[codeField] = merged.join(","); | |||
| } else { | |||
| delete next.itemCode; | |||
| delete next[codeField]; | |||
| } | |||
| return next; | |||
| } | |||
| /** POST body for 庫存結餘報告 so pasted codes are not limited by URL length. */ | |||
| export function buildStockBalanceRequestBody(criteria: Record<string, string>): { | |||
| stockDate?: string; | |||
| itemCodes?: string[]; | |||
| } { | |||
| /** POST body so pasted codes are not limited by URL length. */ | |||
| export function buildItemCodePasteRequestBody( | |||
| criteria: Record<string, string>, | |||
| codeField = resolveItemCodeFieldName(criteria), | |||
| ): Record<string, unknown> { | |||
| const itemCodes = parseItemCodeTokens( | |||
| [criteria.itemCode, criteria.itemCodePaste].filter(Boolean).join(" "), | |||
| [criteria[codeField], criteria.itemCodePaste].filter(Boolean).join(" "), | |||
| ); | |||
| const body: { stockDate?: string; itemCodes?: string[] } = {}; | |||
| const stockDate = criteria.stockDate?.trim(); | |||
| if (stockDate) body.stockDate = stockDate; | |||
| if (itemCodes.length) body.itemCodes = itemCodes; | |||
| const body: Record<string, unknown> = {}; | |||
| for (const [key, value] of Object.entries(criteria)) { | |||
| if (key === "itemCodePaste" || key === codeField) continue; | |||
| const trimmed = value?.trim(); | |||
| if (trimmed) body[key] = trimmed; | |||
| } | |||
| if (itemCodes.length) { | |||
| body[codeField] = itemCodes.join(","); | |||
| body.itemCodes = itemCodes; | |||
| } | |||
| return body; | |||
| } | |||
| @@ -2,6 +2,7 @@ | |||
| import { NEXT_PUBLIC_API_URL } from '@/config/api'; | |||
| import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; | |||
| import { buildItemCodePasteRequestBody } from './parseItemCodeTokens'; | |||
| export interface ItemCodeWithName { | |||
| code: string; | |||
| @@ -72,12 +73,15 @@ export const generateSemiFGProductionAnalysisReport = async ( | |||
| criteria: Record<string, string>, | |||
| reportTitle: string = '成品/半成品生產分析報告' | |||
| ): Promise<void> => { | |||
| const queryParams = new URLSearchParams(criteria).toString(); | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/print-semi-fg-production-analysis?${queryParams}`; | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/print-semi-fg-production-analysis`; | |||
| const response = await clientAuthFetch(url, { | |||
| method: 'GET', | |||
| headers: { Accept: 'application/pdf' }, | |||
| method: 'POST', | |||
| headers: { | |||
| Accept: 'application/pdf', | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| body: JSON.stringify(buildItemCodePasteRequestBody(criteria, 'itemCode')), | |||
| }); | |||
| if (response.status === 401 || response.status === 403) throw new Error("Unauthorized"); | |||
| @@ -111,12 +115,15 @@ export const generateSemiFGProductionAnalysisReportExcel = async ( | |||
| criteria: Record<string, string>, | |||
| reportTitle: string = '成品/半成品生產分析報告' | |||
| ): Promise<void> => { | |||
| const queryParams = new URLSearchParams(criteria).toString(); | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/print-semi-fg-production-analysis-excel?${queryParams}`; | |||
| const url = `${NEXT_PUBLIC_API_URL}/report/print-semi-fg-production-analysis-excel`; | |||
| const response = await clientAuthFetch(url, { | |||
| method: 'GET', | |||
| headers: { Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, | |||
| method: 'POST', | |||
| headers: { | |||
| Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| body: JSON.stringify(buildItemCodePasteRequestBody(criteria, 'itemCode')), | |||
| }); | |||
| if (response.status === 401 || response.status === 403) throw new Error('Unauthorized'); | |||
| @@ -305,34 +305,45 @@ const PoSearch: React.FC<Props> = ({ | |||
| const res = await baseListResp.json(); | |||
| if (!res) return; | |||
| if (res.records && res.records.length > 0) { | |||
| setFilteredPo(res.records); | |||
| setTotalCount(res.total); | |||
| return; | |||
| } | |||
| const records: PoResult[] = res.records ?? []; | |||
| const searchedCodeRaw = (filterArgs as any)?.code; | |||
| const searchedCode = | |||
| typeof searchedCodeRaw === "string" ? searchedCodeRaw.trim() : ""; | |||
| const shouldAutoSyncFromM18 = | |||
| const isM18PoCode = | |||
| searchedCode.length > 14 && | |||
| (searchedCode.startsWith("PP") || searchedCode.startsWith("PF")); | |||
| const hasLocalPending = records.some( | |||
| (row) => String(row.status ?? "").toLowerCase() === "pending", | |||
| ); | |||
| const shouldLookupMissing = records.length === 0 && isM18PoCode; | |||
| const shouldRefreshPending = hasLocalPending && isM18PoCode; | |||
| if (!shouldAutoSyncFromM18 || autoSyncInProgressRef.current) { | |||
| setFilteredPo(res.records); | |||
| if ( | |||
| (!shouldLookupMissing && !shouldRefreshPending) || | |||
| autoSyncInProgressRef.current | |||
| ) { | |||
| setFilteredPo(records); | |||
| setTotalCount(res.total); | |||
| return; | |||
| } | |||
| if (shouldRefreshPending) { | |||
| setFilteredPo(records); | |||
| setTotalCount(res.total); | |||
| } | |||
| try { | |||
| autoSyncInProgressRef.current = true; | |||
| setIsM18LookupLoading(true); | |||
| setAutoSyncStatus("正在從M18找尋PO..."); | |||
| setIsM18LookupLoading(shouldLookupMissing); | |||
| setAutoSyncStatus( | |||
| shouldLookupMissing | |||
| ? "正在從M18找尋PO..." | |||
| : "正在檢查M18是否有更新...", | |||
| ); | |||
| const syncResp = await clientAuthFetch( | |||
| `${NEXT_PUBLIC_API_URL}/m18/test/po-by-code?code=${encodeURIComponent( | |||
| searchedCode, | |||
| )}`, | |||
| )}&ifNewer=true`, | |||
| { method: "GET" }, | |||
| ); | |||
| @@ -350,8 +361,13 @@ const PoSearch: React.FC<Props> = ({ | |||
| } | |||
| const syncOk = Boolean(syncJson?.totalSuccess && syncJson.totalSuccess > 0); | |||
| const skippedIfNewer = String(syncJson?.query ?? "").includes( | |||
| "skipped (ifNewer)", | |||
| ); | |||
| if (syncOk) { | |||
| setAutoSyncStatus("成功找到PO"); | |||
| setAutoSyncStatus( | |||
| shouldLookupMissing ? "成功找到PO" : "已從M18更新PO", | |||
| ); | |||
| const listResp = await clientAuthFetch( | |||
| `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams( | |||
| @@ -363,21 +379,24 @@ const PoSearch: React.FC<Props> = ({ | |||
| const listJson = await listResp.json(); | |||
| setFilteredPo(listJson.records ?? []); | |||
| setTotalCount(listJson.total ?? 0); | |||
| setAutoSyncStatus("成功找到PO"); | |||
| setAutoSyncStatus( | |||
| shouldLookupMissing ? "成功找到PO" : "已從M18更新PO", | |||
| ); | |||
| return; | |||
| } | |||
| setAutoSyncStatus("找不到PO"); | |||
| setAutoSyncStatus(shouldLookupMissing ? "找不到PO" : null); | |||
| } else if (skippedIfNewer || shouldRefreshPending) { | |||
| setAutoSyncStatus(null); | |||
| } else { | |||
| setAutoSyncStatus("找不到PO"); | |||
| } | |||
| // Ensure UI updates even if sync didn't change results | |||
| setFilteredPo(res.records); | |||
| setFilteredPo(records); | |||
| setTotalCount(res.total ?? 0); | |||
| } catch (e) { | |||
| console.error("Auto sync error:", e); | |||
| setAutoSyncStatus("找不到PO"); | |||
| setFilteredPo(res.records); | |||
| setAutoSyncStatus(shouldLookupMissing ? "找不到PO" : null); | |||
| setFilteredPo(records); | |||
| setTotalCount(res.total ?? 0); | |||
| } finally { | |||
| setIsM18LookupLoading(false); | |||
| @@ -481,7 +500,9 @@ const PoSearch: React.FC<Props> = ({ | |||
| sx={{ color: "#fff", zIndex: (theme) => theme.zIndex.modal + 1, flexDirection: "column", gap: 1 }} | |||
| > | |||
| <CircularProgress color="inherit" /> | |||
| <Typography variant="body1">正在從M18找尋PO...</Typography> | |||
| <Typography variant="body1"> | |||
| {autoSyncStatus || "正在從M18找尋PO..."} | |||
| </Typography> | |||
| </Backdrop> | |||
| </> | |||
| </> | |||
| @@ -57,6 +57,15 @@ const asyncItemCodeField = ( | |||
| placeholder: "e.g. FA0591", | |||
| }); | |||
| const pasteItemCodeField = (): ReportField => ({ | |||
| label: "貼上貨品編號 Paste Item Codes", | |||
| name: "itemCodePaste", | |||
| type: "text", | |||
| required: false, | |||
| multiline: true, | |||
| minRows: 4, | |||
| }); | |||
| export const REPORTS: ReportDefinition[] = [ | |||
| //{ | |||
| // id: "rep-001", | |||
| @@ -109,6 +118,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| { label: "入倉日期:由 Last In Date Start", name: "lastInDateStart", type: "date", required: false }, | |||
| { label: "入倉日期:至 Last In Date End", name: "lastInDateEnd", type: "date", required: false }, | |||
| asyncItemCodeField(), | |||
| pasteItemCodeField(), | |||
| { | |||
| label: "樓層 Store ID", | |||
| name: "storeId", | |||
| @@ -148,6 +158,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| { label: "出貨日期:至 Last Out Date End", name: "lastOutDateEnd", type: "date", required: false }, | |||
| { label: "年份 Year", name: "year", type: "text", required: false, placeholder: "e.g. 2026" }, | |||
| asyncItemCodeField(), | |||
| pasteItemCodeField(), | |||
| ] | |||
| }, | |||
| /* | |||
| @@ -302,6 +313,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| { label: "庫存日期:由 Last In Date Start", name: "lastInDateStart", type: "date", required: false }, | |||
| { label: "庫存日期:至 Last In Date End", name: "lastInDateEnd", type: "date", required: false }, | |||
| asyncItemCodeField(), | |||
| pasteItemCodeField(), | |||
| ] | |||
| }, | |||
| /* Hidden for now: 庫存流水帳報告 (rep-020) | |||
| @@ -344,14 +356,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| fields: [ | |||
| { label: "庫存日期: Stock Date", name: "stockDate", type: "date", required: true }, | |||
| asyncItemCodeField(), | |||
| { | |||
| label: "貼上貨品編號 Paste Item Codes", | |||
| name: "itemCodePaste", | |||
| type: "text", | |||
| required: false, | |||
| multiline: true, | |||
| minRows: 4, | |||
| }, | |||
| pasteItemCodeField(), | |||
| ] | |||
| }, | |||
| @@ -365,6 +370,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| { label: "收貨日期:由 Receipt Date Start", name: "receiptDateStart", type: "date", required: false }, | |||
| { label: "收貨日期:至 Receipt Date End", name: "receiptDateEnd", type: "date", required: false }, | |||
| asyncItemCodeField(), | |||
| pasteItemCodeField(), | |||
| ], | |||
| }, | |||
| @@ -417,6 +423,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| { label: "出倉日期:由 Last Out Date Start", name: "lastOutDateStart", type: "date", required: false }, | |||
| { label: "出倉日期:至 Last Out Date End", name: "lastOutDateEnd", type: "date", required: false }, | |||
| asyncItemCodeField(), | |||
| pasteItemCodeField(), | |||
| { label: "提料人 Handler", name: "handler", type: "select", required: false, | |||
| multiple: true, | |||
| dynamicOptions: true, | |||
| @@ -449,6 +456,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| dynamicOptionsEndpoint: `${NEXT_PUBLIC_API_URL}/report/stock-item-code-prefixes`, | |||
| dynamicOptionsParam: "stockCategory", | |||
| options: [] }, | |||
| pasteItemCodeField(), | |||
| ] | |||
| }, | |||
| @@ -475,6 +483,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| dynamicOptionsEndpoint: `${NEXT_PUBLIC_API_URL}/report/semi-fg-item-codes`, | |||
| dynamicOptionsParam: "stockCategory", | |||
| options: [] }, | |||
| pasteItemCodeField(), | |||
| ] | |||
| }, | |||
| { | |||
| @@ -486,6 +495,7 @@ export const REPORTS: ReportDefinition[] = [ | |||
| { label: "同步日期:由 Sync Date Start", name: "syncDateStart", type: "date", required: false }, | |||
| { label: "同步日期:至 Sync Date End", name: "syncDateEnd", type: "date", required: false }, | |||
| asyncItemCodeField("成品貨號 Finished Item Code", "finishedItemCode"), | |||
| pasteItemCodeField(), | |||
| { | |||
| label: "同步狀態 Sync Status", | |||
| name: "syncStatus", | |||
| @@ -52,6 +52,7 @@ | |||
| "lastInDateStart": "Last In Date Start", | |||
| "lastInDateEnd": "Last In Date End", | |||
| "itemCode": "Item Code", | |||
| "itemCodePaste": "Paste Item Codes", | |||
| "storeId": "Floor", | |||
| "warehouse": "Warehouse", | |||
| "area": "Area", | |||
| @@ -68,6 +69,9 @@ | |||
| "PP": "PP", | |||
| "PF": "PF" | |||
| } | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| } | |||
| }, | |||
| "rep-008": { | |||
| @@ -76,7 +80,11 @@ | |||
| "lastOutDateStart": "Last Out Date Start", | |||
| "lastOutDateEnd": "Last Out Date End", | |||
| "year": "Year", | |||
| "itemCode": "Item Code" | |||
| "itemCode": "Item Code", | |||
| "itemCodePaste": "Paste Item Codes" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| } | |||
| }, | |||
| "rep-012": { | |||
| @@ -115,7 +123,11 @@ | |||
| "fields": { | |||
| "lastInDateStart": "Stock Date Start", | |||
| "lastInDateEnd": "Stock Date End", | |||
| "itemCode": "Item Code" | |||
| "itemCode": "Item Code", | |||
| "itemCodePaste": "Paste Item Codes" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| } | |||
| }, | |||
| "rep-007": { | |||
| @@ -134,7 +146,11 @@ | |||
| "fields": { | |||
| "receiptDateStart": "Receipt Date Start", | |||
| "receiptDateEnd": "Receipt Date End", | |||
| "itemCode": "Item Code" | |||
| "itemCode": "Item Code", | |||
| "itemCodePaste": "Paste Item Codes" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| } | |||
| }, | |||
| "rep-009": { | |||
| @@ -173,7 +189,11 @@ | |||
| "lastOutDateStart": "Last Out Date Start", | |||
| "lastOutDateEnd": "Last Out Date End", | |||
| "itemCode": "Item Code", | |||
| "itemCodePaste": "Paste Item Codes", | |||
| "handler": "Handler" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| } | |||
| }, | |||
| "rep-006": { | |||
| @@ -183,7 +203,11 @@ | |||
| "lastOutDateEnd": "Consumption Date End", | |||
| "year": "Year", | |||
| "stockCategory": "Category", | |||
| "itemCode": "Item Code" | |||
| "itemCode": "Item Code", | |||
| "itemCodePaste": "Paste Item Codes" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| } | |||
| }, | |||
| "rep-005": { | |||
| @@ -193,7 +217,11 @@ | |||
| "lastOutDateEnd": "Production Complete Date End", | |||
| "year": "Year", | |||
| "stockCategory": "Category", | |||
| "itemCode": "Item Code" | |||
| "itemCode": "Item Code", | |||
| "itemCodePaste": "Paste Item Codes" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| } | |||
| }, | |||
| "rep-015": { | |||
| @@ -202,8 +230,12 @@ | |||
| "syncDateStart": "Sync Date Start", | |||
| "syncDateEnd": "Sync Date End", | |||
| "finishedItemCode": "Finished Item Code", | |||
| "itemCodePaste": "Paste Item Codes", | |||
| "syncStatus": "Sync Status" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines" | |||
| }, | |||
| "options": { | |||
| "syncStatus": { | |||
| "all": "All", | |||
| @@ -52,6 +52,7 @@ | |||
| "lastInDateStart": "入倉日期:由", | |||
| "lastInDateEnd": "入倉日期:至", | |||
| "itemCode": "貨品編號", | |||
| "itemCodePaste": "貼上貨品編號", | |||
| "storeId": "樓層", | |||
| "warehouse": "倉庫", | |||
| "area": "區域", | |||
| @@ -68,6 +69,9 @@ | |||
| "PP": "PP", | |||
| "PF": "PF" | |||
| } | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| } | |||
| }, | |||
| "rep-008": { | |||
| @@ -76,7 +80,11 @@ | |||
| "lastOutDateStart": "出貨日期:由", | |||
| "lastOutDateEnd": "出貨日期:至", | |||
| "year": "年份", | |||
| "itemCode": "貨品編號" | |||
| "itemCode": "貨品編號", | |||
| "itemCodePaste": "貼上貨品編號" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| } | |||
| }, | |||
| "rep-012": { | |||
| @@ -115,7 +123,11 @@ | |||
| "fields": { | |||
| "lastInDateStart": "庫存日期:由", | |||
| "lastInDateEnd": "庫存日期:至", | |||
| "itemCode": "貨品編號" | |||
| "itemCode": "貨品編號", | |||
| "itemCodePaste": "貼上貨品編號" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| } | |||
| }, | |||
| "rep-007": { | |||
| @@ -134,7 +146,11 @@ | |||
| "fields": { | |||
| "receiptDateStart": "收貨日期:由", | |||
| "receiptDateEnd": "收貨日期:至", | |||
| "itemCode": "貨品編號" | |||
| "itemCode": "貨品編號", | |||
| "itemCodePaste": "貼上貨品編號" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| } | |||
| }, | |||
| "rep-009": { | |||
| @@ -173,7 +189,11 @@ | |||
| "lastOutDateStart": "出倉日期:由", | |||
| "lastOutDateEnd": "出倉日期:至", | |||
| "itemCode": "貨品編號", | |||
| "itemCodePaste": "貼上貨品編號", | |||
| "handler": "提料人" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| } | |||
| }, | |||
| "rep-006": { | |||
| @@ -183,7 +203,11 @@ | |||
| "lastOutDateEnd": "材料消耗日期:至", | |||
| "year": "年份", | |||
| "stockCategory": "類別", | |||
| "itemCode": "貨品編號" | |||
| "itemCode": "貨品編號", | |||
| "itemCodePaste": "貼上貨品編號" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| } | |||
| }, | |||
| "rep-005": { | |||
| @@ -193,7 +217,11 @@ | |||
| "lastOutDateEnd": "完成生產日期:至", | |||
| "year": "年份", | |||
| "stockCategory": "類別", | |||
| "itemCode": "貨品編號" | |||
| "itemCode": "貨品編號", | |||
| "itemCodePaste": "貼上貨品編號" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| } | |||
| }, | |||
| "rep-015": { | |||
| @@ -202,8 +230,12 @@ | |||
| "syncDateStart": "同步日期:由", | |||
| "syncDateEnd": "同步日期:至", | |||
| "finishedItemCode": "成品貨號", | |||
| "itemCodePaste": "貼上貨品編號", | |||
| "syncStatus": "同步狀態" | |||
| }, | |||
| "fieldHints": { | |||
| "itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔" | |||
| }, | |||
| "options": { | |||
| "syncStatus": { | |||
| "all": "全部", | |||