Parcourir la source

change search

undefined
Harry Groves il y a 1 semaine
Parent
révision
1007e4aa55
12 fichiers modifiés avec 44 ajouts et 19 suppressions
  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 Voir le fichier

@@ -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.
* 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 (
query: string,


+ 4
- 4
src/app/(main)/report/page.tsx Voir le fichier

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

const response = await clientAuthFetch(excelUrl, {
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) {
setShowNoDataDialog(true);
return;
@@ -482,10 +482,10 @@ export default function ReportPage() {

const response = await clientAuthFetch(url, {
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) {
const errorText = await response.text();
console.error("Response error:", errorText);


+ 2
- 2
src/app/api/inventory/actions.ts Voir le fichier

@@ -179,8 +179,8 @@ async function fetchInventoriesLatestImpl(data: SearchInventory) {
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);



+ 1
- 1
src/app/api/inventory/index.ts Voir le fichier

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

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


+ 7
- 3
src/app/utils/clientAuthFetch.ts Voir le fichier

@@ -9,7 +9,7 @@ import { LOGIN_SESSION_EXPIRED_HREF } from "@/app/utils/authToken";
*/
export async function clientAuthFetch(
input: RequestInfo | URL,
init?: RequestInit
init?: RequestInit & { skipAuthRedirect?: boolean }
): Promise<Response> {
const token =
typeof window !== "undefined" ? localStorage.getItem("accessToken") : null;
@@ -18,9 +18,13 @@ export async function clientAuthFetch(
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") {
console.warn(`Auth error ${response.status} → redirecting to login`);
window.location.href = LOGIN_SESSION_EXPIRED_HREF;


+ 1
- 1
src/components/InventorySearch/InventoryLotLineTable.tsx Voir le fichier

@@ -584,7 +584,7 @@ const prevAdjustmentModalOpenRef = useRef(false);
return <>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap', mb: 2 }}>
<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>
{inventory && canStockAdjust && (
<Chip


+ 16
- 6
src/components/InventorySearch/InventorySearch.tsx Voir le fichier

@@ -40,8 +40,11 @@ type SearchQuery = Partial<
InventoryResult,
| "id"
| "qty"
| "uomId"
| "uomCode"
| "uomUdfudesc"
| "stockUomId"
| "stockUomCode"
| "germPerSmallestUnit"
| "qtyPerSmallestUnit"
| "itemSmallestUnit"
@@ -71,7 +74,7 @@ const extractItemRecords = (res: unknown): ItemLookupRow[] => {
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> = ({
inventories,
printerCombo,
@@ -84,6 +87,11 @@ const InventorySearch: React.FC<Props> = ({
const canStockAdjust = hasAbility(abilities, AUTH.INVENTORY_ADJUST);
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 uom = item.uomDesc || item.uom || item.purchaseUnit || '';
return {
@@ -134,7 +142,7 @@ const InventorySearch: React.FC<Props> = ({
const applyItemsAsSyntheticInventories = useCallback(
(items: ItemLookupRow[]) => {
if (!items.length) return false;
const synthetics = items.map(buildSyntheticInventory);
const synthetics = items.map(buildSyntheticInventory).map(withUniqueInventoryRowId);
setFilteredInventories(synthetics);
setInventoriesTotalCount(synthetics.length);
setSelectedInventory(synthetics[0]);
@@ -142,7 +150,7 @@ const InventorySearch: React.FC<Props> = ({
setInventoryLotLinesPagingController(() => defaultPagingController);
return true;
},
[buildSyntheticInventory],
[buildSyntheticInventory, withUniqueInventoryRowId],
);

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

if (response) {
const records = (response.records ?? []).map(withUniqueInventoryRowId);
setInventoriesTotalCount(response.total);
switch (actionType) {
case 'init':
case 'reset':
case 'search':
setFilteredInventories(() => response.records);
setFilteredInventories(() => records);
break;
case 'paging':
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;
},
[enableLocationFilter, location, withLocationParams],
[enableLocationFilter, location, withLocationParams, withUniqueInventoryRowId],
);

useEffect(() => {


+ 7
- 0
src/components/InventorySearch/InventoryTable.tsx Voir le fichier

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


+ 1
- 1
src/config/reportConfig.ts Voir le fichier

@@ -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",
title: "庫存結餘報告",


+ 1
- 0
src/i18n/en/inventory.json Voir le fichier

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


+ 1
- 0
src/i18n/zh/inventory.json Voir le fichier

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


+ 1
- 0
src/lib/featureUsageLog.ts Voir le fichier

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


Chargement…
Annuler
Enregistrer