Ver a proveniência

stock take batch save fix

production
CANCERYS\kw093 há 3 semanas
ascendente
cometimento
33755901f3
2 ficheiros alterados com 87 adições e 29 eliminações
  1. +62
    -11
      src/components/StockTakeManagement/ApproverStockTakeAll.tsx
  2. +25
    -18
      src/components/StockTakeManagement/PickerReStockTake.tsx

+ 62
- 11
src/components/StockTakeManagement/ApproverStockTakeAll.tsx Ver ficheiro

@@ -178,6 +178,35 @@ function buildApproverSaveRequest(
return { ok: true, request, finalQty, finalBadQty, goodQty, selection };
}

function buildBatchApproverSaveRequests(
details: InventoryLotDetailResponse[],
qtySelection: Record<number, QtySelectionType>,
approverQty: Record<number, string>,
approverBadQty: Record<number, string>,
currentUserId: number,
t: (key: string) => string
): SaveApproverStockTakeRecordRequest[] {
const records: SaveApproverStockTakeRecordRequest[] = [];
for (const detail of details) {
if (!detail.stockTakeRecordId || detail.stockTakeRecordId <= 0) {
continue;
}
const built = buildApproverSaveRequest(
detail,
qtySelection,
approverQty,
approverBadQty,
currentUserId,
t
);
if (!built.ok) {
continue;
}
records.push(built.request);
}
return records;
}

function parseDateTimeMs(
v: string | string[] | null | undefined
): number {
@@ -720,12 +749,19 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
};

