| @@ -17,7 +17,7 @@ import OutboundOutlinedIcon from "@mui/icons-material/OutboundOutlined"; | |||||
| import BarChartOutlinedIcon from "@mui/icons-material/BarChartOutlined"; | import BarChartOutlinedIcon from "@mui/icons-material/BarChartOutlined"; | ||||
| import PieChartOutlineOutlinedIcon from "@mui/icons-material/PieChartOutlineOutlined"; | import PieChartOutlineOutlinedIcon from "@mui/icons-material/PieChartOutlineOutlined"; | ||||
| import type { SvgIconComponent } from "@mui/icons-material"; | import type { SvgIconComponent } from "@mui/icons-material"; | ||||
| import { REPORTS } from "@/config/reportConfig"; | |||||
| import { DEMO_UNAVAILABLE_REPORT_IDS, REPORTS } from "@/config/reportConfig"; | |||||
| import { REPORT_CATEGORIES, type ReportCategoryConfig } from "./reportCategories"; | import { REPORT_CATEGORIES, type ReportCategoryConfig } from "./reportCategories"; | ||||
| import { useReportLabels } from "./reportI18n"; | import { useReportLabels } from "./reportI18n"; | ||||
| @@ -52,12 +52,16 @@ function ReportCard({ | |||||
| title, | title, | ||||
| accent, | accent, | ||||
| selected, | selected, | ||||
| unavailable, | |||||
| unavailableLabel, | |||||
| onClick, | onClick, | ||||
| }: { | }: { | ||||
| reportId: string; | reportId: string; | ||||
| title: string; | title: string; | ||||
| accent: string; | accent: string; | ||||
| selected: boolean; | selected: boolean; | ||||
| unavailable: boolean; | |||||
| unavailableLabel: string; | |||||
| onClick: () => void; | onClick: () => void; | ||||
| }) { | }) { | ||||
| const Icon = REPORT_ICON_MAP[reportId] ?? Inventory2OutlinedIcon; | const Icon = REPORT_ICON_MAP[reportId] ?? Inventory2OutlinedIcon; | ||||
| @@ -66,7 +70,9 @@ function ReportCard({ | |||||
| <Box | <Box | ||||
| component="button" | component="button" | ||||
| type="button" | type="button" | ||||
| onClick={onClick} | |||||
| disabled={unavailable} | |||||
| onClick={unavailable ? undefined : onClick} | |||||
| aria-disabled={unavailable} | |||||
| sx={{ | sx={{ | ||||
| display: "flex", | display: "flex", | ||||
| alignItems: "center", | alignItems: "center", | ||||
| @@ -74,17 +80,22 @@ function ReportCard({ | |||||
| width: "100%", | width: "100%", | ||||
| p: 1.5, | p: 1.5, | ||||
| textAlign: "left", | textAlign: "left", | ||||
| cursor: "pointer", | |||||
| cursor: unavailable ? "not-allowed" : "pointer", | |||||
| border: "1px solid", | border: "1px solid", | ||||
| borderColor: selected ? accent : "divider", | |||||
| borderColor: unavailable ? "action.disabledBackground" : selected ? accent : "divider", | |||||
| borderRadius: 1.5, | borderRadius: 1.5, | ||||
| bgcolor: "background.paper", | |||||
| boxShadow: selected ? 2 : "0 1px 3px rgba(0,0,0,0.06)", | |||||
| bgcolor: unavailable ? "action.hover" : "background.paper", | |||||
| color: unavailable ? "text.disabled" : "text.primary", | |||||
| boxShadow: !unavailable && selected ? 2 : "0 1px 3px rgba(0,0,0,0.06)", | |||||
| opacity: unavailable ? 0.72 : 1, | |||||
| filter: unavailable ? "grayscale(1)" : "none", | |||||
| transition: "border-color 0.15s, box-shadow 0.15s", | transition: "border-color 0.15s, box-shadow 0.15s", | ||||
| "&:hover": { | |||||
| borderColor: accent, | |||||
| boxShadow: 2, | |||||
| }, | |||||
| "&:hover": unavailable | |||||
| ? {} | |||||
| : { | |||||
| borderColor: accent, | |||||
| boxShadow: 2, | |||||
| }, | |||||
| }} | }} | ||||
| > | > | ||||
| <Box | <Box | ||||
| @@ -95,16 +106,23 @@ function ReportCard({ | |||||
| width: 40, | width: 40, | ||||
| height: 40, | height: 40, | ||||
| borderRadius: 1, | borderRadius: 1, | ||||
| bgcolor: `${accent}18`, | |||||
| color: accent, | |||||
| bgcolor: unavailable ? "action.disabledBackground" : `${accent}18`, | |||||
| color: unavailable ? "text.disabled" : accent, | |||||
| flexShrink: 0, | flexShrink: 0, | ||||
| }} | }} | ||||
| > | > | ||||
| <Icon fontSize="small" /> | <Icon fontSize="small" /> | ||||
| </Box> | </Box> | ||||
| <Typography variant="body2" fontWeight={selected ? 600 : 400} sx={{ lineHeight: 1.35 }}> | |||||
| {title} | |||||
| </Typography> | |||||
| <Box sx={{ minWidth: 0 }}> | |||||
| <Typography variant="body2" fontWeight={selected && !unavailable ? 600 : 400} sx={{ lineHeight: 1.35 }}> | |||||
| {title} | |||||
| </Typography> | |||||
| {unavailable && ( | |||||
| <Typography variant="caption" sx={{ display: "block", lineHeight: 1.3, color: "text.secondary" }}> | |||||
| {unavailableLabel} | |||||
| </Typography> | |||||
| )} | |||||
| </Box> | |||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -118,7 +136,7 @@ function CategoryColumn({ | |||||
| selectedReportId: string; | selectedReportId: string; | ||||
| onSelectReport: (reportId: string) => void; | onSelectReport: (reportId: string) => void; | ||||
| }) { | }) { | ||||
| const { reportTitle, categoryTitle } = useReportLabels(); | |||||
| const { t, reportTitle, categoryTitle } = useReportLabels(); | |||||
| const reports = category.reportIds | const reports = category.reportIds | ||||
| .map((id) => reportById[id]) | .map((id) => reportById[id]) | ||||
| .filter(Boolean); | .filter(Boolean); | ||||
| @@ -161,6 +179,8 @@ function CategoryColumn({ | |||||
| title={reportTitle(report)} | title={reportTitle(report)} | ||||
| accent={category.accent} | accent={category.accent} | ||||
| selected={selectedReportId === report.id} | selected={selectedReportId === report.id} | ||||
| unavailable={DEMO_UNAVAILABLE_REPORT_IDS.has(report.id)} | |||||
| unavailableLabel={t("unavailableInDemo")} | |||||
| onClick={() => onSelectReport(report.id)} | onClick={() => onSelectReport(report.id)} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -205,7 +205,7 @@ function toMaterialExcelRow( | |||||
| export async function generateBomShopSyncReportExcel( | export async function generateBomShopSyncReportExcel( | ||||
| criteria: Record<string, string>, | criteria: Record<string, string>, | ||||
| reportTitle: string = "M18 BOM Shop 同步記錄", | |||||
| reportTitle: string = "BOM Shop 同步記錄", | |||||
| t?: TFunction, | t?: TFunction, | ||||
| ): Promise<void> { | ): Promise<void> { | ||||
| const L = bomSyncLabels(t); | const L = bomSyncLabels(t); | ||||
| @@ -33,12 +33,6 @@ export interface GrnReportRow { | |||||
| lineAmount?: number; | lineAmount?: number; | ||||
| /** PO currency code (currency.code) */ | /** PO currency code (currency.code) */ | ||||
| currencyCode?: string; | currencyCode?: string; | ||||
| /** M18 AN document code from m18_goods_receipt_note_log.grn_code */ | |||||
| grnCode?: string; | |||||
| /** M18 record id (m18_record_id) */ | |||||
| grnId?: number | string; | |||||
| /** From purchase_order.m18CreatedUId; e.g. "2569 (legato)" */ | |||||
| poM18CreatorDisplay?: string; | |||||
| [key: string]: unknown; | [key: string]: unknown; | ||||
| } | } | ||||
| @@ -54,7 +48,6 @@ export interface ListedPoAmounts { | |||||
| poCode?: string; | poCode?: string; | ||||
| currencyCode?: string; | currencyCode?: string; | ||||
| totalAmount?: number; | totalAmount?: number; | ||||
| grnCodes?: string; | |||||
| }[]; | }[]; | ||||
| } | } | ||||
| @@ -148,13 +141,9 @@ function grnLabels(t?: TFunction) { | |||||
| unitPrice: tx(t, "excel.grn.unitPrice", "單價"), | unitPrice: tx(t, "excel.grn.unitPrice", "單價"), | ||||
| currency: tx(t, "excel.grn.currency", "貨幣"), | currency: tx(t, "excel.grn.currency", "貨幣"), | ||||
| amount: tx(t, "excel.grn.amount", "金額"), | amount: tx(t, "excel.grn.amount", "金額"), | ||||
| grnCode: tx(t, "excel.grn.grnCode", "M18 入倉單號"), | |||||
| grnId: tx(t, "excel.grn.grnId", "M18 記錄編號"), | |||||
| poCreator: tx(t, "excel.grn.poCreator", "PO建立者(M18)"), | |||||
| note: tx(t, "excel.grn.note", "備註"), | note: tx(t, "excel.grn.note", "備註"), | ||||
| category: tx(t, "excel.grn.category", "類別"), | category: tx(t, "excel.grn.category", "類別"), | ||||
| totalAmount: tx(t, "excel.grn.totalAmount", "金額"), | totalAmount: tx(t, "excel.grn.totalAmount", "金額"), | ||||
| grnCodes: tx(t, "excel.grn.grnCodes", "M18 入倉單號"), | |||||
| noCompletedPo: tx(t, "excel.grn.noCompletedPo", "(篩選範圍內無已完成之 PO 行)"), | noCompletedPo: tx(t, "excel.grn.noCompletedPo", "(篩選範圍內無已完成之 PO 行)"), | ||||
| categoryCurrencyTotal: tx(t, "excel.grn.categoryCurrencyTotal", "貨幣小計"), | categoryCurrencyTotal: tx(t, "excel.grn.categoryCurrencyTotal", "貨幣小計"), | ||||
| categoryPo: tx(t, "excel.grn.categoryPo", "訂單"), | categoryPo: tx(t, "excel.grn.categoryPo", "訂單"), | ||||
| @@ -187,9 +176,6 @@ function toExcelRow( | |||||
| base[L.currency] = r.currencyCode ?? ""; | base[L.currency] = r.currencyCode ?? ""; | ||||
| base[L.amount] = moneyCellValue(r.lineAmount); | base[L.amount] = moneyCellValue(r.lineAmount); | ||||
| } | } | ||||
| base[L.grnCode] = r.grnCode ?? ""; | |||||
| base[L.grnId] = r.grnId ?? ""; | |||||
| base[L.poCreator] = r.poM18CreatorDisplay ?? ""; | |||||
| return base; | return base; | ||||
| } | } | ||||
| @@ -213,7 +199,6 @@ function buildListedPoAmountSheetRows( | |||||
| [L.poNo]: "", | [L.poNo]: "", | ||||
| [L.currency]: c.currencyCode ?? "", | [L.currency]: c.currencyCode ?? "", | ||||
| [L.totalAmount]: moneyCellValue(c.totalAmount), | [L.totalAmount]: moneyCellValue(c.totalAmount), | ||||
| [L.grnCodes]: "", | |||||
| }); | }); | ||||
| } | } | ||||
| for (const p of listed.byPurchaseOrder) { | for (const p of listed.byPurchaseOrder) { | ||||
| @@ -223,7 +208,6 @@ function buildListedPoAmountSheetRows( | |||||
| [L.poNo]: p.poCode ?? "", | [L.poNo]: p.poCode ?? "", | ||||
| [L.currency]: p.currencyCode ?? "", | [L.currency]: p.currencyCode ?? "", | ||||
| [L.totalAmount]: moneyCellValue(p.totalAmount), | [L.totalAmount]: moneyCellValue(p.totalAmount), | ||||
| [L.grnCodes]: p.grnCodes ?? "", | |||||
| }); | }); | ||||
| } | } | ||||
| return out; | return out; | ||||
| @@ -25,7 +25,7 @@ import { | |||||
| } 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 InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; | ||||
| import { REPORTS } from '@/config/reportConfig'; | |||||
| import { DEMO_UNAVAILABLE_REPORT_IDS, REPORTS } from '@/config/reportConfig'; | |||||
| import { mergePastedItemCodes, buildItemCodePasteRequestBody, parseItemCodeTokens } from './parseItemCodeTokens'; | import { mergePastedItemCodes, buildItemCodePasteRequestBody, parseItemCodeTokens } from './parseItemCodeTokens'; | ||||
| 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'; | ||||
| @@ -152,6 +152,7 @@ export default function ReportPage() { | |||||
| [selectedReportId]); | [selectedReportId]); | ||||
| const handleSelectReport = (reportId: string) => { | const handleSelectReport = (reportId: string) => { | ||||
| if (DEMO_UNAVAILABLE_REPORT_IDS.has(reportId)) return; | |||||
| if (reportId === selectedReportId) return; | if (reportId === selectedReportId) return; | ||||
| setSelectedReportId(reportId); | setSelectedReportId(reportId); | ||||
| setFieldErrors({}); | setFieldErrors({}); | ||||
| @@ -452,6 +453,7 @@ export default function ReportPage() { | |||||
| const executeExcelReport = async () => { | const executeExcelReport = async () => { | ||||
| if (!currentReport) return; | if (!currentReport) return; | ||||
| if (DEMO_UNAVAILABLE_REPORT_IDS.has(currentReport.id)) return; | |||||
| if (excelInFlightRef.current) return; | if (excelInFlightRef.current) return; | ||||
| excelInFlightRef.current = true; | excelInFlightRef.current = true; | ||||
| setLoading(true); | setLoading(true); | ||||
| @@ -33,6 +33,6 @@ export const REPORT_CATEGORIES: ReportCategoryConfig[] = [ | |||||
| headerBg: "#f5d4a8", | headerBg: "#f5d4a8", | ||||
| bodyBg: "#fdf6ec", | bodyBg: "#fdf6ec", | ||||
| accent: "#e65100", | accent: "#e65100", | ||||
| reportIds: ["rep-006", "rep-005"], | |||||
| reportIds: ["rep-006", "rep-005", "rep-015"], | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -37,8 +37,6 @@ import { | |||||
| type LaserBag2AutoSendReport, | type LaserBag2AutoSendReport, | ||||
| type LaserLastReceiveSuccess, | type LaserLastReceiveSuccess, | ||||
| } from "@/app/api/laserPrint/actions"; | } from "@/app/api/laserPrint/actions"; | ||||
| import * as XLSX from "xlsx"; | |||||
| interface TabPanelProps { | interface TabPanelProps { | ||||
| children?: React.ReactNode; | children?: React.ReactNode; | ||||
| index: number; | index: number; | ||||
| @@ -175,49 +173,6 @@ export default function TestingPage() { | |||||
| }; | }; | ||||
| }, [tabValue]); | }, [tabValue]); | ||||
| const handleDownloadGrnPreviewXlsx = async () => { | |||||
| try { | |||||
| const response = await clientAuthFetch( | |||||
| `${NEXT_PUBLIC_API_URL}/report/grn-preview-m18?receiptDate=${encodeURIComponent( | |||||
| grnPreviewReceiptDate, | |||||
| )}`, | |||||
| { method: "GET" }, | |||||
| ); | |||||
| if (response.status === 401 || response.status === 403) return; | |||||
| if (!response.ok) throw new Error(`Download failed: ${response.status}`); | |||||
| const data = await response.json(); | |||||
| const rows = Array.isArray(data?.rows) ? data.rows : []; | |||||
| const ws = XLSX.utils.json_to_sheet(rows); | |||||
| const wb = XLSX.utils.book_new(); | |||||
| XLSX.utils.book_append_sheet(wb, ws, "GRN Preview"); | |||||
| const xlsxArrayBuffer = XLSX.write(wb, { | |||||
| bookType: "xlsx", | |||||
| type: "array", | |||||
| }); | |||||
| const blob = new Blob([xlsxArrayBuffer], { | |||||
| type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", | |||||
| }); | |||||
| const url = window.URL.createObjectURL(blob); | |||||
| const link = document.createElement("a"); | |||||
| link.href = url; | |||||
| link.setAttribute( | |||||
| "download", | |||||
| `grn-preview-m18-${grnPreviewReceiptDate}.xlsx`, | |||||
| ); | |||||
| document.body.appendChild(link); | |||||
| link.click(); | |||||
| link.remove(); | |||||
| window.URL.revokeObjectURL(url); | |||||
| } catch (e) { | |||||
| console.error("GRN Preview XLSX Download Error:", e); | |||||
| alert("GRN Preview XLSX download failed. Check console/network."); | |||||
| } | |||||
| }; | |||||
| const downloadBlob = (blob: Blob, filename: string) => { | const downloadBlob = (blob: Blob, filename: string) => { | ||||
| const url = window.URL.createObjectURL(blob); | const url = window.URL.createObjectURL(blob); | ||||
| const link = document.createElement("a"); | const link = document.createElement("a"); | ||||
| @@ -524,33 +479,35 @@ export default function TestingPage() { | |||||
| </Tabs> | </Tabs> | ||||
| <TabPanel value={tabValue} index={0}> | <TabPanel value={tabValue} index={0}> | ||||
| <Section title="1. GRN Preview (M18)"> | |||||
| <Stack | |||||
| direction="row" | |||||
| spacing={2} | |||||
| sx={{ mb: 2, alignItems: "center" }} | |||||
| > | |||||
| <TextField | |||||
| size="small" | |||||
| label="Receipt Date" | |||||
| type="date" | |||||
| value={grnPreviewReceiptDate} | |||||
| onChange={(e) => setGrnPreviewReceiptDate(e.target.value)} | |||||
| InputLabelProps={{ shrink: true }} | |||||
| /> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="success" | |||||
| size="medium" | |||||
| startIcon={<FileDownload />} | |||||
| onClick={handleDownloadGrnPreviewXlsx} | |||||
| <Section title="1. GRN Preview"> | |||||
| <Box sx={{ opacity: 0.55, filter: "grayscale(1)", pointerEvents: "none" }}> | |||||
| <Stack | |||||
| direction="row" | |||||
| spacing={2} | |||||
| sx={{ mb: 2, alignItems: "center" }} | |||||
| > | > | ||||
| Download GRN Preview XLSX | |||||
| </Button> | |||||
| </Stack> | |||||
| <Typography variant="body2" color="textSecondary"> | |||||
| Backend endpoint:{" "} | |||||
| <code>/report/grn-preview-m18?receiptDate=YYYY-MM-DD</code> | |||||
| <TextField | |||||
| size="small" | |||||
| label="Receipt Date" | |||||
| type="date" | |||||
| value={grnPreviewReceiptDate} | |||||
| onChange={(e) => setGrnPreviewReceiptDate(e.target.value)} | |||||
| InputLabelProps={{ shrink: true }} | |||||
| disabled | |||||
| /> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="success" | |||||
| size="medium" | |||||
| startIcon={<FileDownload />} | |||||
| disabled | |||||
| > | |||||
| Download GRN Preview XLSX | |||||
| </Button> | |||||
| </Stack> | |||||
| </Box> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| Unavailable in demo mode | |||||
| </Typography> | </Typography> | ||||
| </Section> | </Section> | ||||
| </TabPanel> | </TabPanel> | ||||
| @@ -33,6 +33,9 @@ export interface ReportField { | |||||
| export type ReportResponseType = 'pdf' | 'excel'; | export type ReportResponseType = 'pdf' | 'excel'; | ||||
| /** Live-system reports. The demo page shows them greyed out. */ | |||||
| export const DEMO_UNAVAILABLE_REPORT_IDS = new Set(["rep-015"]); | |||||
| export interface ReportDefinition { | export interface ReportDefinition { | ||||
| id: string; | id: string; | ||||
| title: string; | title: string; | ||||
| @@ -459,7 +462,7 @@ export const REPORTS: ReportDefinition[] = [ | |||||
| }, | }, | ||||
| { | { | ||||
| id: "rep-015", | id: "rep-015", | ||||
| title: "M18 BOM Shop 同步記錄", | |||||
| title: "BOM Shop 同步記錄", | |||||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history`, | apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history`, | ||||
| responseType: "excel", | responseType: "excel", | ||||
| fields: [ | fields: [ | ||||
| @@ -15,6 +15,7 @@ | |||||
| "noDataFoundTitle": "No Data Found / 查無資料", | "noDataFoundTitle": "No Data Found / 查無資料", | ||||
| "noDataFoundHint": "No inventory records match your search. Please try adjusting the filters.", | "noDataFoundHint": "No inventory records match your search. Please try adjusting the filters.", | ||||
| "ok": "OK", | "ok": "OK", | ||||
| "unavailableInDemo": "Unavailable in demo mode", | |||||
| "missingRequired": "Missing required fields:\n- {{fields}}", | "missingRequired": "Missing required fields:\n- {{fields}}", | ||||
| "requiredField": "This is a required field", | "requiredField": "This is a required field", | ||||
| "dateNotBeforeToday": "Date cannot be before today:\n- {{fields}}", | "dateNotBeforeToday": "Date cannot be before today:\n- {{fields}}", | ||||
| @@ -231,7 +232,7 @@ | |||||
| } | } | ||||
| }, | }, | ||||
| "rep-015": { | "rep-015": { | ||||
| "title": "M18 BOM Shop Sync History", | |||||
| "title": "BOM Shop Sync History", | |||||
| "fields": { | "fields": { | ||||
| "syncDateStart": "Sync Date Start", | "syncDateStart": "Sync Date Start", | ||||
| "syncDateEnd": "Sync Date End", | "syncDateEnd": "Sync Date End", | ||||
| @@ -332,13 +333,9 @@ | |||||
| "unitPrice": "Unit Price", | "unitPrice": "Unit Price", | ||||
| "currency": "Currency", | "currency": "Currency", | ||||
| "amount": "Amount", | "amount": "Amount", | ||||
| "grnCode": "GRN Code / M18 Receipt No.", | |||||
| "grnId": "GRN Id / M18 Record Id", | |||||
| "poCreator": "PO creator (M18)", | |||||
| "note": "Note", | "note": "Note", | ||||
| "category": "Category", | "category": "Category", | ||||
| "totalAmount": "Total Amount", | |||||
| "grnCodes": "GRN Code(s) / M18 Receipt No." | |||||
| "totalAmount": "Total Amount" | |||||
| }, | }, | ||||
| "bomSync": { | "bomSync": { | ||||
| "sheetSync": "BOM Sync Log", | "sheetSync": "BOM Sync Log", | ||||
| @@ -15,6 +15,7 @@ | |||||
| "noDataFoundTitle": "查無資料", | "noDataFoundTitle": "查無資料", | ||||
| "noDataFoundHint": "找不到符合您搜尋條件的庫存資料,請嘗試調整篩選條件。", | "noDataFoundHint": "找不到符合您搜尋條件的庫存資料,請嘗試調整篩選條件。", | ||||
| "ok": "確定", | "ok": "確定", | ||||
| "unavailableInDemo": "示範模式無法使用", | |||||
| "missingRequired": "缺少必填條件:\n- {{fields}}", | "missingRequired": "缺少必填條件:\n- {{fields}}", | ||||
| "requiredField": "此為必填欄位", | "requiredField": "此為必填欄位", | ||||
| "dateNotBeforeToday": "日期不可早於今天:\n- {{fields}}", | "dateNotBeforeToday": "日期不可早於今天:\n- {{fields}}", | ||||
| @@ -231,7 +232,7 @@ | |||||
| } | } | ||||
| }, | }, | ||||
| "rep-015": { | "rep-015": { | ||||
| "title": "M18 BOM Shop 同步記錄", | |||||
| "title": "BOM Shop 同步記錄", | |||||
| "fields": { | "fields": { | ||||
| "syncDateStart": "同步日期:由", | "syncDateStart": "同步日期:由", | ||||
| "syncDateEnd": "同步日期:至", | "syncDateEnd": "同步日期:至", | ||||
| @@ -332,13 +333,9 @@ | |||||
| "unitPrice": "單價", | "unitPrice": "單價", | ||||
| "currency": "貨幣", | "currency": "貨幣", | ||||
| "amount": "金額", | "amount": "金額", | ||||
| "grnCode": "M18 入倉單號", | |||||
| "grnId": "M18 記錄編號", | |||||
| "poCreator": "PO建立者(M18)", | |||||
| "note": "備註", | "note": "備註", | ||||
| "category": "類別", | "category": "類別", | ||||
| "totalAmount": "金額", | |||||
| "grnCodes": "M18 入倉單號" | |||||
| "totalAmount": "金額" | |||||
| }, | }, | ||||
| "bomSync": { | "bomSync": { | ||||
| "sheetSync": "BOM同步記錄", | "sheetSync": "BOM同步記錄", | ||||