fix: 구매입고 확정 시 품목 NULL + 채번 실패 수정
- popActionRoutes.ts: autoGenMappings 대상 컬럼을 columnMapping에서 제외 - 근본 원인: autoGen 컬럼이 빈값으로 선점되어 채번 SKIP - pop-button.tsx, PopCardListComponent.tsx: 관련 프론트엔드 수정
This commit is contained in:
parent
4fa8c3969d
commit
76a708cab2
|
|
@ -183,6 +183,15 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
|||
const cardMapping = mappings?.cardList;
|
||||
const fieldMapping = mappings?.field;
|
||||
|
||||
logger.info("[pop/execute-action] data-save 분기 판정", {
|
||||
hasCardMapping: !!cardMapping?.targetTable,
|
||||
cardMappingColumns: cardMapping?.columnMapping ? Object.keys(cardMapping.columnMapping).length : 0,
|
||||
itemCount: items.length,
|
||||
hasFieldMapping: !!fieldMapping?.targetTable,
|
||||
fieldMappingColumns: fieldMapping?.columnMapping ? Object.keys(fieldMapping.columnMapping).length : 0,
|
||||
fieldValueKeys: Object.keys(fieldValues),
|
||||
});
|
||||
|
||||
if (cardMapping?.targetTable && Object.keys(cardMapping.columnMapping).length > 0 && items.length > 0) {
|
||||
// ── 카드리스트 기반 INSERT (기존: items 반복) ──
|
||||
if (!isSafeIdentifier(cardMapping.targetTable)) {
|
||||
|
|
@ -214,12 +223,18 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
|||
}
|
||||
}
|
||||
|
||||
// autoGen 대상 컬럼은 필드/히든 매핑에서 제외 (빈값으로 덮어쓰기 방지)
|
||||
const autoGenTargetColumns = new Set(
|
||||
allAutoGen.filter(ag => ag.numberingRuleId && ag.targetColumn).map(ag => ag.targetColumn)
|
||||
);
|
||||
|
||||
for (const item of items) {
|
||||
const columns: string[] = ["company_code"];
|
||||
const values: unknown[] = [companyCode];
|
||||
|
||||
for (const [sourceField, targetColumn] of Object.entries(cardMapping.columnMapping)) {
|
||||
if (!isSafeIdentifier(targetColumn)) continue;
|
||||
if (autoGenTargetColumns.has(targetColumn)) continue;
|
||||
columns.push(`"${targetColumn}"`);
|
||||
values.push(item[sourceField] ?? null);
|
||||
}
|
||||
|
|
@ -228,6 +243,7 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
|||
for (const [sourceField, targetColumn] of Object.entries(fieldMapping.columnMapping)) {
|
||||
if (!isSafeIdentifier(targetColumn)) continue;
|
||||
if (columns.includes(`"${targetColumn}"`)) continue;
|
||||
if (autoGenTargetColumns.has(targetColumn)) continue;
|
||||
columns.push(`"${targetColumn}"`);
|
||||
values.push(fieldValues[sourceField] ?? null);
|
||||
}
|
||||
|
|
@ -315,9 +331,15 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
|||
const columns: string[] = ["company_code"];
|
||||
const values: unknown[] = [companyCode];
|
||||
|
||||
// autoGen 대상 컬럼은 필드 매핑에서 제외 (빈값으로 덮어쓰기 방지)
|
||||
const fieldAutoGenCols = new Set(
|
||||
(fieldMapping.autoGenMappings ?? []).filter(ag => ag.numberingRuleId && ag.targetColumn).map(ag => ag.targetColumn)
|
||||
);
|
||||
|
||||
// 필드 매핑 값 추가
|
||||
for (const [sourceField, targetColumn] of Object.entries(fieldMapping.columnMapping)) {
|
||||
if (!isSafeIdentifier(targetColumn)) continue;
|
||||
if (fieldAutoGenCols.has(targetColumn)) continue;
|
||||
columns.push(`"${targetColumn}"`);
|
||||
values.push(fieldValues[sourceField] ?? null);
|
||||
}
|
||||
|
|
@ -638,12 +660,18 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
|||
}
|
||||
}
|
||||
|
||||
// autoGen 대상 컬럼은 필드/히든 매핑에서 제외 (빈값으로 덮어쓰기 방지)
|
||||
const autoGenTargetColumns = new Set(
|
||||
allAutoGen.filter(ag => ag.numberingRuleId && ag.targetColumn).map(ag => ag.targetColumn)
|
||||
);
|
||||
|
||||
for (const item of items) {
|
||||
const columns: string[] = ["company_code"];
|
||||
const values: unknown[] = [companyCode];
|
||||
|
||||
for (const [sourceField, targetColumn] of Object.entries(cardMapping.columnMapping)) {
|
||||
if (!isSafeIdentifier(targetColumn)) continue;
|
||||
if (autoGenTargetColumns.has(targetColumn)) continue;
|
||||
columns.push(`"${targetColumn}"`);
|
||||
values.push(item[sourceField] ?? null);
|
||||
}
|
||||
|
|
@ -652,6 +680,7 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
|||
for (const [sourceField, targetColumn] of Object.entries(fieldMapping.columnMapping)) {
|
||||
if (!isSafeIdentifier(targetColumn)) continue;
|
||||
if (columns.includes(`"${targetColumn}"`)) continue;
|
||||
if (autoGenTargetColumns.has(targetColumn)) continue;
|
||||
columns.push(`"${targetColumn}"`);
|
||||
values.push(fieldValues[sourceField] ?? null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -654,7 +654,7 @@ export function PopButtonComponent({ config, label, isDesignMode, screenId, comp
|
|||
// 장바구니 모드 상태 (v1 preset 또는 v2 tasks에 cart-save가 있으면 활성)
|
||||
// showCartBadge: true인 경우에도 활성화 (cart-save 없이 배지만 표시할 때)
|
||||
const v2Tasks = (config && "tasks" in config && Array.isArray((config as any).tasks))
|
||||
? (config as any).tasks as PopButtonTask[]
|
||||
? (config as any).tasks as ButtonTask[]
|
||||
: null;
|
||||
const hasCartSaveTask = !!v2Tasks?.some((t) => t.type === "cart-save");
|
||||
const isCartMode = config?.preset === "cart" || hasCartSaveTask || !!(config as any)?.showCartBadge;
|
||||
|
|
@ -798,11 +798,18 @@ export function PopButtonComponent({ config, label, isDesignMode, screenId, comp
|
|||
cartSaveTimeoutRef.current = setTimeout(() => {
|
||||
setCartSaving((prev) => {
|
||||
if (prev) {
|
||||
toast.error("장바구니 저장 응답이 없습니다. 연결 설정을 확인하세요.");
|
||||
// 이벤트 응답이 없지만 저장 대상 화면이 있으면 네비게이션 시도 (폴백)
|
||||
const targetScreenId = cartScreenIdRef.current;
|
||||
if (targetScreenId) {
|
||||
const cleanId = String(targetScreenId).replace(/^.*\/(\d+)$/, "$1").trim();
|
||||
window.location.href = `/pop/screens/${cleanId}`;
|
||||
} else {
|
||||
toast.error("장바구니 저장 응답이 없습니다. 연결 설정을 확인하세요.");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}, 10_000);
|
||||
}, 5_000);
|
||||
}, [componentId, publish, config?.cart?.rowDataMode, config?.cart?.selectedColumns]);
|
||||
|
||||
// 저장 완료 시 타임아웃 정리
|
||||
|
|
@ -943,8 +950,8 @@ export function PopButtonComponent({ config, label, isDesignMode, screenId, comp
|
|||
items: cardListData?.data?.items ?? [],
|
||||
fieldValues: fieldData?.data?.values ?? {},
|
||||
mappings: {
|
||||
cardList: cardListData?.mapping ?? null,
|
||||
field: fieldData?.mapping ?? null,
|
||||
cardList: (cardListData?.mapping ?? null) as Record<string, unknown> | null | undefined,
|
||||
field: (fieldData?.mapping ?? null) as Record<string, unknown> | null | undefined,
|
||||
},
|
||||
cartChanges: (cardListData?.data as Record<string, unknown>)?.cartChanges as CollectedPayload["cartChanges"],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -527,7 +527,7 @@ export function PopCardListComponent({
|
|||
try {
|
||||
// 원본 화면 레이아웃에서 설정 전체 상속 (cardTemplate, inputField, packageConfig, cardSize 등)
|
||||
try {
|
||||
const layoutJson = await screenApi.getLayoutPop(cartListMode.sourceScreenId);
|
||||
const layoutJson = await screenApi.getLayoutPop(cartListMode.sourceScreenId!);
|
||||
const componentsMap = layoutJson?.components || {};
|
||||
const componentList = Object.values(componentsMap) as any[];
|
||||
const matched = cartListMode.sourceComponentId
|
||||
|
|
@ -694,7 +694,9 @@ export function PopCardListComponent({
|
|||
const request = (payload as Record<string, unknown>)?.value as CollectDataRequest | undefined;
|
||||
|
||||
const selectedItems = isCartListMode
|
||||
? filteredRows.filter(r => selectedKeys.has(String(r.__cart_id ?? "")))
|
||||
? (selectedKeys.size > 0
|
||||
? filteredRows.filter(r => selectedKeys.has(String(r.__cart_id ?? "")))
|
||||
: filteredRows)
|
||||
: rows;
|
||||
|
||||
// CardListSaveMapping → SaveMapping 변환
|
||||
|
|
@ -716,7 +718,7 @@ export function PopCardListComponent({
|
|||
requestId: request?.requestId ?? "",
|
||||
componentId: componentId,
|
||||
componentType: "pop-card-list",
|
||||
data: { items: selectedItems, cartChanges },
|
||||
data: { items: selectedItems, cartChanges } as any,
|
||||
mapping,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue