Kaynağa Gözat

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

fix負數倉
CANCERYS\kw093 5 gün önce
ebeveyn
işleme
e39365f4f8
11 değiştirilmiş dosya ile 141 ekleme ve 57 silme
  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 Dosyayı Görüntüle

@@ -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);

/**
* 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. */
export async function fetchInventoriesFresh(data: SearchInventory) {
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) {
return fetchInventoriesLatestImpl(data);
return fetchInventoriesImpl(data);
}

async function fetchInventoryLotLinesImpl(data: SearchInventoryLotLine) {


+ 1
- 0
src/app/api/inventory/index.ts Dosyayı Görüntüle

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


+ 82
- 0
src/app/api/inventory/inventoryBucket.ts Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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

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


+ 2
- 0
src/app/api/jo/index.ts Dosyayı Görüntüle

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

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


+ 4
- 3
src/components/InventorySearch/InventorySearch.tsx Dosyayı Görüntüle

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

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

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


+ 9
- 9
src/components/JoSave/JoRelease.tsx Dosyayı Görüntüle

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

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) => {


+ 9
- 9
src/components/JoSave/PickTable.tsx Dosyayı Görüntüle

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

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

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) => {


+ 9
- 9
src/components/JoSearch/JoSearch.tsx Dosyayı Görüntüle

@@ -25,6 +25,7 @@ import { msg } from "../Swal/CustomAlerts";
import dayjs from "dayjs";
//import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory";
import { getStockAvailableFromInventories } from "@/app/api/inventory/inventoryBucket";
import { PrinterCombo } from "@/app/api/settings/printer";
import { JobTypeResponse } from "@/app/api/jo/actions";
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 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) => {


+ 9
- 9
src/components/JoWorkbench/JoWorkbenchSearch.tsx Dosyayı Görüntüle

@@ -26,6 +26,7 @@ import { msg } from "../Swal/CustomAlerts";
import dayjs from "dayjs";
//import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory";
import { getStockAvailableFromInventories } from "@/app/api/inventory/inventoryBucket";
import { PrinterCombo } from "@/app/api/settings/printer";
import { JobTypeResponse } from "@/app/api/jo/actions";
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 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) => {


+ 10
- 5
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx Dosyayı Görüntüle

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


Yükleniyor…
İptal
Kaydet