Sfoglia il codice sorgente

change search

undefined
Harry Groves 1 settimana fa
parent
commit
1007e4aa55
12 ha cambiato i file con 44 aggiunte e 19 eliminazioni
  1. +2
    -1
      src/app/(main)/report/itemCodeSearchApi.ts
  2. +4
    -4
      src/app/(main)/report/page.tsx
  3. +2
    -2
      src/app/api/inventory/actions.ts
  4. +1
    -1
      src/app/api/inventory/index.ts
  5. +7
    -3
      src/app/utils/clientAuthFetch.ts
  6. +1
    -1
      src/components/InventorySearch/InventoryLotLineTable.tsx
  7. +16
    -6
      src/components/InventorySearch/InventorySearch.tsx
  8. +7
    -0
      src/components/InventorySearch/InventoryTable.tsx
  9. +1
    -1
      src/config/reportConfig.ts
  10. +1
    -0
      src/i18n/en/inventory.json
  11. +1
    -0
      src/i18n/zh/inventory.json
  12. +1
    -0
      src/lib/featureUsageLog.ts

+ 2
- 1
src/app/(main)/report/itemCodeSearchApi.ts Vedi File

@@ -58,9 +58,10 @@ const fetchItemPage = async (
}; };


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 81 | v1.0.0 | 2026-09-10
* FP-MTMS Version Checklist | Functions Ref. No. 81 | v1.0.1 | 2026-09-11
* Typeahead lookup for report item-code multi-select. * Typeahead lookup for report item-code multi-select.
* Uses the existing paged item API so we never load the full catalog. * Uses the existing paged item API so we never load the full catalog.
* Hits are unique by item code — multi-UoM inventory buckets are expanded only in the downloaded report.
*/ */
export const searchItemCodes = async ( export const searchItemCodes = async (
query: string, query: string,


+ 4
- 4
src/app/(main)/report/page.tsx Vedi File

@@ -419,10 +419,10 @@ export default function ReportPage() {


const response = await clientAuthFetch(excelUrl, { const response = await clientAuthFetch(excelUrl, {
method: 'GET', method: 'GET',
headers: { Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
skipAuthRedirect: true,
headers: { Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/json' },
}); });


if (response.status === 401 || response.status === 403) return;
if (response.status === 204) { if (response.status === 204) {
setShowNoDataDialog(true); setShowNoDataDialog(true);
return; return;
@@ -482,10 +482,10 @@ export default function ReportPage() {


const response = await clientAuthFetch(url, { const response = await clientAuthFetch(url, {
method: 'GET', method: 'GET',
headers: { 'Accept': 'application/pdf' },
skipAuthRedirect: true,
headers: { Accept: 'application/pdf, application/json' },
}); });


if (response.status === 401 || response.status === 403) return;
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
console.error("Response error:", errorText); console.error("Response error:", errorText);


+ 2
- 2
src/app/api/inventory/actions.ts Vedi File

@@ -179,8 +179,8 @@ async function fetchInventoriesLatestImpl(data: SearchInventory) {
export const fetchInventories = cache(fetchInventoriesImpl); export const fetchInventories = cache(fetchInventoriesImpl);


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.4 | 2026-09-10
* Inventory search page: latest inventory row per item + stock UoM, with optional location filters.
* FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.5 | 2026-09-11
* Inventory search page: one row per item + stock UoM, with optional location filters.
*/ */
export const fetchInventoriesLatest = cache(fetchInventoriesLatestImpl); export const fetchInventoriesLatest = cache(fetchInventoriesLatestImpl);




+ 1
- 1
src/app/api/inventory/index.ts Vedi File

@@ -5,7 +5,7 @@ import { cache } from "react";
import "server-only"; import "server-only";


export interface InventoryResult { export interface InventoryResult {
id: number;
id: number | string;
itemId: number; itemId: number;
itemCode: string; itemCode: string;
itemName: string; itemName: string;


+ 7
- 3
src/app/utils/clientAuthFetch.ts Vedi File

@@ -9,7 +9,7 @@ import { LOGIN_SESSION_EXPIRED_HREF } from "@/app/utils/authToken";
*/ */
export async function clientAuthFetch( export async function clientAuthFetch(
input: RequestInfo | URL, input: RequestInfo | URL,
init?: RequestInit
init?: RequestInit & { skipAuthRedirect?: boolean }
): Promise<Response> { ): Promise<Response> {
const token = const token =
typeof window !== "undefined" ? localStorage.getItem("accessToken") : null; typeof window !== "undefined" ? localStorage.getItem("accessToken") : null;
@@ -18,9 +18,13 @@ export async function clientAuthFetch(
headers.set("Authorization", `Bearer ${token}`); headers.set("Authorization", `Bearer ${token}`);
} }


const response = await fetch(input, { ...init, headers });
const { skipAuthRedirect, ...fetchInit } = init ?? {};
const response = await fetch(input, { ...fetchInit, headers });


if (response.status === 401 || response.status === 403) {
if (
!skipAuthRedirect &&
(response.status === 401 || response.status === 403)
) {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
console.warn(`Auth error ${response.status} → redirecting to login`); console.warn(`Auth error ${response.status} → redirecting to login`);
window.location.href = LOGIN_SESSION_EXPIRED_HREF; window.location.href = LOGIN_SESSION_EXPIRED_HREF;


+ 1
- 1
src/components/InventorySearch/InventoryLotLineTable.tsx Vedi File

@@ -584,7 +584,7 @@ const prevAdjustmentModalOpenRef = useRef(false);
return <> return <>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap', mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap', mb: 2 }}>
<Typography variant="h6"> <Typography variant="h6">
{inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)})` : t("No items are selected yet.")}
{inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)}) | ${t("UoM ID")}: ${inventory.uomId ?? "-"} | ${inventory.uomUdfudesc || inventory.uomShortDesc || inventory.uomCode || "-"}` : t("No items are selected yet.")}
</Typography> </Typography>
{inventory && canStockAdjust && ( {inventory && canStockAdjust && (
<Chip <Chip


+ 16
- 6
src/components/InventorySearch/InventorySearch.tsx Vedi File

@@ -40,8 +40,11 @@ type SearchQuery = Partial<
InventoryResult, InventoryResult,
| "id" | "id"
| "qty" | "qty"
| "uomId"
| "uomCode" | "uomCode"
| "uomUdfudesc" | "uomUdfudesc"
| "stockUomId"
| "stockUomCode"
| "germPerSmallestUnit" | "germPerSmallestUnit"
| "qtyPerSmallestUnit" | "qtyPerSmallestUnit"
| "itemSmallestUnit" | "itemSmallestUnit"
@@ -71,7 +74,7 @@ const extractItemRecords = (res: unknown): ItemLookupRow[] => {
return []; return [];
}; };


/** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.4 | 2026-09-10 */
/** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.5 | 2026-09-11 */
const InventorySearch: React.FC<Props> = ({ const InventorySearch: React.FC<Props> = ({
inventories, inventories,
printerCombo, printerCombo,
@@ -84,6 +87,11 @@ const InventorySearch: React.FC<Props> = ({
const canStockAdjust = hasAbility(abilities, AUTH.INVENTORY_ADJUST); const canStockAdjust = hasAbility(abilities, AUTH.INVENTORY_ADJUST);
const searchInFlightRef = useRef(false); const searchInFlightRef = useRef(false);


const withUniqueInventoryRowId = useCallback((row: InventoryResult): InventoryResult => ({
...row,
id: `${row.itemId}-${row.uomId ?? 0}-${row.id}`,
}), []);

const buildSyntheticInventory = useCallback((item: ItemLookupRow): InventoryResult => { const buildSyntheticInventory = useCallback((item: ItemLookupRow): InventoryResult => {
const uom = item.uomDesc || item.uom || item.purchaseUnit || ''; const uom = item.uomDesc || item.uom || item.purchaseUnit || '';
return { return {
@@ -134,7 +142,7 @@ const InventorySearch: React.FC<Props> = ({
const applyItemsAsSyntheticInventories = useCallback( const applyItemsAsSyntheticInventories = useCallback(
(items: ItemLookupRow[]) => { (items: ItemLookupRow[]) => {
if (!items.length) return false; if (!items.length) return false;
const synthetics = items.map(buildSyntheticInventory);
const synthetics = items.map(buildSyntheticInventory).map(withUniqueInventoryRowId);
setFilteredInventories(synthetics); setFilteredInventories(synthetics);
setInventoriesTotalCount(synthetics.length); setInventoriesTotalCount(synthetics.length);
setSelectedInventory(synthetics[0]); setSelectedInventory(synthetics[0]);
@@ -142,7 +150,7 @@ const InventorySearch: React.FC<Props> = ({
setInventoryLotLinesPagingController(() => defaultPagingController); setInventoryLotLinesPagingController(() => defaultPagingController);
return true; return true;
}, },
[buildSyntheticInventory],
[buildSyntheticInventory, withUniqueInventoryRowId],
); );


// Scan-mode UI (hardware QR scanner via QrCodeScannerProvider) // Scan-mode UI (hardware QR scanner via QrCodeScannerProvider)
@@ -253,23 +261,25 @@ const InventorySearch: React.FC<Props> = ({
const response = await fetchInventoriesLatest(params); const response = await fetchInventoriesLatest(params);


if (response) { if (response) {
const records = (response.records ?? []).map(withUniqueInventoryRowId);
setInventoriesTotalCount(response.total); setInventoriesTotalCount(response.total);
switch (actionType) { switch (actionType) {
case 'init': case 'init':
case 'reset': case 'reset':
case 'search': case 'search':
setFilteredInventories(() => response.records);
setFilteredInventories(() => records);
break; break;
case 'paging': case 'paging':
setFilteredInventories((fi) => setFilteredInventories((fi) =>
uniqBy([...fi, ...response.records], (row) => `${row.itemId}-${row.uomId ?? row.uomUdfudesc ?? ''}`),
uniqBy([...fi, ...records], (row) => `${row.itemId}-${row.uomId ?? row.uomUdfudesc ?? ''}`),
); );
} }
return { ...response, records };
} }


return response; return response;
}, },
[enableLocationFilter, location, withLocationParams],
[enableLocationFilter, location, withLocationParams, withUniqueInventoryRowId],
); );


useEffect(() => { useEffect(() => {


+ 7
- 0
src/components/InventorySearch/InventoryTable.tsx Vedi File

@@ -39,6 +39,13 @@ const InventoryTable: React.FC<Props> = ({ inventories, pagingController, setPag
headerAlign: "right", headerAlign: "right",
type: "integer", type: "integer",
}, },
{
name: "uomId",
label: t("UoM ID"),
align: "right",
headerAlign: "right",
renderCell: (params) => params.uomId ?? "-",
},
{ {
name: "uomUdfudesc", name: "uomUdfudesc",
label: t("Stock UoM"), label: t("Stock UoM"),


+ 1
- 1
src/config/reportConfig.ts Vedi File

@@ -332,7 +332,7 @@ export const REPORTS: ReportDefinition[] = [
] ]
}, },
*/ */
/** FP-MTMS Version Checklist | Functions Ref. No. 82 | v1.0.0 | 2026-09-10 */
/** FP-MTMS Version Checklist | Functions Ref. No. 82 | v1.0.1 | 2026-09-11 */
{ {
id: "rep-007", id: "rep-007",
title: "庫存結餘報告", title: "庫存結餘報告",


+ 1
- 0
src/i18n/en/inventory.json Vedi File

@@ -78,6 +78,7 @@
"Target Location": "Target Location", "Target Location": "Target Location",
"Type": "Type", "Type": "Type",
"UoM": "UoM", "UoM": "UoM",
"UoM ID": "UoM ID",
"WIP": "Work in progress", "WIP": "Work in progress",
"Warehouse": "Warehouse", "Warehouse": "Warehouse",
"cmb": "Consumables", "cmb": "Consumables",


+ 1
- 0
src/i18n/zh/inventory.json Vedi File

@@ -78,6 +78,7 @@
"Target Location": "目標倉位", "Target Location": "目標倉位",
"Type": "類型", "Type": "類型",
"UoM": "單位", "UoM": "單位",
"UoM ID": "單位ID",
"WIP": "半成品", "WIP": "半成品",
"Warehouse": "倉庫", "Warehouse": "倉庫",
"cmb": "消耗品", "cmb": "消耗品",


+ 1
- 0
src/lib/featureUsageLog.ts Vedi File

@@ -58,6 +58,7 @@ export function logFeatureUsage(
const res = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/feature-usage/log`, { const res = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/feature-usage/log`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
skipAuthRedirect: true,
body: JSON.stringify({ body: JSON.stringify({
featureCode, featureCode,
actionType, actionType,


Caricamento…
Annulla
Salva