diff --git a/src/app/(main)/report/SemiFGProductionAnalysisReport.tsx b/src/app/(main)/report/SemiFGProductionAnalysisReport.tsx index 5592a93c..f676ba66 100644 --- a/src/app/(main)/report/SemiFGProductionAnalysisReport.tsx +++ b/src/app/(main)/report/SemiFGProductionAnalysisReport.tsx @@ -26,6 +26,7 @@ import { generateSemiFGProductionAnalysisReportExcel, ItemCodeWithCategory, } from './semiFGProductionAnalysisApi'; +import { parseItemCodeTokens } from './parseItemCodeTokens'; interface SemiFGProductionAnalysisReportProps { criteria: Record; @@ -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, diff --git a/src/app/(main)/report/bomShopSyncReportApi.ts b/src/app/(main)/report/bomShopSyncReportApi.ts index 08b31c48..d3eee465 100644 --- a/src/app/(main)/report/bomShopSyncReportApi.ts +++ b/src/app/(main)/report/bomShopSyncReportApi.ts @@ -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, ): Promise { - 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) diff --git a/src/app/(main)/report/grnReportApi.ts b/src/app/(main)/report/grnReportApi.ts index a8972cec..2e2f001f 100644 --- a/src/app/(main)/report/grnReportApi.ts +++ b/src/app/(main)/report/grnReportApi.ts @@ -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 ): 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) diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx index dce43d73..89f442e8 100644 --- a/src/app/(main)/report/page.tsx +++ b/src/app/(main)/report/page.tsx @@ -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', diff --git a/src/app/(main)/report/parseItemCodeTokens.ts b/src/app/(main)/report/parseItemCodeTokens.ts index 148a08e2..fcddfe79 100644 --- a/src/app/(main)/report/parseItemCodeTokens.ts +++ b/src/app/(main)/report/parseItemCodeTokens.ts @@ -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): Record { +export function resolveItemCodeFieldName(criteria: Record): 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, + codeField = resolveItemCodeFieldName(criteria), +): Record { const next: Record = { ...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): { - stockDate?: string; - itemCodes?: string[]; -} { +/** POST body so pasted codes are not limited by URL length. */ +export function buildItemCodePasteRequestBody( + criteria: Record, + codeField = resolveItemCodeFieldName(criteria), +): Record { 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 = {}; + 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; } diff --git a/src/app/(main)/report/semiFGProductionAnalysisApi.ts b/src/app/(main)/report/semiFGProductionAnalysisApi.ts index b4e8b69c..e77636e5 100644 --- a/src/app/(main)/report/semiFGProductionAnalysisApi.ts +++ b/src/app/(main)/report/semiFGProductionAnalysisApi.ts @@ -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, reportTitle: string = '成品/半成品生產分析報告' ): Promise => { - 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, reportTitle: string = '成品/半成品生產分析報告' ): Promise => { - 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'); diff --git a/src/components/PoSearch/PoSearch.tsx b/src/components/PoSearch/PoSearch.tsx index 45294d56..5d28527e 100644 --- a/src/components/PoSearch/PoSearch.tsx +++ b/src/components/PoSearch/PoSearch.tsx @@ -305,34 +305,45 @@ const PoSearch: React.FC = ({ 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 = ({ } 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 = ({ 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 = ({ sx={{ color: "#fff", zIndex: (theme) => theme.zIndex.modal + 1, flexDirection: "column", gap: 1 }} > - 正在從M18找尋PO... + + {autoSyncStatus || "正在從M18找尋PO..."} + diff --git a/src/config/reportConfig.ts b/src/config/reportConfig.ts index f34bcd93..f60a290a 100644 --- a/src/config/reportConfig.ts +++ b/src/config/reportConfig.ts @@ -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", diff --git a/src/i18n/en/report.json b/src/i18n/en/report.json index eb761d48..a9558ab7 100644 --- a/src/i18n/en/report.json +++ b/src/i18n/en/report.json @@ -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", diff --git a/src/i18n/zh/report.json b/src/i18n/zh/report.json index c0de47f6..86517558 100644 --- a/src/i18n/zh/report.json +++ b/src/i18n/zh/report.json @@ -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": "全部",