Просмотр исходного кода

added paste item code to some reports

production
PC-20260115JRSN\Administrator 2 дней назад
Родитель
Сommit
da1a0889c5
10 измененных файлов: 229 добавлений и 87 удалений
  1. +6
    -6
      src/app/(main)/report/SemiFGProductionAnalysisReport.tsx
  2. +8
    -4
      src/app/(main)/report/bomShopSyncReportApi.ts
  3. +9
    -5
      src/app/(main)/report/grnReportApi.ts
  4. +27
    -10
      src/app/(main)/report/page.tsx
  5. +30
    -15
      src/app/(main)/report/parseItemCodeTokens.ts
  6. +15
    -8
      src/app/(main)/report/semiFGProductionAnalysisApi.ts
  7. +42
    -21
      src/components/PoSearch/PoSearch.tsx
  8. +18
    -8
      src/config/reportConfig.ts
  9. +37
    -5
      src/i18n/en/report.json
  10. +37
    -5
      src/i18n/zh/report.json

+ 6
- 6
src/app/(main)/report/SemiFGProductionAnalysisReport.tsx Просмотреть файл

@@ -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,


+ 8
- 4
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<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)


+ 9
- 5
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<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)


+ 27
- 10
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',


+ 30
- 15
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<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;
}

+ 15
- 8
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<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');


+ 42
- 21
src/components/PoSearch/PoSearch.tsx Просмотреть файл

@@ -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>
</>
</>


+ 18
- 8
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",


+ 37
- 5
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",


+ 37
- 5
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": "全部",


Загрузка…
Отмена
Сохранить