Переглянути джерело

update jo edit and jo compelte reocrd search

MergeProblem1
CANCERYS\kw093 12 години тому
джерело
коміт
e1a0f56b80
3 змінених файлів з 193 додано та 44 видалено
  1. +50
    -2
      src/app/api/jo/actions.ts
  2. +1
    -3
      src/components/Jodetail/JobPickExecutionForm.tsx
  3. +142
    -39
      src/components/Jodetail/newJobPickExecution.tsx

+ 50
- 2
src/app/api/jo/actions.ts Переглянути файл

@@ -1138,11 +1138,59 @@ export const fetchJos = cache(async (data?: SearchJoResultRequest) => {
}
}
)
console.log("fetchJos response:", response)
// console.log("fetchJos response:", response)
return response
})

export interface PostPickOrderResponse<T = null> {
id: number | null;
name: string;
code: string;
type?: string;
message: string | null;
errorPosition: string
entity?: T | T[];
consoCode?: string;
}
export interface PickExecutionIssueData {
type: string;
pickOrderId: number;
pickOrderCode: string;
pickOrderCreateDate: string;
pickExecutionDate: string;
pickOrderLineId: number;
itemId: number;
itemCode: string;
itemDescription: string;
lotId: number|null;
lotNo: string|null;
storeLocation: string;
requiredQty: number;
actualPickQty: number;
missQty: number;
badItemQty: number;
badPackageQty?: number;
/** Optional: frontend-only reference to stock_out_line.id for the picked lot. */
stockOutLineId?: number;
issueRemark: string;
pickerName: string;
handledBy?: number;
badReason?: string;
reason?: string;
}
/** 无 miss/bad/bad package:仅后端 hold + SOL checked,不写 pick_execution_issue(避免 DUPLICATE)。 */
export const applyPickExecutionHoldAndChecked = async (data: PickExecutionIssueData) => {
const result = await serverFetchJson<PostPickOrderResponse>(
`${BASE_API_URL}/pickExecution/applyHoldAndChecked`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);
revalidateTag("pickorder");
return result;
};
export const updateJo = cache(async (data: UpdateJoRequest) => {
return serverFetchJson<SaveJoResponse>(`${BASE_API_URL}/jo/update`,
{


+ 1
- 3
src/components/Jodetail/JobPickExecutionForm.tsx Переглянути файл

@@ -204,6 +204,7 @@ useEffect(() => {
const total = ap + miss + totalBadQty;
const availableQty = selectedLot?.availableQty || 0;
const maxActual = requiredQty + availableQty;
// 1. Check actualPickQty cannot be negative
if (ap < 0) {
newErrors.actualPickQty = t("Qty cannot be negative");
@@ -321,9 +322,6 @@ useEffect(() => {

const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot);
const requiredQty = calculateRequiredQty(selectedLot);
const availableQty = selectedLot?.availableQty || 0;
const maxActual = requiredQty + availableQty;
return (
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle>


+ 142
- 39
src/components/Jodetail/newJobPickExecution.tsx Переглянути файл

@@ -27,6 +27,7 @@ import {
updateStockOutLineStatus,
createStockOutLine,
recordPickExecutionIssue,
//applyPickExecutionHoldAndChecked,
fetchFGPickOrders,
FGPickOrderResponse,
autoAssignAndReleasePickOrder,
@@ -46,6 +47,7 @@ import {
fetchJobOrderLotsHierarchicalByPickOrderId,
updateJoPickOrderHandledBy,
JobOrderLotsHierarchicalResponse,
applyPickExecutionHoldAndChecked
} from "@/app/api/jo/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";
import {
@@ -2403,32 +2405,55 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => {
console.log("Pick execution form opened for lot ID:", lot.lotId);
}, []);

const handlePickExecutionFormSubmit = useCallback(async (data: any) => {
try {
if (currentUserId && selectedLotForExecutionForm?.pickOrderId && selectedLotForExecutionForm?.itemId) {
try {
await updateHandledBy(selectedLotForExecutionForm.pickOrderId, selectedLotForExecutionForm.itemId);
console.log(`✅ [ISSUE FORM] Handler updated for itemId ${selectedLotForExecutionForm.itemId}`);
} catch (error) {
console.error(`❌ [ISSUE FORM] Error updating handler (non-critical):`, error);
const handlePickExecutionFormSubmit = useCallback(
async (data: any) => {
const lotSnap = selectedLotForExecutionForm;
const pickOrderIdEarly =
filterArgs?.pickOrderId
? Number(filterArgs.pickOrderId)
: Number(lotSnap?.pickOrderId || 0) || undefined;
try {
if (currentUserId && lotSnap?.pickOrderId && lotSnap?.itemId) {
try {
await updateHandledBy(lotSnap.pickOrderId, lotSnap.itemId);
console.log(`✅ [ISSUE FORM] Handler updated for itemId ${lotSnap.itemId}`);
} catch (error) {
console.error(`❌ [ISSUE FORM] Error updating handler (non-critical):`, error);
}
}
}
console.log("Pick execution form submitted:", data);
const issueData = {
...data,
type: "Jo", // Delivery Order Record 类型
pickerName: session?.user?.name || undefined,
handledBy: currentUserId || undefined,
};
const result = await recordPickExecutionIssue(issueData);
console.log("Pick execution issue recorded:", result);
if (result && result.code === "SUCCESS") {
console.log(" Pick execution issue recorded successfully");
console.log("Pick execution form submitted:", data);
const issueData = {
...data,
type: "Jo",
pickerName: session?.user?.name || undefined,
handledBy: currentUserId || undefined,
};
const missN = Number(issueData.missQty ?? 0) || 0;
const badN = Number(issueData.badItemQty ?? 0) || 0;
const badPkgN = Number(issueData.badPackageQty ?? 0) || 0;
const useHoldOnlyApi = missN === 0 && badN === 0 && badPkgN === 0;
const result = useHoldOnlyApi
? await applyPickExecutionHoldAndChecked(issueData)
: await recordPickExecutionIssue(issueData);
console.log(
useHoldOnlyApi ? "Pick hold/checked applied:" : "Pick execution issue recorded:",
result,
);
if (!result || result.code !== "SUCCESS") {
console.error("❌ Pick execution submit failed:", result);
throw new Error(result?.message || "Submit failed");
}
const solId = Number(issueData.stockOutLineId || data?.stockOutLineId);
const picked = Number(issueData.actualPickQty ?? 0);
if (solId > 0) {
const picked = Number(issueData.actualPickQty || 0);
setIssuePickedQtyBySolId((prev) => {
const next = { ...prev, [solId]: picked };
const pid = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined;
@@ -2436,23 +2461,101 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => {
return next;
});
}
} else {
console.error("❌ Failed to record pick execution issue:", result);
// Hold-only:與整批相同規則,只送一筆 batch,把實揈/完成狀態寫回 DB
if (useHoldOnlyApi && pickOrderIdEarly && solId > 0) {
const freshData = await fetchJobOrderLotsHierarchicalByPickOrderId(pickOrderIdEarly);
const flatLots = getAllLotsFromHierarchical(freshData);
const lotRow = flatLots.find((l: any) => Number(l.stockOutLineId) === solId);
if (!lotRow) {
throw new Error("Could not find lot row after refresh for batch submit");
}
const requiredQty = Number(lotRow.requiredQty || lotRow.pickOrderLineRequiredQty || 0);
const issuePickedVal = picked;
const currentActualPickQty = Number(issuePickedVal ?? lotRow.actualPickQty ?? 0);
const onlyComplete =
lotRow.stockOutLineStatus === "partially_completed" ||
lotRow.stockOutLineStatus === "PARTIALLY_COMPLETE" ||
issuePickedVal !== undefined;
const expired = isLotAvailabilityExpired(lotRow);
const unavailable = isInventoryLotLineUnavailable(lotRow);
let targetActual: number;
let newStatus: string;
if (unavailable) {
targetActual = currentActualPickQty;
newStatus = "completed";
} else if (expired && issuePickedVal === undefined) {
targetActual = 0;
newStatus = "completed";
} else if (onlyComplete) {
targetActual = currentActualPickQty;
newStatus = "completed";
} else {
const remainingQty = Math.max(0, requiredQty - currentActualPickQty);
const cumulativeQty = currentActualPickQty + remainingQty;
targetActual = cumulativeQty;
newStatus = "partially_completed";
if (requiredQty > 0 && cumulativeQty >= requiredQty) {
newStatus = "completed";
}
}
const line: batchSubmitListLineRequest = {
stockOutLineId: solId,
pickOrderLineId: Number(lotRow.pickOrderLineId),
inventoryLotLineId: lotRow.lotId ? Number(lotRow.lotId) : null,
requiredQty,
actualPickQty: targetActual,
stockOutLineStatus: newStatus,
pickOrderConsoCode: String(lotRow.pickOrderConsoCode || ""),
noLot: Boolean(lotRow.noLot === true),
};
const batchResult = await batchSubmitList({
userId: currentUserId || 0,
lines: [line],
});
if (!batchResult || batchResult.code !== "SUCCESS") {
throw new Error(batchResult?.message || "Batch submit failed after hold adjustment");
}
const conso = String(lotRow.pickOrderConsoCode || "").trim();
if (conso) {
try {
await checkAndCompletePickOrderByConsoCode(conso);
} catch (e) {
console.error("❌ completion check after single batch:", e);
}
}
}
setPickExecutionFormOpen(false);
setSelectedLotForExecutionForm(null);
await fetchJobOrderData(pickOrderIdEarly);
} catch (error) {
console.error("Error submitting pick execution form:", error);
throw error;
}
setPickExecutionFormOpen(false);
setSelectedLotForExecutionForm(null);
const pickOrderId =
filterArgs?.pickOrderId
? Number(filterArgs.pickOrderId)
: Number(selectedLotForExecutionForm?.pickOrderId || 0) || undefined;
await fetchJobOrderData(pickOrderId);
} catch (error) {
console.error("Error submitting pick execution form:", error);
}
}, [fetchJobOrderData, currentUserId, selectedLotForExecutionForm, updateHandledBy, filterArgs?.pickOrderId, filterArgs]);

},
[
fetchJobOrderData,
getAllLotsFromHierarchical,
currentUserId,
selectedLotForExecutionForm,
updateHandledBy,
filterArgs,
session?.user?.name,
batchSubmitList,
checkAndCompletePickOrderByConsoCode,
isLotAvailabilityExpired,
isInventoryLotLineUnavailable,
],
);
// Calculate remaining required quantity
const calculateRemainingRequiredQty = useCallback((lot: any) => {
const requiredQty = lot.requiredQty || 0;


Завантаження…
Відмінити
Зберегти