폼 조건별 계산식 설정기능
This commit is contained in:
parent
fff10a1911
commit
417e1d297b
|
|
@ -16,7 +16,13 @@ import { ItemSelectionModal } from "../modal-repeater-table/ItemSelectionModal";
|
|||
import { RepeaterColumnConfig, CalculationRule } from "../modal-repeater-table/types";
|
||||
|
||||
// 타입 정의
|
||||
import { TableSectionConfig, TableColumnConfig, TableJoinCondition, FormDataState } from "./types";
|
||||
import {
|
||||
TableSectionConfig,
|
||||
TableColumnConfig,
|
||||
TableJoinCondition,
|
||||
FormDataState,
|
||||
TableCalculationRule,
|
||||
} from "./types";
|
||||
|
||||
interface TableSectionRendererProps {
|
||||
sectionId: string;
|
||||
|
|
@ -811,39 +817,69 @@ export function TableSectionRenderer({
|
|||
});
|
||||
}, [tableConfig.columns, dynamicSelectOptionsMap]);
|
||||
|
||||
// 계산 규칙 변환
|
||||
const calculationRules: CalculationRule[] = (tableConfig.calculations || []).map(convertToCalculationRule);
|
||||
// 원본 계산 규칙 (조건부 계산 포함)
|
||||
const originalCalculationRules: TableCalculationRule[] = useMemo(
|
||||
() => tableConfig.calculations || [],
|
||||
[tableConfig.calculations],
|
||||
);
|
||||
|
||||
// 계산 로직
|
||||
// 기본 계산 규칙 변환 (RepeaterTable용 - 조건부 계산이 없는 경우에 사용)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const calculationRules: CalculationRule[] = originalCalculationRules.map(convertToCalculationRule);
|
||||
|
||||
// 조건부 계산 로직: 행의 조건 필드 값에 따라 적절한 계산식 선택
|
||||
const getFormulaForRow = useCallback((rule: TableCalculationRule, row: Record<string, unknown>): string => {
|
||||
// 조건부 계산이 활성화된 경우
|
||||
if (rule.conditionalCalculation?.enabled && rule.conditionalCalculation.conditionField) {
|
||||
const conditionValue = row[rule.conditionalCalculation.conditionField];
|
||||
// 조건값과 일치하는 규칙 찾기
|
||||
const matchedRule = rule.conditionalCalculation.rules?.find((r) => r.conditionValue === conditionValue);
|
||||
if (matchedRule) {
|
||||
return matchedRule.formula;
|
||||
}
|
||||
// 일치하는 규칙이 없으면 기본 계산식 사용
|
||||
if (rule.conditionalCalculation.defaultFormula) {
|
||||
return rule.conditionalCalculation.defaultFormula;
|
||||
}
|
||||
}
|
||||
// 조건부 계산이 비활성화되었거나 기본값이 없으면 원래 계산식 사용
|
||||
return rule.formula;
|
||||
}, []);
|
||||
|
||||
// 계산 로직 (조건부 계산 지원)
|
||||
const calculateRow = useCallback(
|
||||
(row: any): any => {
|
||||
if (calculationRules.length === 0) return row;
|
||||
if (originalCalculationRules.length === 0) return row;
|
||||
|
||||
const updatedRow = { ...row };
|
||||
|
||||
for (const rule of calculationRules) {
|
||||
for (const rule of originalCalculationRules) {
|
||||
try {
|
||||
let formula = rule.formula;
|
||||
// 조건부 계산에 따라 적절한 계산식 선택
|
||||
let formula = getFormulaForRow(rule, row);
|
||||
|
||||
if (!formula) continue;
|
||||
|
||||
const fieldMatches = formula.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || [];
|
||||
const dependencies = rule.dependencies.length > 0 ? rule.dependencies : fieldMatches;
|
||||
|
||||
for (const dep of dependencies) {
|
||||
if (dep === rule.result) continue;
|
||||
if (dep === rule.resultField) continue;
|
||||
const value = parseFloat(row[dep]) || 0;
|
||||
formula = formula.replace(new RegExp(`\\b${dep}\\b`, "g"), value.toString());
|
||||
}
|
||||
|
||||
const result = new Function(`return ${formula}`)();
|
||||
updatedRow[rule.result] = result;
|
||||
updatedRow[rule.resultField] = result;
|
||||
} catch (error) {
|
||||
console.error(`계산 오류 (${rule.formula}):`, error);
|
||||
updatedRow[rule.result] = 0;
|
||||
updatedRow[rule.resultField] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return updatedRow;
|
||||
},
|
||||
[calculationRules],
|
||||
[originalCalculationRules, getFormulaForRow],
|
||||
);
|
||||
|
||||
const calculateAll = useCallback(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import {
|
|||
TablePreFilter,
|
||||
TableModalFilter,
|
||||
TableCalculationRule,
|
||||
ConditionalCalculationRule,
|
||||
ConditionalCalculationConfig,
|
||||
LookupOption,
|
||||
LookupCondition,
|
||||
ConditionalTableOption,
|
||||
|
|
@ -52,6 +54,429 @@ const HelpText = ({ children }: { children: React.ReactNode }) => (
|
|||
<p className="text-[10px] text-muted-foreground mt-0.5">{children}</p>
|
||||
);
|
||||
|
||||
// 계산 규칙 편집 컴포넌트 (조건부 계산 지원)
|
||||
interface CalculationRuleEditorProps {
|
||||
calc: TableCalculationRule;
|
||||
index: number;
|
||||
columns: TableColumnConfig[];
|
||||
sourceTableName?: string; // 소스 테이블명 추가
|
||||
onUpdate: (updates: Partial<TableCalculationRule>) => void;
|
||||
onRemove: () => void;
|
||||
}
|
||||
|
||||
const CalculationRuleEditor: React.FC<CalculationRuleEditorProps> = ({
|
||||
calc,
|
||||
index,
|
||||
columns,
|
||||
sourceTableName,
|
||||
onUpdate,
|
||||
onRemove,
|
||||
}) => {
|
||||
const [categoryOptions, setCategoryOptions] = useState<{ value: string; label: string }[]>([]);
|
||||
const [loadingOptions, setLoadingOptions] = useState(false);
|
||||
const [categoryColumns, setCategoryColumns] = useState<Record<string, boolean>>({});
|
||||
|
||||
// 조건부 계산 활성화 여부
|
||||
const isConditionalEnabled = calc.conditionalCalculation?.enabled ?? false;
|
||||
|
||||
// 소스 테이블의 카테고리 컬럼 정보 로드
|
||||
useEffect(() => {
|
||||
const loadCategoryColumns = async () => {
|
||||
console.log("[CalculationRuleEditor] sourceTableName:", sourceTableName);
|
||||
|
||||
if (!sourceTableName) {
|
||||
setCategoryColumns({});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { getCategoryColumns } = await import("@/lib/api/tableCategoryValue");
|
||||
const result = await getCategoryColumns(sourceTableName);
|
||||
console.log("[CalculationRuleEditor] getCategoryColumns 결과:", result);
|
||||
|
||||
if (result && result.success && Array.isArray(result.data)) {
|
||||
const categoryMap: Record<string, boolean> = {};
|
||||
result.data.forEach((col: any) => {
|
||||
// API 응답은 camelCase (columnName)
|
||||
const colName = col.columnName || col.column_name;
|
||||
if (colName) {
|
||||
categoryMap[colName] = true;
|
||||
}
|
||||
});
|
||||
console.log("[CalculationRuleEditor] categoryMap:", categoryMap);
|
||||
setCategoryColumns(categoryMap);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("카테고리 컬럼 조회 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadCategoryColumns();
|
||||
}, [sourceTableName]);
|
||||
|
||||
// 조건 필드가 선택되었을 때 옵션 로드 (테이블 타입 관리의 카테고리 기준)
|
||||
useEffect(() => {
|
||||
const loadConditionOptions = async () => {
|
||||
if (!isConditionalEnabled || !calc.conditionalCalculation?.conditionField) {
|
||||
setCategoryOptions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const conditionField = calc.conditionalCalculation.conditionField;
|
||||
|
||||
// 소스 필드(sourceField)가 있으면 해당 필드명 사용, 없으면 field명 사용
|
||||
const selectedColumn = columns.find((col) => col.field === conditionField);
|
||||
const actualFieldName = selectedColumn?.sourceField || conditionField;
|
||||
|
||||
console.log("[loadConditionOptions] 조건 필드:", {
|
||||
conditionField,
|
||||
actualFieldName,
|
||||
sourceTableName,
|
||||
categoryColumnsKeys: Object.keys(categoryColumns),
|
||||
isCategoryColumn: categoryColumns[actualFieldName],
|
||||
});
|
||||
|
||||
// 소스 테이블에서 해당 컬럼이 카테고리 타입인지 확인
|
||||
if (sourceTableName && categoryColumns[actualFieldName]) {
|
||||
try {
|
||||
setLoadingOptions(true);
|
||||
const { getCategoryValues } = await import("@/lib/api/tableCategoryValue");
|
||||
console.log("[loadConditionOptions] getCategoryValues 호출:", sourceTableName, actualFieldName);
|
||||
const result = await getCategoryValues(sourceTableName, actualFieldName, false);
|
||||
console.log("[loadConditionOptions] getCategoryValues 결과:", result);
|
||||
if (result && result.success && Array.isArray(result.data)) {
|
||||
const options = result.data.map((item: any) => ({
|
||||
// API 응답은 camelCase (valueCode, valueLabel)
|
||||
value: item.valueCode || item.value_code || item.value,
|
||||
label: item.valueLabel || item.displayLabel || item.display_label || item.label || item.valueCode || item.value_code || item.value,
|
||||
}));
|
||||
console.log("[loadConditionOptions] 매핑된 옵션:", options);
|
||||
setCategoryOptions(options);
|
||||
} else {
|
||||
setCategoryOptions([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("카테고리 값 로드 실패:", error);
|
||||
setCategoryOptions([]);
|
||||
} finally {
|
||||
setLoadingOptions(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 카테고리 키가 직접 설정된 경우 (저장된 값)
|
||||
const categoryKey = calc.conditionalCalculation?.conditionFieldCategoryKey;
|
||||
if (categoryKey) {
|
||||
try {
|
||||
setLoadingOptions(true);
|
||||
const [tableName, columnName] = categoryKey.split(".");
|
||||
if (tableName && columnName) {
|
||||
const { getCategoryValues } = await import("@/lib/api/tableCategoryValue");
|
||||
const result = await getCategoryValues(tableName, columnName, false);
|
||||
if (result && result.success && Array.isArray(result.data)) {
|
||||
setCategoryOptions(
|
||||
result.data.map((item: any) => ({
|
||||
// API 응답은 camelCase (valueCode, valueLabel)
|
||||
value: item.valueCode || item.value_code || item.value,
|
||||
label: item.valueLabel || item.displayLabel || item.display_label || item.label || item.valueCode || item.value_code || item.value,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("카테고리 옵션 로드 실패:", error);
|
||||
} finally {
|
||||
setLoadingOptions(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 그 외 타입은 옵션 없음 (직접 입력)
|
||||
setCategoryOptions([]);
|
||||
};
|
||||
|
||||
loadConditionOptions();
|
||||
}, [isConditionalEnabled, calc.conditionalCalculation?.conditionField, calc.conditionalCalculation?.conditionFieldCategoryKey, columns, sourceTableName, categoryColumns]);
|
||||
|
||||
// 조건부 계산 토글
|
||||
const toggleConditionalCalculation = (enabled: boolean) => {
|
||||
onUpdate({
|
||||
conditionalCalculation: enabled
|
||||
? {
|
||||
enabled: true,
|
||||
conditionField: "",
|
||||
conditionFieldType: "static",
|
||||
rules: [],
|
||||
defaultFormula: calc.formula || "",
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
// 조건 필드 변경
|
||||
const updateConditionField = (field: string) => {
|
||||
const selectedColumn = columns.find((col) => col.field === field);
|
||||
const actualFieldName = selectedColumn?.sourceField || field;
|
||||
|
||||
// 컬럼의 타입과 옵션 확인 (테이블 타입 관리의 카테고리 기준)
|
||||
let conditionFieldType: "static" | "code" | "table" = "static";
|
||||
let conditionFieldCategoryKey = "";
|
||||
|
||||
// 소스 테이블에서 해당 컬럼이 카테고리 타입인지 확인
|
||||
if (sourceTableName && categoryColumns[actualFieldName]) {
|
||||
conditionFieldType = "code";
|
||||
conditionFieldCategoryKey = `${sourceTableName}.${actualFieldName}`;
|
||||
}
|
||||
|
||||
onUpdate({
|
||||
conditionalCalculation: {
|
||||
...calc.conditionalCalculation!,
|
||||
conditionField: field,
|
||||
conditionFieldType,
|
||||
conditionFieldCategoryKey,
|
||||
rules: [], // 필드 변경 시 규칙 초기화
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 조건 규칙 추가
|
||||
const addConditionRule = () => {
|
||||
const newRule: ConditionalCalculationRule = {
|
||||
conditionValue: "",
|
||||
formula: calc.formula || "",
|
||||
};
|
||||
onUpdate({
|
||||
conditionalCalculation: {
|
||||
...calc.conditionalCalculation!,
|
||||
rules: [...(calc.conditionalCalculation?.rules || []), newRule],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 조건 규칙 업데이트
|
||||
const updateConditionRule = (ruleIndex: number, updates: Partial<ConditionalCalculationRule>) => {
|
||||
const newRules = [...(calc.conditionalCalculation?.rules || [])];
|
||||
newRules[ruleIndex] = { ...newRules[ruleIndex], ...updates };
|
||||
onUpdate({
|
||||
conditionalCalculation: {
|
||||
...calc.conditionalCalculation!,
|
||||
rules: newRules,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 조건 규칙 삭제
|
||||
const removeConditionRule = (ruleIndex: number) => {
|
||||
onUpdate({
|
||||
conditionalCalculation: {
|
||||
...calc.conditionalCalculation!,
|
||||
rules: (calc.conditionalCalculation?.rules || []).filter((_, i) => i !== ruleIndex),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 기본 계산식 업데이트
|
||||
const updateDefaultFormula = (formula: string) => {
|
||||
onUpdate({
|
||||
conditionalCalculation: {
|
||||
...calc.conditionalCalculation!,
|
||||
defaultFormula: formula,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 조건 필드로 사용 가능한 컬럼 (모든 컬럼)
|
||||
const availableColumns = columns.filter((col) => col.field);
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg p-3 bg-muted/30 space-y-3">
|
||||
{/* 기본 계산 규칙 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
value={calc.resultField || ""}
|
||||
onValueChange={(value) => onUpdate({ resultField: value })}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs w-[150px]">
|
||||
<SelectValue placeholder="결과 필드 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{columns.length === 0 ? (
|
||||
<SelectItem value="__no_columns__" disabled>
|
||||
컬럼 설정에서 먼저 컬럼을 추가하세요
|
||||
</SelectItem>
|
||||
) : (
|
||||
columns
|
||||
.filter((col) => col.field)
|
||||
.map((col, idx) => (
|
||||
<SelectItem key={col.field || `col_${idx}`} value={col.field}>
|
||||
{col.label || col.field}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-xs text-muted-foreground">=</span>
|
||||
<Input
|
||||
value={calc.formula}
|
||||
onChange={(e) => onUpdate({ formula: e.target.value })}
|
||||
placeholder="수식 (예: qty * unit_price)"
|
||||
className="h-8 text-xs flex-1"
|
||||
disabled={isConditionalEnabled}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={onRemove}
|
||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 조건부 계산 토글 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
id={`conditional-calc-${index}`}
|
||||
checked={isConditionalEnabled}
|
||||
onCheckedChange={toggleConditionalCalculation}
|
||||
className="scale-75"
|
||||
/>
|
||||
<Label htmlFor={`conditional-calc-${index}`} className="text-xs cursor-pointer">
|
||||
조건부 계산 활성화
|
||||
</Label>
|
||||
{availableColumns.length === 0 && !isConditionalEnabled && (
|
||||
<span className="text-[10px] text-muted-foreground ml-2">
|
||||
(컬럼 설정에서 먼저 컬럼을 추가하세요)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 조건부 계산 설정 */}
|
||||
{isConditionalEnabled && (
|
||||
<div className="border-t pt-3 space-y-3">
|
||||
{/* 조건 필드 선택 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-xs w-[80px] shrink-0">조건 필드:</Label>
|
||||
<Select
|
||||
value={calc.conditionalCalculation?.conditionField || ""}
|
||||
onValueChange={updateConditionField}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs flex-1">
|
||||
<SelectValue placeholder="조건 기준 컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableColumns.length === 0 ? (
|
||||
<SelectItem value="__no_columns__" disabled>
|
||||
컬럼이 없습니다
|
||||
</SelectItem>
|
||||
) : (
|
||||
availableColumns.map((col, idx) => {
|
||||
// 소스 필드명으로 카테고리 여부 확인
|
||||
const actualFieldName = col.sourceField || col.field;
|
||||
const isCategoryColumn = categoryColumns[actualFieldName];
|
||||
return (
|
||||
<SelectItem key={col.field || `col_${idx}`} value={col.field}>
|
||||
{col.label || col.field} {isCategoryColumn ? "(카테고리)" : `(${col.type})`}
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 조건별 계산식 목록 */}
|
||||
{calc.conditionalCalculation?.conditionField && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs">조건별 계산식:</Label>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={addConditionRule}
|
||||
className="h-6 text-[10px] px-2"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
조건 추가
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{(calc.conditionalCalculation?.rules || []).map((rule, ruleIndex) => (
|
||||
<div key={ruleIndex} className="flex items-center gap-2 bg-background rounded p-2">
|
||||
{/* 조건값 선택 */}
|
||||
{categoryOptions.length > 0 ? (
|
||||
<Select
|
||||
value={rule.conditionValue}
|
||||
onValueChange={(value) =>
|
||||
updateConditionRule(ruleIndex, { conditionValue: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs w-[120px]">
|
||||
<SelectValue placeholder="조건값" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categoryOptions.map((opt, optIdx) => (
|
||||
<SelectItem key={`${opt.value}_${optIdx}`} value={opt.value}>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<Input
|
||||
value={rule.conditionValue}
|
||||
onChange={(e) =>
|
||||
updateConditionRule(ruleIndex, { conditionValue: e.target.value })
|
||||
}
|
||||
placeholder="조건값"
|
||||
className="h-7 text-xs w-[120px]"
|
||||
/>
|
||||
)}
|
||||
<span className="text-xs text-muted-foreground">→</span>
|
||||
<Input
|
||||
value={rule.formula}
|
||||
onChange={(e) =>
|
||||
updateConditionRule(ruleIndex, { formula: e.target.value })
|
||||
}
|
||||
placeholder="계산식"
|
||||
className="h-7 text-xs flex-1"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => removeConditionRule(ruleIndex)}
|
||||
className="h-7 w-7 p-0 text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 기본 계산식 */}
|
||||
<div className="flex items-center gap-2 bg-background/50 rounded p-2 border-dashed border">
|
||||
<span className="text-xs text-muted-foreground w-[120px] text-center">
|
||||
(기본값)
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">→</span>
|
||||
<Input
|
||||
value={calc.conditionalCalculation?.defaultFormula || ""}
|
||||
onChange={(e) => updateDefaultFormula(e.target.value)}
|
||||
placeholder="기본 계산식 (조건 미해당 시)"
|
||||
className="h-7 text-xs flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loadingOptions && (
|
||||
<p className="text-[10px] text-muted-foreground">옵션 로딩 중...</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 옵션 소스 설정 컴포넌트 (검색 가능한 Combobox)
|
||||
interface OptionSourceConfigProps {
|
||||
optionSource: {
|
||||
|
|
@ -3034,46 +3459,15 @@ export function TableSectionSettingsModal({
|
|||
</div>
|
||||
|
||||
{(tableConfig.calculations || []).map((calc, index) => (
|
||||
<div key={index} className="flex items-center gap-2 border rounded-lg p-2 bg-muted/30">
|
||||
<Select
|
||||
value={calc.resultField || ""}
|
||||
onValueChange={(value) => updateCalculation(index, { resultField: value })}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs w-[150px]">
|
||||
<SelectValue placeholder="결과 필드 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(tableConfig.columns || []).length === 0 ? (
|
||||
<SelectItem value="__no_columns__" disabled>
|
||||
컬럼 설정에서 먼저 컬럼을 추가하세요
|
||||
</SelectItem>
|
||||
) : (
|
||||
(tableConfig.columns || [])
|
||||
.filter((col) => col.field) // 빈 필드명 제외
|
||||
.map((col, idx) => (
|
||||
<SelectItem key={col.field || `col_${idx}`} value={col.field}>
|
||||
{col.label || col.field}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-xs text-muted-foreground">=</span>
|
||||
<Input
|
||||
value={calc.formula}
|
||||
onChange={(e) => updateCalculation(index, { formula: e.target.value })}
|
||||
placeholder="수식 (예: quantity * unit_price)"
|
||||
className="h-8 text-xs flex-1"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => removeCalculation(index)}
|
||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<CalculationRuleEditor
|
||||
key={index}
|
||||
calc={calc}
|
||||
index={index}
|
||||
columns={tableConfig.columns || []}
|
||||
sourceTableName={tableConfig.source?.tableName}
|
||||
onUpdate={(updates) => updateCalculation(index, updates)}
|
||||
onRemove={() => removeCalculation(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -604,6 +604,27 @@ export interface ColumnModeConfig {
|
|||
valueMapping: ValueMappingConfig; // 이 모드의 값 매핑
|
||||
}
|
||||
|
||||
/**
|
||||
* 조건부 계산 규칙
|
||||
* 특정 필드 값에 따라 다른 계산식 적용
|
||||
*/
|
||||
export interface ConditionalCalculationRule {
|
||||
conditionValue: string; // 조건 값 (예: "국내", "해외")
|
||||
formula: string; // 해당 조건일 때 사용할 계산식
|
||||
}
|
||||
|
||||
/**
|
||||
* 조건부 계산 설정
|
||||
*/
|
||||
export interface ConditionalCalculationConfig {
|
||||
enabled: boolean; // 조건부 계산 활성화 여부
|
||||
conditionField: string; // 조건 기준 필드 (예: "sales_type")
|
||||
conditionFieldType?: "static" | "code" | "table"; // 조건 필드의 옵션 타입
|
||||
conditionFieldCategoryKey?: string; // 카테고리 키 (예: "sales_order_mng.sales_type")
|
||||
rules: ConditionalCalculationRule[]; // 조건별 계산 규칙
|
||||
defaultFormula?: string; // 조건에 해당하지 않을 때 기본 계산식
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 계산 규칙
|
||||
* 다른 컬럼 값을 기반으로 자동 계산
|
||||
|
|
@ -612,6 +633,9 @@ export interface TableCalculationRule {
|
|||
resultField: string; // 결과를 저장할 필드
|
||||
formula: string; // 계산 공식 (예: "quantity * unit_price")
|
||||
dependencies: string[]; // 의존하는 필드들
|
||||
|
||||
// 조건부 계산 (선택사항)
|
||||
conditionalCalculation?: ConditionalCalculationConfig;
|
||||
}
|
||||
|
||||
// 다중 행 저장 설정
|
||||
|
|
|
|||
Loading…
Reference in New Issue