Procházet zdrojové kódy

#24 / #25 / #26)#27 #22 #4/#6

fix負數倉
CANCERYS\kw093 před 5 dny
rodič
revize
e39365f4f8
11 změnil soubory, kde provedl 141 přidání a 57 odebrání
  1. +5
    -13
      src/app/api/inventory/actions.ts
  2. +1
    -0
      src/app/api/inventory/index.ts
  3. +82
    -0
      src/app/api/inventory/inventoryBucket.ts
  4. +1
    -0
      src/app/api/jo/actions.ts
  5. +2
    -0
      src/app/api/jo/index.ts
  6. +4
    -3
      src/components/InventorySearch/InventorySearch.tsx
  7. +9
    -9
      src/components/JoSave/JoRelease.tsx
  8. +9
    -9
      src/components/JoSave/PickTable.tsx
  9. +9
    -9
      src/components/JoSearch/JoSearch.tsx
  10. +9
    -9
      src/components/JoWorkbench/JoWorkbenchSearch.tsx
  11. +10
    -5
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx

+ 5
- 13
src/app/api/inventory/actions.ts Zobrazit soubor

@@ -161,30 +161,22 @@ async function fetchInventoriesImpl(data: SearchInventory) {
); );
} }


async function fetchInventoriesLatestImpl(data: SearchInventory) {
const queryStr = convertObjToURLSearchParams(data);
return serverFetchJson<InventoryResultByPage>(
`${BASE_API_URL}/inventory/searchLatest/getRecordByPage?${queryStr}`,
{ next: { tags: ["inventories"] } },
);
}

export const fetchInventories = cache(fetchInventoriesImpl); export const fetchInventories = cache(fetchInventoriesImpl);


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.3 | 2026-09-07
* Inventory search page: latest inventory row per item (no baseUnit/uomId filter).
* ChangeList #24 — same as fetchInventories (all stock-UOM buckets).
* Backend `/inventory/searchLatest/getRecordByPage` is deprecated and aliases getRecordByPage.
*/ */
export const fetchInventoriesLatest = cache(fetchInventoriesLatestImpl);
export const fetchInventoriesLatest = cache(fetchInventoriesImpl);


/** Bypass React cache() after mutations so lists show fresh qty. */ /** Bypass React cache() after mutations so lists show fresh qty. */
export async function fetchInventoriesFresh(data: SearchInventory) { export async function fetchInventoriesFresh(data: SearchInventory) {
return fetchInventoriesImpl(data); return fetchInventoriesImpl(data);
} }


/** Bypass React cache() for inventory search page latest-inventory search. */
/** @deprecated Use fetchInventoriesFresh — same full-page search. */
export async function fetchInventoriesLatestFresh(data: SearchInventory) { export async function fetchInventoriesLatestFresh(data: SearchInventory) {
return fetchInventoriesLatestImpl(data);
return fetchInventoriesImpl(data);
} }


async function fetchInventoryLotLinesImpl(data: SearchInventoryLotLine) { async function fetchInventoryLotLinesImpl(data: SearchInventoryLotLine) {


+ 1
- 0
src/app/api/inventory/index.ts Zobrazit soubor

@@ -14,6 +14,7 @@ export interface InventoryResult {
onHoldQty: number; onHoldQty: number;
unavailableQty: number; unavailableQty: number;
availableQty: number; availableQty: number;
stockUomId?: number | null;
uomCode: string; uomCode: string;
uomUdfudesc: string; uomUdfudesc: string;
uomShortDesc: string; uomShortDesc: string;


+ 82
- 0
src/app/api/inventory/inventoryBucket.ts Zobrazit soubor

@@ -0,0 +1,82 @@
/** Keys used to pick one inventory row when an item has multiple stock-UOM buckets. */
export type InventoryBucketPick = {
itemId?: number | null;
itemCode?: string | null;
itemName?: string | null;
uomId?: number | null;
stockUomId?: number | null;
uom?: string | null;
shortUom?: string | null;
stockUom?: string | null;
};

export type InventoryBucketRow = {
itemId?: number | null;
itemCode?: string | null;
itemName?: string | null;
stockUomId?: number | null;
uomCode?: string | null;
uomUdfudesc?: string | null;
uomShortDesc?: string | null;
availableQty?: number | null;
onHandQty?: number | null;
unavailableQty?: number | null;
};

const norm = (s?: string | null) => (s ?? "").trim().toLowerCase();

/** Available = onHand − unavailable. Do not treat 0 as missing (`||` is wrong). */
export function inventoryAvailableQty(inv: InventoryBucketRow): number {
if (inv.availableQty != null && !Number.isNaN(Number(inv.availableQty))) {
return Number(inv.availableQty);
}
return Number(inv.onHandQty ?? 0) - Number(inv.unavailableQty ?? 0);
}

function itemMatches(inv: InventoryBucketRow, pick: InventoryBucketPick): boolean {
if (pick.itemId != null && inv.itemId != null) {
return Number(inv.itemId) === Number(pick.itemId);
}
if (pick.itemCode && inv.itemCode) {
return inv.itemCode === pick.itemCode;
}
if (pick.itemName && inv.itemName) {
return inv.itemName === pick.itemName;
}
return false;
}

function uomMatches(inv: InventoryBucketRow, pick: InventoryBucketPick): boolean {
const pickUomId = pick.stockUomId ?? pick.uomId;
if (pickUomId != null && inv.stockUomId != null) {
return Number(inv.stockUomId) === Number(pickUomId);
}
const labels = [pick.uom, pick.shortUom, pick.stockUom].map(norm).filter(Boolean);
if (labels.length === 0) return true;
const invLabels = [inv.uomUdfudesc, inv.uomShortDesc, inv.uomCode].map(norm);
return labels.some((l) => invLabels.includes(l));
}

export function matchInventoryBucket(
inventories: InventoryBucketRow[],
pick: InventoryBucketPick,
): InventoryBucketRow | undefined {
const itemHits = inventories.filter((inv) => itemMatches(inv, pick));
if (itemHits.length === 0) return undefined;
const hasUomPick =
pick.stockUomId != null ||
pick.uomId != null ||
[pick.uom, pick.shortUom, pick.stockUom].some((s) => Boolean(norm(s)));
if (hasUomPick) {
return itemHits.find((inv) => uomMatches(inv, pick));
}
return itemHits[0];
}

export function getStockAvailableFromInventories(
inventories: InventoryBucketRow[],
pick: InventoryBucketPick,
): number {
const inv = matchInventoryBucket(inventories, pick);
return inv ? inventoryAvailableQty(inv) : 0;
}

+ 1
- 0
src/app/api/jo/actions.ts Zobrazit soubor

@@ -533,6 +533,7 @@ export interface JobOrderLineInfo {


stockUom: string, stockUom: string,
stockBaseUom: string, stockBaseUom: string,
stockUomId?: number | null,
availableStatus: string, availableStatus: string,
bomProcessId: number, bomProcessId: number,


+ 2
- 0
src/app/api/jo/index.ts Zobrazit soubor

@@ -67,6 +67,8 @@ export interface JoDetail {


export interface JoDetailPickLine { export interface JoDetailPickLine {
id: number; id: number;
itemId?: number;
uomId?: number;
code: string; code: string;
name: string; name: string;
type: string; type: string;


+ 4
- 3
src/components/InventorySearch/InventorySearch.tsx Zobrazit soubor

@@ -12,7 +12,7 @@ import {
analyzeQrCode, analyzeQrCode,
SearchInventory, SearchInventory,
SearchInventoryLotLine, SearchInventoryLotLine,
fetchInventoriesLatest,
fetchInventories,
fetchInventoryLotLines, fetchInventoryLotLines,
} from '@/app/api/inventory/actions'; } from '@/app/api/inventory/actions';
import { PrinterCombo } from '@/app/api/settings/printer'; import { PrinterCombo } from '@/app/api/settings/printer';
@@ -85,6 +85,7 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
uomCode: item.uom || uom, uomCode: item.uom || uom,
uomUdfudesc: uom, uomUdfudesc: uom,
uomShortDesc: item.uom || uom, uomShortDesc: item.uom || uom,
stockUomId: undefined,
qtyPerSmallestUnit: 1, qtyPerSmallestUnit: 1,
baseUom: uom, baseUom: uom,
price: 0, price: 0,
@@ -208,7 +209,7 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
pageSize: pagingController.pageSize, pageSize: pagingController.pageSize,
}; };


const response = await fetchInventoriesLatest(params);
const response = await fetchInventories(params);


if (response) { if (response) {
setInventoriesTotalCount(response.total); setInventoriesTotalCount(response.total);
@@ -220,7 +221,7 @@ const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
break; break;
case 'paging': case 'paging':
setFilteredInventories((fi) => setFilteredInventories((fi) =>
uniqBy([...fi, ...response.records], 'itemId'),
uniqBy([...fi, ...response.records], 'id'),
); );
} }
} }


+ 9
- 9
src/components/JoSave/JoRelease.tsx Zobrazit soubor

@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { JoDetailPickLine } from "@/app/api/jo"; import { JoDetailPickLine } from "@/app/api/jo";
import { fetchInventories } from "@/app/api/inventory/actions"; import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory"; import { InventoryResult } from "@/app/api/inventory";
import { getStockAvailableFromInventories } from "@/app/api/inventory/inventoryBucket";
import { useEffect, useState, useMemo } from "react"; import { useEffect, useState, useMemo } from "react";
import { useFormContext } from "react-hook-form"; import { useFormContext } from "react-hook-form";
import { JoDetail } from "@/app/api/jo"; import { JoDetail } from "@/app/api/jo";
@@ -50,15 +51,14 @@ const JoRelease: React.FC<Props> = ({
}, [pickLines]); }, [pickLines]);


