Bladeren bron

update

MergeProblem1
CANCERYS\kw093 23 uur geleden
bovenliggende
commit
4851a4d4c5
6 gewijzigde bestanden met toevoegingen van 194 en 69 verwijderingen
  1. +11
    -2
      src/app/api/stockTake/actions.ts
  2. +4
    -1
      src/components/StockTakeManagement/ApproverStockTake.tsx
  3. +168
    -64
      src/components/StockTakeManagement/ApproverStockTakeAll.tsx
  4. +4
    -1
      src/components/StockTakeManagement/PickerReStockTake.tsx
  5. +4
    -1
      src/components/StockTakeManagement/PickerStockTake.tsx
  6. +3
    -0
      src/i18n/zh/inventory.json

+ 11
- 2
src/app/api/stockTake/actions.ts Bestand weergeven

@@ -81,11 +81,13 @@ export const getInventoryLotDetailsBySection = async (
stockTakeSection: string,
stockTakeId?: number | null,
pageNum?: number,
pageSize?: number
pageSize?: number,
stockTakeRoundId?: number | null
) => {
console.log('🌐 [API] getInventoryLotDetailsBySection called with:', {
stockTakeSection,
stockTakeId,
stockTakeRoundId,
pageNum,
pageSize
});
@@ -95,6 +97,9 @@ export const getInventoryLotDetailsBySection = async (
if (stockTakeId != null && stockTakeId > 0) {
url += `&stockTakeId=${stockTakeId}`;
}
if (stockTakeRoundId != null && stockTakeRoundId > 0) {
url += `&stockTakeRoundId=${stockTakeRoundId}`;
}
console.log(' [API] Full URL:', url);
@@ -476,7 +481,8 @@ export const getInventoryLotDetailsBySectionNotMatch = async (
stockTakeSection: string,
stockTakeId?: number | null,
pageNum: number = 0,
pageSize: number = 10
pageSize: number = 10,
stockTakeRoundId?: number | null
) => {
const encodedSection = encodeURIComponent(stockTakeSection);
let url = `${BASE_API_URL}/stockTakeRecord/inventoryLotDetailsBySectionNotMatch?stockTakeSection=${encodedSection}&pageNum=${pageNum}`;
@@ -490,6 +496,9 @@ export const getInventoryLotDetailsBySectionNotMatch = async (
if (stockTakeId != null && stockTakeId > 0) {
url += `&stockTakeId=${stockTakeId}`;
}
if (stockTakeRoundId != null && stockTakeRoundId > 0) {
url += `&stockTakeRoundId=${stockTakeRoundId}`;
}
const response = await serverFetchJson<RecordsRes<InventoryLotDetailResponse>>(
url,


+ 4
- 1
src/components/StockTakeManagement/ApproverStockTake.tsx Bestand weergeven

@@ -116,7 +116,10 @@ const ApproverStockTake: React.FC<ApproverStockTakeProps> = ({
selectedSession.stockTakeSession,
selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null,
pageNum,
actualSize
actualSize,
selectedSession.stockTakeRoundId != null && selectedSession.stockTakeRoundId > 0
? selectedSession.stockTakeRoundId
: null
);
setInventoryLotDetails(Array.isArray(response.records) ? response.records : []);
setTotal(response.total || 0);


+ 168
- 64
src/components/StockTakeManagement/ApproverStockTakeAll.tsx Bestand weergeven

@@ -33,8 +33,6 @@ import {
saveApproverStockTakeRecord,
getApproverInventoryLotDetailsAllPending,
getApproverInventoryLotDetailsAllApproved,
BatchSaveApproverStockTakeAllRequest,
batchSaveApproverStockTakeRecordsAll,
updateStockTakeRecordStatusToNotMatch,
type ApproverInventoryLotDetailsQuery,
} from "@/app/api/stockTake/actions";
@@ -94,6 +92,79 @@ function hasAnyApproverSearchCriterion(f: ApproverSearchFilters): boolean {
);
}

function isBlankApproverField(value: string | undefined): boolean {
return value == null || String(value).trim() === "";
}

/** 審核員「盤點數」與「不良數」皆未輸入時應跳過儲存,維持 pending */
function isApproverInputsBothEmpty(
detailId: number,
approverQty: Record<number, string>,
approverBadQty: Record<number, string>
): boolean {
return isBlankApproverField(approverQty[detailId]) && isBlankApproverField(approverBadQty[detailId]);
}

type ApproverSaveBuildResult =
| {
ok: true;
request: SaveApproverStockTakeRecordRequest;
finalQty: number;
finalBadQty: number;
goodQty: number;
selection: QtySelectionType;
}
| { ok: false; reason: "skip_approver_empty" }
| { ok: false; reason: "error"; message: string };

function buildApproverSaveRequest(
detail: InventoryLotDetailResponse,
qtySelection: Record<number, QtySelectionType>,
approverQty: Record<number, string>,
approverBadQty: Record<number, string>,
currentUserId: number,
t: (key: string) => string
): ApproverSaveBuildResult {
const selection = qtySelection[detail.id] || "first";
let finalQty: number;
let finalBadQty: number;

if (selection === "first") {
if (detail.firstStockTakeQty == null) {
return { ok: false, reason: "error", message: t("First QTY is not available") };
}
finalQty = detail.firstStockTakeQty;
finalBadQty = detail.firstBadQty || 0;
} else if (selection === "second") {
if (detail.secondStockTakeQty == null) {
return { ok: false, reason: "error", message: t("Second QTY is not available") };
}
finalQty = detail.secondStockTakeQty;
finalBadQty = detail.secondBadQty || 0;
} else {
if (isApproverInputsBothEmpty(detail.id, approverQty, approverBadQty)) {
return { ok: false, reason: "skip_approver_empty" };
}
const approverQtyValue = approverQty[detail.id] || "0";
const approverBadQtyValue = approverBadQty[detail.id] || "0";
finalQty = parseFloat(approverQtyValue) || 0;
finalBadQty = parseFloat(approverBadQtyValue) || 0;
}

const request: SaveApproverStockTakeRecordRequest = {
stockTakeRecordId: detail.stockTakeRecordId || null,
qty: finalQty,
badQty: finalBadQty,
approverId: currentUserId,
approverQty: selection === "approver" ? finalQty : null,
approverBadQty: selection === "approver" ? finalBadQty : null,
lastSelect: selection === "first" ? 1 : selection === "second" ? 2 : 3,
};

const goodQty = finalQty - finalBadQty;
return { ok: true, request, finalQty, finalBadQty, goodQty, selection };
}

function parseDateTimeMs(
v: string | string[] | null | undefined
): number {
@@ -528,52 +599,31 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
return;
}

const selection = qtySelection[detail.id] || "first";
let finalQty: number;
let finalBadQty: number;

if (selection === "first") {
if (detail.firstStockTakeQty == null) {
onSnackbar(t("First QTY is not available"), "error");
return;
}
finalQty = detail.firstStockTakeQty;
finalBadQty = detail.firstBadQty || 0;
} else if (selection === "second") {
if (detail.secondStockTakeQty == null) {
onSnackbar(t("Second QTY is not available"), "error");
const built = buildApproverSaveRequest(
detail,
qtySelection,
approverQty,
approverBadQty,
currentUserId,
t
);
if (!built.ok) {
if (built.reason === "skip_approver_empty") {
onSnackbar(t("Approver input empty; save skipped, row remains pending"), "warning");
return;
}

finalQty = detail.secondStockTakeQty;
finalBadQty = detail.secondBadQty || 0;
} else {
// 与 Picker 逻辑一致:Approver 输入为空时按 0 处理
const approverQtyValue = approverQty[detail.id] || "0";
const approverBadQtyValue = approverBadQty[detail.id] || "0";
finalQty = parseFloat(approverQtyValue) || 0;
finalBadQty = parseFloat(approverBadQtyValue) || 0;
onSnackbar(built.message, "error");
return;
}

const { request, goodQty, finalQty, finalBadQty, selection } = built;

setSaving(true);
try {
const request: SaveApproverStockTakeRecordRequest = {
stockTakeRecordId: detail.stockTakeRecordId || null,
qty: finalQty,
badQty: finalBadQty,
approverId: currentUserId,
approverQty: selection === "approver" ? finalQty : null,
approverBadQty: selection === "approver" ? finalBadQty : null,
// lastSelect: 1=First, 2=Second, 3=Approver Input
lastSelect: selection === "first" ? 1 : selection === "second" ? 2 : 3,
};

await saveApproverStockTakeRecord(request, selectedSession.stockTakeId);

onSnackbar(t("Approver stock take record saved successfully"), "success");

const goodQty = finalQty - finalBadQty;

setInventoryLotDetails((prev) =>
prev.map((d) =>
d.id === detail.id
@@ -666,53 +716,107 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
if (!selectedSession || !currentUserId) {
return;
}
if (inventoryLotDetails.length === 0) {
onSnackbar(t("No rows loaded; set search criteria and search first"), "warning");
return;
}

setBatchSaving(true);
let successCount = 0;
let skippedApproverEmpty = 0;
let errorCount = 0;

try {
const request: BatchSaveApproverStockTakeAllRequest = {
stockTakeId: selectedSession.stockTakeId,
approverId: currentUserId,
itemKeyword: appliedFilters?.itemKeyword || null,
warehouseKeyword: appliedFilters?.warehouseKeyword || null,
sectionDescription:
appliedFilters?.sectionDescription && appliedFilters.sectionDescription !== "All"
? appliedFilters.sectionDescription
: null,
stockTakeSections: appliedFilters?.stockTakeSession || null,
};
for (const detail of sortedDetails) {
if (detail.stockTakeRecordStatus === "completed") {
continue;
}

const result = await batchSaveApproverStockTakeRecordsAll(request);
const built = buildApproverSaveRequest(
detail,
qtySelection,
approverQty,
approverBadQty,
currentUserId,
t
);
if (!built.ok) {
if (built.reason === "skip_approver_empty") {
skippedApproverEmpty += 1;
continue;
}
errorCount += 1;
continue;
}

try {
await saveApproverStockTakeRecord(built.request, selectedSession.stockTakeId);
successCount += 1;
const { goodQty, finalQty, finalBadQty, selection } = built;
setInventoryLotDetails((prev) =>
prev.map((d) =>
d.id === detail.id
? {
...d,
finalQty: goodQty,
approverQty: selection === "approver" ? finalQty : d.approverQty,
approverBadQty: selection === "approver" ? finalBadQty : d.approverBadQty,
stockTakeRecordStatus: "completed",
}
: d
)
);
} catch (e: any) {
errorCount += 1;
let msg = e?.message || t("Failed to save approver stock take record");
if (e?.response) {
try {
const errorData = await e.response.json();
msg = errorData.message || errorData.error || msg;
} catch {
/* ignore */
}
}
console.error("Batch save row failed", detail.id, msg);
}
}

onSnackbar(
t("Batch approver save completed: {{success}} success, {{errors}} errors", {
success: result.successCount,
errors: result.errorCount,
t("Batch approver save completed: {{success}} success, {{skipped}} skipped, {{errors}} errors", {
success: successCount,
skipped: skippedApproverEmpty,
errors: errorCount,
}),
result.errorCount > 0 ? "warning" : "success"
errorCount > 0 ? "warning" : "success"
);

if (appliedFilters) {
if (appliedFilters && successCount > 0) {
await loadDetails(appliedFilters);
}
} catch (e: any) {
console.error("handleBatchSubmitAll (all): Error:", e);
let errorMessage = t("Failed to batch save approver stock take records");

if (e?.message) {
errorMessage = e.message;
} else if (e?.response) {
try {
const errorData = await e.response.json();
errorMessage = errorData.message || errorData.error || errorMessage;
} catch {
}
}

onSnackbar(errorMessage, "error");
} finally {
setBatchSaving(false);
}
}, [selectedSession, currentUserId, variancePercentTolerance, t, onSnackbar, loadDetails, page, pageSize, mode, appliedFilters]);
}, [
selectedSession,
currentUserId,
t,
onSnackbar,
loadDetails,
mode,
appliedFilters,
inventoryLotDetails.length,
sortedDetails,
qtySelection,
approverQty,
approverBadQty,
]);

const formatNumber = (num: number | null | undefined): string => {
if (num == null) return "0";


+ 4
- 1
src/components/StockTakeManagement/PickerReStockTake.tsx Bestand weergeven

@@ -111,7 +111,10 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
selectedSession.stockTakeSession,
selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null,
pageNum,
actualSize
actualSize,
selectedSession.stockTakeRoundId != null && selectedSession.stockTakeRoundId > 0
? selectedSession.stockTakeRoundId
: null
);
setInventoryLotDetails(Array.isArray(response.records) ? response.records : []);
setTotal(response.total || 0);


+ 4
- 1
src/components/StockTakeManagement/PickerStockTake.tsx Bestand weergeven

@@ -125,7 +125,10 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({
selectedSession.stockTakeSession,
selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null,
pageNum,
actualSize
actualSize,
selectedSession.stockTakeRoundId != null && selectedSession.stockTakeRoundId > 0
? selectedSession.stockTakeRoundId
: null
);
setInventoryLotDetails(Array.isArray(response.records) ? response.records : []);
setTotal(response.total || 0);


+ 3
- 0
src/i18n/zh/inventory.json Bestand weergeven

@@ -181,6 +181,9 @@
"UNAVAILABLE": "不可用",
"No issues found": "未找到問題",
"Approver stock take record saved successfully": "審核員盤點記錄保存成功",
"Approver input empty; save skipped, row remains pending": "審核員盤點數與不良數皆未輸入,已略過儲存,該列維持待審核",
"No rows loaded; set search criteria and search first": "尚未載入資料,請設定搜尋條件並按搜尋",
"Batch approver save completed: {{success}} success, {{skipped}} skipped, {{errors}} errors": "批次審核儲存完成:成功 {{success}} 筆,略過 {{skipped}} 筆,錯誤 {{errors}} 筆",
"Approver Input": "審核員輸入",
"Approve": "審核",
"complete": "完成",


Laden…
Annuleren
Opslaan