fix(orders/new): 첫 클릭에도 재고 초과 경고 즉시 표시 — setCart 콜백 안 warned 변수 제거
Deploy momo-erp / deploy (push) Successful in 1m58s

원인:
- addManyToCart 가 setCart 함수형 업데이트 안에서 외부 변수 warned 에 값 세팅
- React 18 batched updates 로 콜백 실행이 한 박자 늦어 if(warned) 가 false → 첫 클릭 경고 누락
- 두 번째 클릭 때 이미 콜백이 실행돼 warned 가 true 보여 경고 표시 — 사용자가 본 현상

수정:
- 함수형 업데이터 진입 전에 cart 를 동기적으로 읽어 newQty/limit 비교
- 초과면 Swal 띄우고 return — setCart 호출 자체를 안 함 (장바구니 변경 없음)
- 통과 시에만 setCart 로 카트 갱신
- updateQty, setQty 도 동일 패턴(stale-closure 차단도 함수형 업데이터 밖에서)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chpark
2026-05-20 22:08:07 +09:00
parent e3e4919933
commit 0aa8ce9025
+44 -59
View File
@@ -126,22 +126,13 @@ function ItemsBrowse() {
const isDelivery = item.REQUIRES_DELIVERY === "Y";
const effStock = isDelivery ? Number.MAX_SAFE_INTEGER : stock;
const limit = unlimitedQty || maxQ <= 0 ? effStock : Math.min(effStock, maxQ);
let toastTitle = "";
let warned = false;
setCart((c) => {
const found = c.find((x) => x.item.OBJID === item.OBJID);
const newQty = (found?.qty ?? 0) + qty;
if (newQty > limit) {
warned = true;
return c;
}
toastTitle = found
? `수량 +${qty}${newQty}`
: `장바구니에 추가됨: ${item.ITEM_NAME} (${qty}개)`;
if (found) return c.map((x) => x.item.OBJID === item.OBJID ? { ...x, qty: newQty } : x);
return [...c, { item, qty }];
});
if (warned) {
// setCart 함수형 업데이트 안에서 외부 변수에 warned 세팅하면 비동기 타이밍 때문에
// 첫 클릭에는 if(warned) 체크가 한 박자 늦게 동작. 동기 체크로 변경.
const found = cart.find((x) => x.item.OBJID === item.OBJID);
const newQty = (found?.qty ?? 0) + qty;
if (newQty > limit) {
const isStockLimit = maxQ <= 0 || stock <= maxQ;
Swal.fire({
icon: "warning",
@@ -152,60 +143,54 @@ function ItemsBrowse() {
confirmButtonColor: "#0f766e",
confirmButtonText: "확인",
});
return;
return; // 차단 — 장바구니 변경 없음
}
setCart((c) => {
const f = c.find((x) => x.item.OBJID === item.OBJID);
if (f) return c.map((x) => x.item.OBJID === item.OBJID ? { ...x, qty: newQty } : x);
return [...c, { item, qty }];
});
Swal.fire({
toast: true, position: "top-end", icon: "success",
title: toastTitle,
title: found ? `수량 +${qty}${newQty}` : `장바구니에 추가됨: ${item.ITEM_NAME} (${qty}개)`,
showConfirmButton: false, timer: 1000, timerProgressBar: true,
});
};
const updateQty = (objid: string, delta: number) => {
let warnLimit = -1;
let warnIsStock = false;
setCart((c) =>
c.map((x) => {
if (x.item.OBJID !== objid) return x;
const newQty = x.qty + delta;
if (newQty <= 0) return x;
const stock = Number(x.item.STOCK_QTY);
const maxQ = Number(x.item.MAX_ORDER_QTY ?? 0);
const isDelivery = x.item.REQUIRES_DELIVERY === "Y";
const effStock = isDelivery ? Number.MAX_SAFE_INTEGER : stock;
const limit = unlimitedQty || maxQ <= 0 ? effStock : Math.min(effStock, maxQ);
if (newQty > limit) {
warnLimit = limit;
warnIsStock = maxQ <= 0 || stock <= maxQ;
return x;
}
return { ...x, qty: newQty };
})
);
if (warnLimit >= 0) toastLimit(warnLimit, warnIsStock);
const target = cart.find((x) => x.item.OBJID === objid);
if (!target) return;
const newQty = target.qty + delta;
if (newQty <= 0) return;
const stock = Number(target.item.STOCK_QTY);
const maxQ = Number(target.item.MAX_ORDER_QTY ?? 0);
const isDelivery = target.item.REQUIRES_DELIVERY === "Y";
const effStock = isDelivery ? Number.MAX_SAFE_INTEGER : stock;
const limit = unlimitedQty || maxQ <= 0 ? effStock : Math.min(effStock, maxQ);
if (newQty > limit) {
toastLimit(limit, maxQ <= 0 || stock <= maxQ);
return;
}
setCart((c) => c.map((x) => x.item.OBJID === objid ? { ...x, qty: newQty } : x));
};
const setQty = (objid: string, value: number) => {
let warnLimit = -1;
let warnIsStock = false;
setCart((c) =>
c.map((x) => {
if (x.item.OBJID !== objid) return x;
const stock = Number(x.item.STOCK_QTY);
const maxQ = Number(x.item.MAX_ORDER_QTY ?? 0);
const isDelivery = x.item.REQUIRES_DELIVERY === "Y";
const effStock = isDelivery ? Number.MAX_SAFE_INTEGER : stock;
const limit = unlimitedQty || maxQ <= 0 ? effStock : Math.min(effStock, maxQ);
const requested = Math.floor(value || 0);
if (requested > limit) {
warnLimit = limit;
warnIsStock = maxQ <= 0 || stock <= maxQ;
}
const clamped = Math.max(1, Math.min(limit, requested));
return { ...x, qty: clamped };
})
);
if (warnLimit >= 0) toastLimit(warnLimit, warnIsStock);
const target = cart.find((x) => x.item.OBJID === objid);
if (!target) return;
const stock = Number(target.item.STOCK_QTY);
const maxQ = Number(target.item.MAX_ORDER_QTY ?? 0);
const isDelivery = target.item.REQUIRES_DELIVERY === "Y";
const effStock = isDelivery ? Number.MAX_SAFE_INTEGER : stock;
const limit = unlimitedQty || maxQ <= 0 ? effStock : Math.min(effStock, maxQ);
const requested = Math.floor(value || 0);
if (requested > limit) {
toastLimit(limit, maxQ <= 0 || stock <= maxQ);
// 차단 — 기존 수량 유지
return;
}
const clamped = Math.max(1, requested);
setCart((c) => c.map((x) => x.item.OBJID === objid ? { ...x, qty: clamped } : x));
};
const toastLimit = (limit: number, isStockLimit: boolean) => {