const getStockAvailable = (pickLine: JoDetailPickLine) => { const getStockAvailable = (pickLine: JoDetailPickLine) => {
const inventory = inventoryData.find(inventory =>
inventory.itemCode === pickLine.code || inventory.itemName === pickLine.name
);
if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
}
return 0;
return getStockAvailableFromInventories(inventoryData, {
itemId: pickLine.itemId,
itemCode: pickLine.code,
itemName: pickLine.name,
uomId: pickLine.uomId,
uom: pickLine.uom,
shortUom: pickLine.shortUom,
});
}; };


const isStockSufficient = (pickLine: JoDetailPickLine) => { const isStockSufficient = (pickLine: JoDetailPickLine) => {


+ 9
- 9
src/components/JoSave/PickTable.tsx Zobrazit soubor

@@ -12,6 +12,7 @@ import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutli
import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined'; import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';
import { fetchInventories } from "@/app/api/inventory/actions"; import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory"; import { InventoryResult } from "@/app/api/inventory";
import { getStockAvailableFromInventories } from "@/app/api/inventory/inventoryBucket";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import DoDisturbAltRoundedIcon from '@mui/icons-material/DoDisturbAltRounded'; import DoDisturbAltRoundedIcon from '@mui/icons-material/DoDisturbAltRounded';


@@ -54,15 +55,14 @@ const PickTable: React.FC<Props> = ({
}, [pickLines]); }, [pickLines]);


const getStockAvailable = (pickLine: JoDetailPickLine) => { const getStockAvailable = (pickLine: JoDetailPickLine) => {
const inventory = inventoryData.find(inventory =>
inventory.itemCode === pickLine.code || inventory.itemName === pickLine.name
);
if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
}
return 0;
return getStockAvailableFromInventories(inventoryData, {
itemId: pickLine.itemId,
itemCode: pickLine.code,
itemName: pickLine.name,
uomId: pickLine.uomId,
uom: pickLine.uom,
shortUom: pickLine.shortUom,
});
}; };


