Kaynağa Gözat

update

reset-do-picking-order
CANCERYS\kw093 4 gün önce
ebeveyn
işleme
44d51c8390
3 değiştirilmiş dosya ile 141 ekleme ve 24 silme
  1. +12
    -0
      src/app/api/jo/actions.ts
  2. +1
    -0
      src/app/api/pickOrder/actions.ts
  3. +128
    -24
      src/components/Jodetail/newJobPickExecution.tsx

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

@@ -579,6 +579,18 @@ export interface PickOrderLineWithLotsResponse {
status: string | null;
handler: string | null;
lots: LotDetailResponse[];
stockouts?: StockOutLineDetailResponse[];
}

export interface StockOutLineDetailResponse {
id: number | null;
status: string | null;
qty: number | null;
lotId: number | null;
lotNo: string | null;
location: string | null;
availableQty: number | null;
noLot: boolean;
}

export interface LotDetailResponse {


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

@@ -474,6 +474,7 @@ export interface QrPickSubmitLineRequest {
export interface UpdateStockOutLineStatusByQRCodeAndLotNoRequest {
pickOrderLineId: number,
inventoryLotNo: string,
stockInLineId?: number | null,
stockOutLineId: number,
itemId: number,
status: string


+ 128
- 24
src/components/Jodetail/newJobPickExecution.tsx Dosyayı Görüntüle

@@ -516,8 +516,28 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => {
const allLots: any[] = [];
data.pickOrderLines.forEach((line) => {
// 用来记录这一行已经通过 lots 出现过的 lotId(避免 stockouts 再渲染一次)
const lotIdSet = new Set<number>();

// lots:按 lotId 去重并合并 requiredQty(对齐 GoodPickExecutiondetail)
if (line.lots && line.lots.length > 0) {
line.lots.forEach((lot) => {
const lotMap = new Map<number, any>();

line.lots.forEach((lot: any) => {
const lotId = lot.lotId;
if (lotId == null) return;
if (lotMap.has(lotId)) {
const existingLot = lotMap.get(lotId);
existingLot.requiredQty =
(existingLot.requiredQty || 0) + (lot.requiredQty || 0);
} else {
lotMap.set(lotId, { ...lot });
}
});

lotMap.forEach((lot: any) => {
if (lot.lotId != null) lotIdSet.add(lot.lotId);

allLots.push({
...lot,
pickOrderLineId: line.id,
@@ -530,7 +550,6 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => {
pickOrderLineStatus: line.status,
jobOrderId: data.pickOrder.jobOrder.id,
jobOrderCode: data.pickOrder.jobOrder.code,
// 添加 pickOrder 信息(如果需要)
pickOrderId: data.pickOrder.id,
pickOrderCode: data.pickOrder.code,
pickOrderConsoCode: data.pickOrder.consoCode,
@@ -539,6 +558,60 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => {
pickOrderStatus: data.pickOrder.status,
pickOrderAssignTo: data.pickOrder.assignTo,
handler: line.handler,
noLot: false,
});
});
}

// stockouts:用于“无 suggested lot / noLot”场景也显示并可 submit 0 闭环
if (line.stockouts && line.stockouts.length > 0) {
line.stockouts.forEach((stockout: any) => {
const hasLot = stockout.lotId != null;
const lotAlreadyInLots = hasLot && lotIdSet.has(stockout.lotId as number);

// 有批次 & 已经通过 lots 渲染过 → 跳过,避免一条变两行
if (!stockout.noLot && lotAlreadyInLots) {
return;
}

allLots.push({
pickOrderLineId: line.id,
itemId: line.itemId,
itemCode: line.itemCode,
itemName: line.itemName,
uomCode: line.uomCode,
uomDesc: line.uomDesc,
pickOrderLineRequiredQty: line.requiredQty,
pickOrderLineStatus: line.status,
jobOrderId: data.pickOrder.jobOrder.id,
jobOrderCode: data.pickOrder.jobOrder.code,
pickOrderId: data.pickOrder.id,
pickOrderCode: data.pickOrder.code,
pickOrderConsoCode: data.pickOrder.consoCode,
pickOrderTargetDate: data.pickOrder.targetDate,
pickOrderType: data.pickOrder.type,
pickOrderStatus: data.pickOrder.status,
pickOrderAssignTo: data.pickOrder.assignTo,
handler: line.handler,

lotId: stockout.lotId || null,
lotNo: stockout.lotNo || null,
expiryDate: null,
location: stockout.location || null,
availableQty: stockout.availableQty ?? 0,
requiredQty: line.requiredQty ?? 0,
actualPickQty: stockout.qty ?? 0,
processingStatus: stockout.status || "pending",
lotAvailability: stockout.noLot ? "insufficient_stock" : "available",
suggestedPickLotId: null,
stockOutLineId: stockout.id || null,
stockOutLineQty: stockout.qty ?? 0,
stockOutLineStatus: stockout.status || null,
stockInLineId: null,
routerIndex: stockout.noLot ? 999999 : null,
routerArea: null,
routerRoute: null,
noLot: !!stockout.noLot,
});
});
}
@@ -1011,30 +1084,57 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => {
console.log("Scanned Lot No:", scannedLotData.lotNo);
console.log("Scanned StockInLineId:", scannedLotData.stockInLineId);

// Call confirmLotSubstitution to update the suggested lot
console.log("🔄 [LOT CONFIRM] Calling confirmLotSubstitution...");
const substitutionResult = await confirmLotSubstitution({
pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId,
originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId,
newInventoryLotNo: scannedLotData.lotNo || '',
// ✅ required by LotSubstitutionConfirmRequest
newStockInLineId: scannedLotData?.stockInLineId ?? null,
});
const originalSuggestedPickLotId =
selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId;

// noLot / missing suggestedPickLotId 场景:没有 originalSuggestedPickLotId,改用 updateStockOutLineStatusByQRCodeAndLotNo
if (!originalSuggestedPickLotId) {
if (!selectedLotForQr?.stockOutLineId) {
throw new Error("Missing stockOutLineId for noLot line");
}
console.log("🔄 [LOT CONFIRM] No originalSuggestedPickLotId, using updateStockOutLineStatusByQRCodeAndLotNo...");
const res = await updateStockOutLineStatusByQRCodeAndLotNo({
pickOrderLineId: selectedLotForQr.pickOrderLineId,
inventoryLotNo: scannedLotData.lotNo || '',
stockInLineId: scannedLotData?.stockInLineId ?? null,
stockOutLineId: selectedLotForQr.stockOutLineId,
itemId: selectedLotForQr.itemId,
status: "checked",
});
console.log("✅ [LOT CONFIRM] updateStockOutLineStatusByQRCodeAndLotNo result:", res);
const ok = res?.code === "checked" || res?.code === "SUCCESS";
if (!ok) {
setQrScanError(true);
setQrScanSuccess(false);
setQrScanErrorMsg(res?.message || "换批失败:无法更新 stock out line");
return;
}
} else {
// Call confirmLotSubstitution to update the suggested lot
console.log("🔄 [LOT CONFIRM] Calling confirmLotSubstitution...");
const substitutionResult = await confirmLotSubstitution({
pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId,
originalSuggestedPickLotId,
newInventoryLotNo: scannedLotData.lotNo || '',
// ✅ required by LotSubstitutionConfirmRequest
newStockInLineId: scannedLotData?.stockInLineId ?? null,
});
console.log("✅ [LOT CONFIRM] Lot substitution result:", substitutionResult);
console.log("✅ [LOT CONFIRM] Lot substitution result:", substitutionResult);

// ✅ CRITICAL: substitution failed => DO NOT mark original stockOutLine as checked.
// Keep modal open so user can cancel/rescan.
if (!substitutionResult || substitutionResult.code !== "SUCCESS") {
console.error("❌ [LOT CONFIRM] Lot substitution failed. Will NOT update stockOutLine status.");
setQrScanError(true);
setQrScanSuccess(false);
setQrScanErrorMsg(
substitutionResult?.message ||
`换批失败:stockInLineId ${scannedLotData?.stockInLineId ?? ""} 不存在或无法匹配`
);
return;
// ✅ CRITICAL: substitution failed => DO NOT mark original stockOutLine as checked.
// Keep modal open so user can cancel/rescan.
if (!substitutionResult || substitutionResult.code !== "SUCCESS") {
console.error("❌ [LOT CONFIRM] Lot substitution failed. Will NOT update stockOutLine status.");
setQrScanError(true);
setQrScanSuccess(false);
setQrScanErrorMsg(
substitutionResult?.message ||
`换批失败:stockInLineId ${scannedLotData?.stockInLineId ?? ""} 不存在或无法匹配`
);
return;
}
}
// Update stock out line status to 'checked' after substitution
@@ -1280,6 +1380,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => {
const res = await updateStockOutLineStatusByQRCodeAndLotNo({
pickOrderLineId: exactMatch.pickOrderLineId,
inventoryLotNo: exactMatch.lotNo,
stockInLineId: exactMatch.stockInLineId ?? null,
stockOutLineId: exactMatch.stockOutLineId,
itemId: exactMatch.itemId,
status: "checked",
@@ -2152,6 +2253,9 @@ const sortedData = [...sourceData].sort((a, b) => {
};
}, [isManualScanning, stopScan, resetScan]);
const getStatusMessage = useCallback((lot: any) => {
if (lot?.noLot === true || lot?.lotAvailability === 'insufficient_stock') {
return t("This order is insufficient, please pick another lot.");
}
switch (lot.stockOutLineStatus?.toLowerCase()) {
case 'pending':
return t("Please finish QR code scan and pick order.");


Yükleniyor…
İptal
Kaydet