const isIntegerString = (value: string) => /^\d+$/.test(value);
const batchSaveRecordIds = useMemo(
const batchApproverSaveRecords = useMemo(
() =>
sortedDetails
.map((d) => d.stockTakeRecordId)
.filter((id): id is number => typeof id === "number" && id > 0),
[sortedDetails],
currentUserId
? buildBatchApproverSaveRequests(
sortedDetails,
qtySelection,
approverQty,
approverBadQty,
currentUserId,
t
)
: [],
[sortedDetails, qtySelection, approverQty, approverBadQty, currentUserId, t]
);

const batchSaveSectionLabels = useMemo(() => {
@@ -744,7 +780,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
onSnackbar(t("No rows loaded; set search criteria and search first"), "warning");
return;
}
if (batchSaveRecordIds.length === 0) {
if (batchApproverSaveRecords.length === 0) {
onSnackbar(t("No valid records to batch save"), "warning");
return;
}
@@ -754,7 +790,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
selectedSession,
currentUserId,
inventoryLotDetails.length,
batchSaveRecordIds.length,
batchApproverSaveRecords.length,
onSnackbar,
t,
]);
@@ -763,7 +799,16 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
if (batchSaveInFlightRef.current) return;
if (mode === "approved") return;
if (!selectedSession || !currentUserId) return;
if (batchSaveRecordIds.length === 0) {

const records = buildBatchApproverSaveRequests(
sortedDetails,
qtySelection,
approverQty,
approverBadQty,
currentUserId,
t
);
if (records.length === 0) {
onSnackbar(t("No valid records to batch save"), "warning");
return;
}
@@ -776,7 +821,10 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
const result = await batchSaveApproverStockTakeRecordsByIds({
stockTakeId: selectedSession.stockTakeId,
approverId: currentUserId,
recordIds: batchSaveRecordIds,
recordIds: records
.map((r) => r.stockTakeRecordId)
.filter((id): id is number => typeof id === "number" && id > 0),
records,
});

onSnackbar(
@@ -805,7 +853,10 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
mode,
selectedSession,
currentUserId,
batchSaveRecordIds,
sortedDetails,
qtySelection,
approverQty,
approverBadQty,
t,
onSnackbar,
loadDetails,
@@ -1515,7 +1566,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({
<DialogTitle>{t("Confirm batch save approver")}</DialogTitle>
<DialogContent dividers>
<Typography variant="body2" sx={{ mb: 2 }}>
{t("Batch save confirm message", { count: batchSaveRecordIds.length })}
{t("Batch save confirm message", { count: batchApproverSaveRecords.length })}
</Typography>
{batchSaveSectionLabels.length > 0 ? (
<Box>


+ 25
- 18
src/components/StockTakeManagement/PickerReStockTake.tsx Ver ficheiro

@@ -34,7 +34,11 @@ import PickerBatchSaveFab from "./PickerBatchSaveFab";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import {
OUTPUT_DATE_FORMAT,
sanitizeStockTakeQtyInput,
validateStockTakeQtyString,
} from "@/app/utils/formatUtil";

interface PickerReStockTakeProps {
selectedSession: AllPickedStockTakeListReponse;
@@ -81,11 +85,6 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
e.preventDefault();
}
};
const sanitizeIntegerInput = (value: string) => {
// 只保留数字
return value.replace(/[^\d]/g, "");
};
const isIntegerString = (value: string) => /^\d+$/.test(value);
const handleChangeRowsPerPage = useCallback((event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const newSize = parseInt(event.target.value, 10);
if (newSize === -1) {
@@ -209,28 +208,36 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
return;
}

const totalQty = parseFloat(totalQtyStr);
const badQty = parseFloat(badQtyStr || "0") || 0;

if (Number.isNaN(totalQty)) {
onSnackbar(t("Invalid QTY"), "error");
const totalValidated = validateStockTakeQtyString(totalQtyStr);
if (!totalValidated.ok) {
onSnackbar(t(totalValidated.errorKey), "error");
return;
}
const badValidated = validateStockTakeQtyString(badQtyStr, { allowEmpty: true });
if (!badValidated.ok) {
onSnackbar(t(badValidated.errorKey), "error");
return;
}

const availableQty = totalQty - badQty;
const availableQty = totalValidated.qty - badValidated.qty;

if (availableQty < 0) {
onSnackbar(t("Available QTY cannot be negative"), "error");
return;
}
const availableValidated = validateStockTakeQtyString(String(availableQty));
if (!availableValidated.ok) {
onSnackbar(t(availableValidated.errorKey), "error");
return;
}

setSaving(true);
try {
const request: SaveStockTakeRecordRequest = {
stockTakeRecordId: detail.stockTakeRecordId || null,
inventoryLotLineId: detail.id,
qty: availableQty,
badQty: badQty,
qty: availableValidated.qty,
badQty: badValidated.qty,
remark: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : null,
};
const result = await saveStockTakeRecord(
@@ -536,7 +543,7 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
onKeyDown={blockNonIntegerKeys}
onChange={(e) => {
const clean = sanitizeIntegerInput(e.target.value);
const clean = sanitizeStockTakeQtyInput(e.target.value);
const val = clean;
if (val.includes("-")) return;
setRecordInputs(prev => ({
@@ -562,7 +569,7 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
onKeyDown={blockNonIntegerKeys}
onChange={(e) => {
const clean = sanitizeIntegerInput(e.target.value);
const clean = sanitizeStockTakeQtyInput(e.target.value);
const val = clean;
if (val.includes("-")) return;
setRecordInputs(prev => ({
@@ -606,7 +613,7 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
onKeyDown={blockNonIntegerKeys}
onChange={(e) => {
const clean = sanitizeIntegerInput(e.target.value);
const clean = sanitizeStockTakeQtyInput(e.target.value);
const val = clean;
if (val.includes("-")) return;
setRecordInputs(prev => ({
@@ -632,7 +639,7 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
onKeyDown={blockNonIntegerKeys}
onChange={(e) => {
const clean = sanitizeIntegerInput(e.target.value);
const clean = sanitizeStockTakeQtyInput(e.target.value);
const val = clean;
if (val.includes("-")) return;
setRecordInputs(prev => ({


Carregando…
Cancelar
Guardar