fix(numbering-rule): 채번 미리보기 순번 수정 및 저장 시 재할당 로직 추가
- 미리보기 시 currentSequence + 1로 다음 순번 표시 - UniversalFormModal에서 미리보기/실제할당 분리 - _needsAllocation 플래그로 저장 시 재할당 여부 판단 - RepeatScreenModal 외부 데이터 소스 조인/필터 설정 UI 추가
This commit is contained in:
parent
6a676dcf5c
commit
038c5a0973
|
|
@ -898,9 +898,10 @@ class NumberingRuleService {
|
|||
|
||||
switch (part.partType) {
|
||||
case "sequence": {
|
||||
// 순번 (현재 순번으로 미리보기, 증가 안 함)
|
||||
// 순번 (다음 할당될 순번으로 미리보기, 실제 증가는 allocate 시)
|
||||
const length = autoConfig.sequenceLength || 3;
|
||||
return String(rule.currentSequence || 1).padStart(length, "0");
|
||||
const nextSequence = (rule.currentSequence || 0) + 1;
|
||||
return String(nextSequence).padStart(length, "0");
|
||||
}
|
||||
|
||||
case "number": {
|
||||
|
|
|
|||
|
|
@ -2517,7 +2517,7 @@ function LayoutRowConfigModal({
|
|||
</div>
|
||||
|
||||
{/* 외부 데이터 소스 설정 */}
|
||||
<div className="border rounded p-3 bg-blue-50 space-y-2">
|
||||
<div className="border rounded p-3 bg-blue-50 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-semibold">외부 데이터 소스</Label>
|
||||
<Switch
|
||||
|
|
@ -2529,28 +2529,234 @@ function LayoutRowConfigModal({
|
|||
/>
|
||||
</div>
|
||||
{row.tableDataSource?.enabled && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">소스 테이블</Label>
|
||||
<Select
|
||||
<TableSelector
|
||||
value={row.tableDataSource?.sourceTable || ""}
|
||||
onValueChange={(value) => onUpdateRow({
|
||||
onChange={(value) => onUpdateRow({
|
||||
tableDataSource: { ...row.tableDataSource!, sourceTable: value }
|
||||
})}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-[10px]">
|
||||
<SelectValue placeholder="테이블 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{allTables.map((table) => (
|
||||
<SelectItem key={table.tableName} value={table.tableName}>
|
||||
{table.displayName || table.tableName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 조인 조건 설정 */}
|
||||
<div className="space-y-2 pt-2 border-t border-blue-200">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px] font-semibold">조인 조건</Label>
|
||||
<p className="text-[9px] text-muted-foreground">
|
||||
두 테이블을 연결하는 키를 설정합니다
|
||||
</p>
|
||||
</div>
|
||||
{(row.tableDataSource?.joinConditions || []).map((condition, conditionIndex) => (
|
||||
<div key={`join-${conditionIndex}`} className="space-y-2 p-2 border rounded bg-white">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[9px] font-medium">조인 {conditionIndex + 1}</span>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
const newConditions = [...(row.tableDataSource?.joinConditions || [])];
|
||||
newConditions.splice(conditionIndex, 1);
|
||||
onUpdateRow({
|
||||
tableDataSource: { ...row.tableDataSource!, joinConditions: newConditions }
|
||||
});
|
||||
}}
|
||||
className="h-5 w-5 p-0"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[9px]">조인 키 (소스)</Label>
|
||||
<SourceColumnSelector
|
||||
sourceTable={row.tableDataSource?.sourceTable || ""}
|
||||
value={condition.sourceKey}
|
||||
onChange={(value) => {
|
||||
const newConditions = [...(row.tableDataSource?.joinConditions || [])];
|
||||
newConditions[conditionIndex] = { ...condition, sourceKey: value };
|
||||
onUpdateRow({
|
||||
tableDataSource: { ...row.tableDataSource!, joinConditions: newConditions }
|
||||
});
|
||||
}}
|
||||
placeholder="예: sales_order_id"
|
||||
/>
|
||||
<p className="text-[8px] text-muted-foreground">
|
||||
외부 테이블의 컬럼
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[9px]">조인 키 (대상)</Label>
|
||||
<SourceColumnSelector
|
||||
sourceTable={dataSourceTable || ""}
|
||||
value={condition.referenceKey}
|
||||
onChange={(value) => {
|
||||
const newConditions = [...(row.tableDataSource?.joinConditions || [])];
|
||||
newConditions[conditionIndex] = { ...condition, referenceKey: value };
|
||||
onUpdateRow({
|
||||
tableDataSource: { ...row.tableDataSource!, joinConditions: newConditions }
|
||||
});
|
||||
}}
|
||||
placeholder="예: id"
|
||||
/>
|
||||
<p className="text-[8px] text-muted-foreground">
|
||||
메인 테이블의 컬럼
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-1 p-1.5 bg-blue-50 rounded border border-blue-100">
|
||||
<p className="text-[8px] text-blue-700 font-mono">
|
||||
{row.tableDataSource?.sourceTable}.{condition.sourceKey} = {dataSourceTable}.{condition.referenceKey}
|
||||
</p>
|
||||
<p className="text-[8px] text-muted-foreground mt-0.5">
|
||||
외부 테이블에서 메인 테이블의 값과 일치하는 데이터를 가져옵니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const newConditions = [
|
||||
...(row.tableDataSource?.joinConditions || []),
|
||||
{ sourceKey: "", referenceKey: "", referenceType: "card" as const },
|
||||
];
|
||||
onUpdateRow({
|
||||
tableDataSource: { ...row.tableDataSource!, joinConditions: newConditions }
|
||||
});
|
||||
}}
|
||||
className="w-full h-7 text-[9px]"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
조인 조건 추가
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 필터 설정 */}
|
||||
<div className="space-y-2 pt-2 border-t border-blue-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px] font-semibold">데이터 필터</Label>
|
||||
<p className="text-[9px] text-muted-foreground">
|
||||
특정 조건으로 데이터를 제외합니다
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={row.tableDataSource?.filterConfig?.enabled || false}
|
||||
onCheckedChange={(checked) => {
|
||||
onUpdateRow({
|
||||
tableDataSource: {
|
||||
...row.tableDataSource!,
|
||||
filterConfig: {
|
||||
enabled: checked,
|
||||
filterField: "",
|
||||
filterType: "notEquals",
|
||||
referenceField: "",
|
||||
referenceSource: "representativeData",
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="scale-75"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{row.tableDataSource?.filterConfig?.enabled && (
|
||||
<div className="space-y-2 p-2 bg-amber-50 rounded border border-amber-200">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[9px]">필터 필드</Label>
|
||||
<SourceColumnSelector
|
||||
sourceTable={row.tableDataSource?.sourceTable || ""}
|
||||
value={row.tableDataSource?.filterConfig?.filterField || ""}
|
||||
onChange={(value) => {
|
||||
onUpdateRow({
|
||||
tableDataSource: {
|
||||
...row.tableDataSource!,
|
||||
filterConfig: {
|
||||
...row.tableDataSource!.filterConfig!,
|
||||
filterField: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
placeholder="예: order_no"
|
||||
/>
|
||||
<p className="text-[8px] text-muted-foreground">
|
||||
외부 테이블에서 비교할 컬럼
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[9px]">비교 필드</Label>
|
||||
<SourceColumnSelector
|
||||
sourceTable={dataSourceTable || ""}
|
||||
value={row.tableDataSource?.filterConfig?.referenceField || ""}
|
||||
onChange={(value) => {
|
||||
onUpdateRow({
|
||||
tableDataSource: {
|
||||
...row.tableDataSource!,
|
||||
filterConfig: {
|
||||
...row.tableDataSource!.filterConfig!,
|
||||
referenceField: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
placeholder="예: order_no"
|
||||
/>
|
||||
<p className="text-[8px] text-muted-foreground">
|
||||
현재 선택한 행의 컬럼
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[9px]">필터 조건</Label>
|
||||
<Select
|
||||
value={row.tableDataSource?.filterConfig?.filterType || "notEquals"}
|
||||
onValueChange={(value: "equals" | "notEquals") => {
|
||||
onUpdateRow({
|
||||
tableDataSource: {
|
||||
...row.tableDataSource!,
|
||||
filterConfig: {
|
||||
...row.tableDataSource!.filterConfig!,
|
||||
filterType: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-[10px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="notEquals">같지 않은 값만 (내꺼 제외)</SelectItem>
|
||||
<SelectItem value="equals">같은 값만 (내꺼만)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 p-1.5 bg-amber-100 rounded border border-amber-200">
|
||||
<p className="text-[8px] text-amber-800 font-mono">
|
||||
{row.tableDataSource?.sourceTable}.{row.tableDataSource?.filterConfig?.filterField} != 현재행.{row.tableDataSource?.filterConfig?.referenceField}
|
||||
</p>
|
||||
<p className="text-[8px] text-muted-foreground mt-0.5">
|
||||
{row.tableDataSource?.filterConfig?.filterType === "notEquals"
|
||||
? "현재 선택한 행과 다른 데이터만 표시합니다"
|
||||
: "현재 선택한 행과 같은 데이터만 표시합니다"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -386,10 +386,14 @@ export function UniversalFormModalComponent({
|
|||
!updatedData[field.columnName]
|
||||
) {
|
||||
try {
|
||||
// generateOnOpen: 미리보기만 표시 (실제 순번 할당은 저장 시)
|
||||
const response = await generateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
// 임시 플래그 추가하여 저장 시 실제 순번으로 교체할 것을 표시
|
||||
updatedData[field.columnName] = response.data.generatedCode;
|
||||
updatedData[`_${field.columnName}_needsAllocation`] = true; // 저장 시 재할당 필요
|
||||
hasChanges = true;
|
||||
console.log(`[채번 미리보기] ${field.columnName} = ${response.data.generatedCode} (저장 시 실제 순번으로 교체)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`채번규칙 생성 실패 (${field.columnName}):`, error);
|
||||
|
|
@ -633,18 +637,35 @@ export function UniversalFormModalComponent({
|
|||
for (const section of config.sections) {
|
||||
for (const field of section.fields) {
|
||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||
// generateOnSave: 저장 시 새로 생성
|
||||
// generateOnOpen: 열 때 미리보기로 표시했지만, 저장 시 실제 순번 할당 필요
|
||||
if (field.numberingRule.generateOnSave && !dataToSave[field.columnName]) {
|
||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
dataToSave[field.columnName] = response.data.generatedCode;
|
||||
}
|
||||
} else if (field.numberingRule.generateOnOpen && dataToSave[field.columnName]) {
|
||||
// generateOnOpen인 경우, 미리보기 값이 있더라도 실제 순번 할당
|
||||
console.log(`[채번 체크] ${field.columnName}:`, {
|
||||
enabled: field.numberingRule.enabled,
|
||||
ruleId: field.numberingRule.ruleId,
|
||||
generateOnSave: field.numberingRule.generateOnSave,
|
||||
generateOnOpen: field.numberingRule.generateOnOpen,
|
||||
currentValue: dataToSave[field.columnName],
|
||||
needsAllocation: dataToSave[`_${field.columnName}_needsAllocation`]
|
||||
});
|
||||
|
||||
// generateOnSave: 항상 새로 생성
|
||||
// generateOnOpen: 미리보기 값이 있으면 실제 순번으로 교체
|
||||
const shouldAllocate =
|
||||
field.numberingRule.generateOnSave ||
|
||||
(field.numberingRule.generateOnOpen && dataToSave[`_${field.columnName}_needsAllocation`]);
|
||||
|
||||
console.log(`[채번] shouldAllocate = ${shouldAllocate}`);
|
||||
|
||||
if (shouldAllocate) {
|
||||
console.log(`[채번] allocateNumberingCode 호출 시작: ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`);
|
||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
const oldValue = dataToSave[field.columnName];
|
||||
dataToSave[field.columnName] = response.data.generatedCode;
|
||||
console.log(`[채번 성공] ${field.columnName}: ${oldValue} → ${response.data.generatedCode}`);
|
||||
|
||||
// 임시 플래그 제거
|
||||
delete dataToSave[`_${field.columnName}_needsAllocation`];
|
||||
} else {
|
||||
console.error(`[채번 실패] ${field.columnName}:`, response.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue