CANCERYS\kw093 5 дней назад
Родитель
Сommit
f1fe469ccb
8 измененных файлов: 121 добавлений и 35 удалений
  1. +89
    -6
      src/app/api/stockTake/actions.ts
  2. +3
    -1
      src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx
  3. +1
    -0
      src/components/Jodetail/newJobPickExecution.tsx
  4. +3
    -3
      src/components/PickOrderSearch/LotTable.tsx
  5. +3
    -16
      src/components/PickOrderSearch/PickExecution.tsx
  6. +15
    -7
      src/components/ProductionProcess/ProductionProcessList.tsx
  7. +4
    -2
      src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx
  8. +3
    -0
      src/i18n/zh/inventory.json

+ 89
- 6
src/app/api/stockTake/actions.ts Просмотреть файл

@@ -3,6 +3,7 @@
import { cache } from 'react';
import { serverFetchJson } from "@/app/utils/fetchUtil"; // 改为 serverFetchJson
import { BASE_API_URL } from "@/config/api";
import { stockTakeDebugLog } from "@/components/StockTakeManagement/stockTakeDebugLog";

export interface RecordsRes<T> {
records: T[];
@@ -51,6 +52,31 @@ export interface InventoryLotDetailResponse {
approverTime?: string | string[] | null;
}

/**
* `approverInventoryLotDetailsAll*`:
* - `total` = 全域 `inventory_lot_line` 中 `status = available` 筆數(與 DB COUNT 一致)
* - `filteredRecordCount` = 目前 tab/篩選後筆數(分頁用)
*/
export interface ApproverInventoryLotDetailsRecordsRes extends RecordsRes<InventoryLotDetailResponse> {
filteredRecordCount?: number;
totalWaitingForApprover?: number;
totalApproved?: number;
}

function normalizeApproverInventoryLotDetailsRes(
raw: ApproverInventoryLotDetailsRecordsRes
): ApproverInventoryLotDetailsRecordsRes {
const waiting = Number(raw.totalWaitingForApprover ?? 0) || 0;
const approved = Number(raw.totalApproved ?? 0) || 0;
return {
records: Array.isArray(raw.records) ? raw.records : [],
total: Number(raw.total ?? 0) || 0,
filteredRecordCount: Number(raw.filteredRecordCount ?? 0) || 0,
totalWaitingForApprover: waiting,
totalApproved: approved,
};
}

export const getInventoryLotDetailsBySection = async (
stockTakeSection: string,
stockTakeId?: number | null,
@@ -122,13 +148,13 @@ export const getApproverInventoryLotDetailsAll = async (
}

const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAll?${params.toString()}`;
const response = await serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(
const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(
url,
{
method: "GET",
},
);
return response;
return normalizeApproverInventoryLotDetailsRes(response);
}
export const getApproverInventoryLotDetailsAllPending = async (
stockTakeId?: number | null,
@@ -142,7 +168,8 @@ export const getApproverInventoryLotDetailsAllPending = async (
params.append("stockTakeId", String(stockTakeId));
}
const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllPending?${params.toString()}`;
return serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(url, { method: "GET" });
const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(url, { method: "GET" });
return normalizeApproverInventoryLotDetailsRes(response);
}
export const getApproverInventoryLotDetailsAllApproved = async (
stockTakeId?: number | null,
@@ -156,7 +183,8 @@ export const getApproverInventoryLotDetailsAllApproved = async (
params.append("stockTakeId", String(stockTakeId));
}
const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllApproved?${params.toString()}`;
return serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(url, { method: "GET" });
const response = await serverFetchJson<ApproverInventoryLotDetailsRecordsRes>(url, { method: "GET" });
return normalizeApproverInventoryLotDetailsRes(response);
}

export const importStockTake = async (data: FormData) => {
@@ -242,6 +270,20 @@ export const saveStockTakeRecord = async (
console.log('saveStockTakeRecord: request:', request);
console.log('saveStockTakeRecord: stockTakeId:', stockTakeId);
console.log('saveStockTakeRecord: stockTakerId:', stockTakerId);
// #region agent log
stockTakeDebugLog(
"actions.ts:saveStockTakeRecord",
"server action saveStockTakeRecord ok",
"H3",
{
stockTakeId,
stockTakerId,
inventoryLotLineId: request.inventoryLotLineId,
hasRecordId: request.stockTakeRecordId != null,
resultId: result?.id ?? null,
}
);
// #endregion
return result;
} catch (error: any) {
// 尝试从错误响应中提取消息
@@ -271,12 +313,26 @@ export interface BatchSaveStockTakeRecordResponse {
errors: string[];
}
export const batchSaveStockTakeRecords = cache(async (data: BatchSaveStockTakeRecordRequest) => {
return serverFetchJson<BatchSaveStockTakeRecordResponse>(`${BASE_API_URL}/stockTakeRecord/batchSaveStockTakeRecords`,
const r = await serverFetchJson<BatchSaveStockTakeRecordResponse>(`${BASE_API_URL}/stockTakeRecord/batchSaveStockTakeRecords`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
// #region agent log
stockTakeDebugLog(
"actions.ts:batchSaveStockTakeRecords",
"server batch picker result",
"H4",
{
stockTakeId: data.stockTakeId,
stockTakeSection: data.stockTakeSection,
successCount: r.successCount,
errorCount: r.errorCount,
}
);
// #endregion
return r
})
// Add these interfaces and functions

@@ -325,6 +381,19 @@ export const saveApproverStockTakeRecord = async (
body: JSON.stringify(request),
},
);
// #region agent log
stockTakeDebugLog(
"actions.ts:saveApproverStockTakeRecord",
"server action saveApproverStockTakeRecord ok",
"H3",
{
stockTakeId,
stockTakeRecordId: request.stockTakeRecordId ?? null,
lastSelect: request.lastSelect ?? null,
hasApproverQty: request.approverQty != null,
}
);
// #endregion
return result;
} catch (error: any) {
if (error?.response) {
@@ -354,7 +423,7 @@ export const batchSaveApproverStockTakeRecords = cache(async (data: BatchSaveApp
)

export const batchSaveApproverStockTakeRecordsAll = cache(async (data: BatchSaveApproverStockTakeAllRequest) => {
return serverFetchJson<BatchSaveApproverStockTakeRecordResponse>(
const r = await serverFetchJson<BatchSaveApproverStockTakeRecordResponse>(
`${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecordsAll`,
{
method: "POST",
@@ -362,6 +431,20 @@ export const batchSaveApproverStockTakeRecordsAll = cache(async (data: BatchSave
headers: { "Content-Type": "application/json" },
}
)
// #region agent log
stockTakeDebugLog(
"actions.ts:batchSaveApproverStockTakeRecordsAll",
"server batch approver-all result",
"H4",
{
stockTakeId: data.stockTakeId,
approverId: data.approverId,
successCount: r.successCount,
errorCount: r.errorCount,
}
);
// #endregion
return r
})

export const updateStockTakeRecordStatusToNotMatch = async (


+ 3
- 1
src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx Просмотреть файл

@@ -65,7 +65,9 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
const { t } = useTranslation("ticketReleaseTable");
const { data: session } = useSession() as { data: SessionWithTokens | null };
const abilities = session?.abilities ?? session?.user?.abilities ?? [];
const canManageDoPickOps = abilities.includes(AUTH.ADMIN);
// 依照 DB `authority.authority = 'ADMIN'` 的逻辑:仅 abilities 明確包含 ADMIN 才允許操作
// (避免 abilities 裡出現前後空白導致 includes 判斷失效)
const canManageDoPickOps = abilities.some((a) => a.trim() === AUTH.ADMIN);

const [queryDate, setQueryDate] = useState<Dayjs>(() => dayjs());
const [selectedFloor, setSelectedFloor] = useState<string>("");


+ 1
- 0
src/components/Jodetail/newJobPickExecution.tsx Просмотреть файл

@@ -2763,6 +2763,7 @@ const sortedData = [...sourceData].sort((a, b) => {
disabled={
(Number(lot.stockOutLineId) > 0 && actionBusyBySolId[Number(lot.stockOutLineId)] === true) ||
lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'checked' ||
lot.noLot === true ||
!lot.lotId ||
(Number(lot.stockOutLineId) > 0 &&


+ 3
- 3
src/components/PickOrderSearch/LotTable.tsx Просмотреть файл

@@ -397,8 +397,8 @@ const LotTable: React.FC<LotTableProps> = ({
const { t } = useTranslation("pickOrder");
const calculateRemainingRequiredQty = useCallback((lot: LotPickData) => {
const requiredQty = lot.requiredQty || 0;
const stockOutLineQty = lot.stockOutLineQty || 0;
return Math.max(0, requiredQty - stockOutLineQty);
const availableQty = lot.availableQty || 0;
return Math.max(0, requiredQty + availableQty);
}, []);
// Add QR scanner context
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
@@ -506,7 +506,7 @@ const LotTable: React.FC<LotTableProps> = ({
const stockOutLineUpdate = await updateStockOutLineStatus({
id: selectedLotForQr.stockOutLineId,
status: 'checked',
qty: selectedLotForQr.stockOutLineQty || 0
qty: 0
});
console.log(" Stock out line updated to 'checked':", stockOutLineUpdate);


+ 3
- 16
src/components/PickOrderSearch/PickExecution.tsx Просмотреть файл

@@ -361,13 +361,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
try {
// FIXED: 计算累计拣货数量
const totalPickedForThisLot = (selectedLot.actualPickQty || 0) + qty;
console.log(" DEBUG - Previous picked:", selectedLot.actualPickQty || 0);
console.log(" DEBUG - Current submit:", qty);
console.log(" DEBUG - Total picked:", totalPickedForThisLot);
console.log("�� DEBUG - Required qty:", selectedLot.requiredQty);

// FIXED: 状态应该基于累计拣货数量
let newStatus = 'partially_completed';
let newStatus = 'completed';
if (totalPickedForThisLot >= selectedLot.requiredQty) {
newStatus = 'completed';
}
@@ -388,16 +384,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
return;
}
if (qty > 0) {
const inventoryLotLineUpdate = await updateInventoryLotLineQuantities({
inventoryLotLineId: lotId,
qty: qty,
status: 'available',
operation: 'pick'
});
console.log("Inventory lot line updated:", inventoryLotLineUpdate);
}

// RE-ENABLE: Check if pick order should be completed
if (newStatus === 'completed') {


+ 15
- 7
src/components/ProductionProcess/ProductionProcessList.tsx Просмотреть файл

@@ -32,6 +32,7 @@ import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox";
import { AUTH } from "@/authorities";


import {
@@ -103,6 +104,9 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({
const [openModal, setOpenModal] = useState<boolean>(false);
const [modalInfo, setModalInfo] = useState<StockInLineInput>();
const currentUserId = session?.id ? parseInt(session.id) : undefined;
const abilities = session?.abilities ?? session?.user?.abilities ?? [];
// 依照 DB `authority.authority = 'ADMIN'` 的逻辑:僅 abilities 明確包含 ADMIN 才能操作
const canManageUpdateJo = abilities.some((a) => a.trim() === AUTH.ADMIN);
type ProcessFilter = "all" | "drink" | "other";
const [suggestedLocationCode, setSuggestedLocationCode] = useState<string | null>(null);

@@ -275,6 +279,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({
fetchProcesses();
}, [fetchProcesses]);
const handleUpdateJo = useCallback(async (process: AllJoborderProductProcessInfoResponse) => {
if (!canManageUpdateJo) return;
if (!process.jobOrderId) {
alert(t("Invalid Job Order Id"));
return;
@@ -308,7 +313,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({
} finally {
setLoading(false);
}
}, [t, fetchProcesses]);
}, [t, fetchProcesses, canManageUpdateJo]);

const openConfirm = useCallback((message: string, action: () => Promise<void>) => {
setConfirmMessage(message);
@@ -590,13 +595,16 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({
<Button
variant="contained"
size="small"
disabled={!canManageUpdateJo}
onClick={() =>
openConfirm(
t("Confirm to update this Job Order?"),
async () => {
await handleUpdateJo(process);
}
)
canManageUpdateJo
? openConfirm(
t("Confirm to update this Job Order?"),
async () => {
await handleUpdateJo(process);
}
)
: undefined
}
>
{t("Update Job Order")}


+ 4
- 2
src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx Просмотреть файл

@@ -317,12 +317,14 @@ useEffect(() => {
try {
const parseStartTime = performance.now();
const data: QrCodeInfo = JSON.parse(scannedValues);

const normalizedScannedValues = scannedValues.replace(/\\"/g, '"');
const data: QrCodeInfo = JSON.parse(normalizedScannedValues);
const parseTime = performance.now() - parseStartTime;
// console.log(`%c Parsed scan data`, "color:green", data);
//console.log(`⏱️ [QR SCANNER PROCESS] JSON parse time: ${parseTime.toFixed(2)}ms`);
const content = scannedValues.substring(1, scannedValues.length - 1);
const content = normalizedScannedValues.substring(1, normalizedScannedValues.length - 1);
data.value = content;
const setResultStartTime = performance.now();


+ 3
- 0
src/i18n/zh/inventory.json Просмотреть файл

@@ -9,6 +9,9 @@
"Approver Pending": "審核待處理",
"Approver Approved": "審核通過",
"Approver Time": "審核時間",
"Total need stock take": "總需盤點數量",
"Waiting for Approver": "待審核數量",
"Total Approved": "已審核數量",
"mat": "物料",
"variance": "差異",
"Plan Start Date": "計劃開始日期",


Загрузка…
Отмена
Сохранить