const isStockSufficient = (pickLine: JoDetailPickLine) => { const isStockSufficient = (pickLine: JoDetailPickLine) => {


+ 9
- 9
src/components/JoSearch/JoSearch.tsx Zobrazit soubor

@@ -25,6 +25,7 @@ import { msg } from "../Swal/CustomAlerts";
import dayjs from "dayjs"; import dayjs from "dayjs";
//import { fetchInventories } from "@/app/api/inventory/actions"; //import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory"; import { InventoryResult } from "@/app/api/inventory";
import { getStockAvailableFromInventories } from "@/app/api/inventory/inventoryBucket";
import { PrinterCombo } from "@/app/api/settings/printer"; import { PrinterCombo } from "@/app/api/settings/printer";
import { JobTypeResponse } from "@/app/api/jo/actions"; import { JobTypeResponse } from "@/app/api/jo/actions";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
@@ -132,15 +133,14 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo, jobT
*/ */
const getStockAvailable = (pickLine: JoDetailPickLine) => { const getStockAvailable = (pickLine: JoDetailPickLine) => {
const inventory = inventoryData.find(inventory =>
inventory.itemCode === pickLine.code || inventory.itemName === pickLine.name
);
if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
}
return 0;
return getStockAvailableFromInventories(inventoryData, {
itemId: pickLine.itemId,
itemCode: pickLine.code,
itemName: pickLine.name,
uomId: pickLine.uomId,
uom: pickLine.uom,
shortUom: pickLine.shortUom,
});
}; };


const isStockSufficient = (pickLine: JoDetailPickLine) => { const isStockSufficient = (pickLine: JoDetailPickLine) => {


+ 9
- 9
src/components/JoWorkbench/JoWorkbenchSearch.tsx Zobrazit soubor

@@ -26,6 +26,7 @@ import { msg } from "../Swal/CustomAlerts";
import dayjs from "dayjs"; import dayjs from "dayjs";
//import { fetchInventories } from "@/app/api/inventory/actions"; //import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory"; import { InventoryResult } from "@/app/api/inventory";
import { getStockAvailableFromInventories } from "@/app/api/inventory/inventoryBucket";
import { PrinterCombo } from "@/app/api/settings/printer"; import { PrinterCombo } from "@/app/api/settings/printer";
import { JobTypeResponse } from "@/app/api/jo/actions"; import { JobTypeResponse } from "@/app/api/jo/actions";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
@@ -133,15 +134,14 @@ const JoWorkbenchSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCo
*/ */
const getStockAvailable = (pickLine: JoDetailPickLine) => { const getStockAvailable = (pickLine: JoDetailPickLine) => {
const inventory = inventoryData.find(inventory =>
inventory.itemCode === pickLine.code || inventory.itemName === pickLine.name
);
if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
}
return 0;
return getStockAvailableFromInventories(inventoryData, {
itemId: pickLine.itemId,
itemCode: pickLine.code,
itemName: pickLine.name,
uomId: pickLine.uomId,
uom: pickLine.uom,
shortUom: pickLine.shortUom,
});
}; };


const isStockSufficient = (pickLine: JoDetailPickLine) => { const isStockSufficient = (pickLine: JoDetailPickLine) => {


+ 10
- 5
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx Zobrazit soubor

@@ -36,6 +36,7 @@ import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutli
import DoDisturbAltRoundedIcon from '@mui/icons-material/DoDisturbAltRounded'; import DoDisturbAltRoundedIcon from '@mui/icons-material/DoDisturbAltRounded';
import { fetchInventories } from "@/app/api/inventory/actions"; import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory"; import { InventoryResult } from "@/app/api/inventory";
import { matchInventoryBucket, inventoryAvailableQty } from "@/app/api/inventory/inventoryBucket";
import { releaseJoForWorkbench } from "@/app/api/jo/workbenchActions"; import { releaseJoForWorkbench } from "@/app/api/jo/workbenchActions";
import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan"; import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
import ProcessSummaryHeader from "./ProcessSummaryHeader"; import ProcessSummaryHeader from "./ProcessSummaryHeader";
@@ -187,13 +188,17 @@ const getStockAvailable = (line: JobOrderLineInfo) => {
if (line.type?.toLowerCase() === "consumables" || line.type?.toLowerCase() === "nm") { if (line.type?.toLowerCase() === "consumables" || line.type?.toLowerCase() === "nm") {
return line.stockQty || 0; return line.stockQty || 0;
} }
const inventory = inventoryData.find(inv =>
inv.itemCode === line.itemCode || inv.itemName === line.itemName
);
const inventory = matchInventoryBucket(inventoryData, {
itemId: line.itemId,
itemCode: line.itemCode,
itemName: line.itemName,
stockUomId: line.stockUomId,
stockUom: line.stockUom,
});
if (inventory) { if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
return inventoryAvailableQty(inventory);
} }
return line.stockQty || 0;
return line.stockQty ?? 0;
}; };
const handleOpenPlanStartDialog = useCallback(() => { const handleOpenPlanStartDialog = useCallback(() => {
// 将 processData.date 转换为 dayjs 对象 // 将 processData.date 转换为 dayjs 对象


Načítá se…
Zrušit
Uložit