jskim-node #406
|
|
@ -233,8 +233,35 @@ export async function createMoldSerial(req: AuthenticatedRequest, res: Response)
|
|||
const { moldCode } = req.params;
|
||||
const { serial_number, status, progress, work_description, manager, completion_date, remarks } = req.body;
|
||||
|
||||
if (!serial_number) {
|
||||
res.status(400).json({ success: false, message: "일련번호는 필수입니다." });
|
||||
let finalSerialNumber = serial_number;
|
||||
|
||||
// 일련번호가 비어있으면 채번 규칙으로 자동 생성
|
||||
if (!finalSerialNumber) {
|
||||
try {
|
||||
const { numberingRuleService } = await import("../services/numberingRuleService");
|
||||
const rule = await numberingRuleService.getNumberingRuleByColumn(
|
||||
companyCode,
|
||||
"mold_serial",
|
||||
"serial_number"
|
||||
);
|
||||
|
||||
if (rule) {
|
||||
// formData에 mold_code를 포함 (reference 파트에서 참조)
|
||||
const formData = { mold_code: moldCode, ...req.body };
|
||||
finalSerialNumber = await numberingRuleService.allocateCode(
|
||||
rule.ruleId,
|
||||
companyCode,
|
||||
formData
|
||||
);
|
||||
logger.info("일련번호 자동 채번 완료", { serialNumber: finalSerialNumber, ruleId: rule.ruleId });
|
||||
}
|
||||
} catch (numError: any) {
|
||||
logger.error("일련번호 자동 채번 실패", { error: numError.message });
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalSerialNumber) {
|
||||
res.status(400).json({ success: false, message: "일련번호를 생성할 수 없습니다. 채번 규칙을 확인해주세요." });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -244,7 +271,7 @@ export async function createMoldSerial(req: AuthenticatedRequest, res: Response)
|
|||
RETURNING *
|
||||
`;
|
||||
const params = [
|
||||
companyCode, moldCode, serial_number, status || "STORED",
|
||||
companyCode, moldCode, finalSerialNumber, status || "STORED",
|
||||
progress || 0, work_description || null, manager || null,
|
||||
completion_date || null, remarks || null, userId,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -172,6 +172,16 @@ class NumberingRuleService {
|
|||
break;
|
||||
}
|
||||
|
||||
case "reference": {
|
||||
const refColumn = autoConfig.referenceColumnName;
|
||||
if (refColumn && formData && formData[refColumn]) {
|
||||
prefixParts.push(String(formData[refColumn]));
|
||||
} else {
|
||||
prefixParts.push("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -1245,6 +1255,14 @@ class NumberingRuleService {
|
|||
return "";
|
||||
}
|
||||
|
||||
case "reference": {
|
||||
const refColumn = autoConfig.referenceColumnName;
|
||||
if (refColumn && formData && formData[refColumn]) {
|
||||
return String(formData[refColumn]);
|
||||
}
|
||||
return "REF";
|
||||
}
|
||||
|
||||
default:
|
||||
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
||||
return "";
|
||||
|
|
@ -1375,6 +1393,13 @@ class NumberingRuleService {
|
|||
|
||||
return catMapping2?.format || "CATEGORY";
|
||||
}
|
||||
case "reference": {
|
||||
const refCol2 = autoConfig.referenceColumnName;
|
||||
if (refCol2 && formData && formData[refCol2]) {
|
||||
return String(formData[refCol2]);
|
||||
}
|
||||
return "REF";
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
@ -1524,6 +1549,15 @@ class NumberingRuleService {
|
|||
return "";
|
||||
}
|
||||
|
||||
case "reference": {
|
||||
const refColumn = autoConfig.referenceColumnName;
|
||||
if (refColumn && formData && formData[refColumn]) {
|
||||
return String(formData[refColumn]);
|
||||
}
|
||||
logger.warn("reference 파트: 참조 컬럼 값 없음", { refColumn, formDataKeys: formData ? Object.keys(formData) : [] });
|
||||
return "";
|
||||
}
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2691,6 +2691,32 @@ export class TableManagementService {
|
|||
logger.info(`created_date 자동 추가: ${data.created_date}`);
|
||||
}
|
||||
|
||||
// 채번 자동 적용: input_type = 'numbering'인 컬럼에 값이 비어있으면 자동 채번
|
||||
try {
|
||||
const companyCode = data.company_code || "*";
|
||||
const numberingColsResult = await query<any>(
|
||||
`SELECT DISTINCT column_name FROM table_type_columns
|
||||
WHERE table_name = $1 AND input_type = 'numbering'
|
||||
AND company_code IN ($2, '*')`,
|
||||
[tableName, companyCode]
|
||||
);
|
||||
|
||||
for (const row of numberingColsResult) {
|
||||
const col = row.column_name;
|
||||
if (!data[col] || data[col] === "" || data[col] === "자동 생성됩니다") {
|
||||
const { numberingRuleService } = await import("./numberingRuleService");
|
||||
const rule = await numberingRuleService.getNumberingRuleByColumn(companyCode, tableName, col);
|
||||
if (rule) {
|
||||
const generatedCode = await numberingRuleService.allocateCode(rule.ruleId, companyCode, data);
|
||||
data[col] = generatedCode;
|
||||
logger.info(`채번 자동 적용: ${tableName}.${col} = ${generatedCode}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (numErr: any) {
|
||||
logger.warn(`채번 자동 적용 중 오류 (무시됨): ${numErr.message}`);
|
||||
}
|
||||
|
||||
// 🆕 테이블에 존재하는 컬럼만 필터링 (존재하지 않는 컬럼은 무시)
|
||||
const skippedColumns: string[] = [];
|
||||
const existingColumns = Object.keys(data).filter((col) => {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ interface AutoConfigPanelProps {
|
|||
config?: any;
|
||||
onChange: (config: any) => void;
|
||||
isPreview?: boolean;
|
||||
tableName?: string;
|
||||
}
|
||||
|
||||
interface TableInfo {
|
||||
|
|
@ -37,6 +38,7 @@ export const AutoConfigPanel: React.FC<AutoConfigPanelProps> = ({
|
|||
config = {},
|
||||
onChange,
|
||||
isPreview = false,
|
||||
tableName,
|
||||
}) => {
|
||||
// 1. 순번 (자동 증가)
|
||||
if (partType === "sequence") {
|
||||
|
|
@ -161,6 +163,18 @@ export const AutoConfigPanel: React.FC<AutoConfigPanelProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
// 6. 참조 (마스터-디테일 분번)
|
||||
if (partType === "reference") {
|
||||
return (
|
||||
<ReferenceConfigSection
|
||||
config={config}
|
||||
onChange={onChange}
|
||||
isPreview={isPreview}
|
||||
tableName={tableName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
@ -1088,3 +1102,94 @@ const CategoryConfigPanel: React.FC<CategoryConfigPanelProps> = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function ReferenceConfigSection({
|
||||
config,
|
||||
onChange,
|
||||
isPreview,
|
||||
tableName,
|
||||
}: {
|
||||
config: any;
|
||||
onChange: (c: any) => void;
|
||||
isPreview: boolean;
|
||||
tableName?: string;
|
||||
}) {
|
||||
const [columns, setColumns] = useState<ColumnInfo[]>([]);
|
||||
const [loadingCols, setLoadingCols] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tableName) return;
|
||||
setLoadingCols(true);
|
||||
|
||||
const loadEntityColumns = async () => {
|
||||
try {
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
const response = await apiClient.get(
|
||||
`/screen-management/tables/${tableName}/columns`
|
||||
);
|
||||
const allCols = response.data?.data || response.data || [];
|
||||
const entityCols = allCols.filter(
|
||||
(c: any) =>
|
||||
(c.inputType || c.input_type) === "entity" ||
|
||||
(c.inputType || c.input_type) === "numbering"
|
||||
);
|
||||
setColumns(
|
||||
entityCols.map((c: any) => ({
|
||||
columnName: c.columnName || c.column_name,
|
||||
displayName:
|
||||
c.columnLabel || c.column_label || c.columnName || c.column_name,
|
||||
dataType: c.dataType || c.data_type || "",
|
||||
inputType: c.inputType || c.input_type || "",
|
||||
}))
|
||||
);
|
||||
} catch {
|
||||
setColumns([]);
|
||||
} finally {
|
||||
setLoadingCols(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadEntityColumns();
|
||||
}, [tableName]);
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label className="text-xs font-medium sm:text-sm">참조 컬럼</Label>
|
||||
<Select
|
||||
value={config.referenceColumnName || ""}
|
||||
onValueChange={(value) =>
|
||||
onChange({ ...config, referenceColumnName: value })
|
||||
}
|
||||
disabled={isPreview || loadingCols}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
loadingCols
|
||||
? "로딩 중..."
|
||||
: columns.length === 0
|
||||
? "엔티티 컬럼 없음"
|
||||
: "컬럼 선택"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{columns.map((col) => (
|
||||
<SelectItem
|
||||
key={col.columnName}
|
||||
value={col.columnName}
|
||||
className="text-xs"
|
||||
>
|
||||
{col.displayName} ({col.columnName})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground sm:text-xs">
|
||||
마스터 테이블과 연결된 엔티티/채번 컬럼의 값을 코드에 포함합니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface NumberingRuleCardProps {
|
|||
onUpdate: (updates: Partial<NumberingRulePart>) => void;
|
||||
onDelete: () => void;
|
||||
isPreview?: boolean;
|
||||
tableName?: string;
|
||||
}
|
||||
|
||||
export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
|
||||
|
|
@ -23,6 +24,7 @@ export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
|
|||
onUpdate,
|
||||
onDelete,
|
||||
isPreview = false,
|
||||
tableName,
|
||||
}) => {
|
||||
return (
|
||||
<Card className="border-border bg-card flex-1">
|
||||
|
|
@ -57,6 +59,7 @@ export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
|
|||
date: { dateFormat: "YYYYMMDD" },
|
||||
text: { textValue: "CODE" },
|
||||
category: { categoryKey: "", categoryMappings: [] },
|
||||
reference: { referenceColumnName: "" },
|
||||
};
|
||||
onUpdate({
|
||||
partType: newPartType,
|
||||
|
|
@ -105,6 +108,7 @@ export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
|
|||
config={part.autoConfig}
|
||||
onChange={(autoConfig) => onUpdate({ autoConfig })}
|
||||
isPreview={isPreview}
|
||||
tableName={tableName}
|
||||
/>
|
||||
) : (
|
||||
<ManualConfigPanel
|
||||
|
|
|
|||
|
|
@ -460,6 +460,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
|||
onUpdate={(updates) => handleUpdatePart(part.order, updates)}
|
||||
onDelete={() => handleDeletePart(part.order)}
|
||||
isPreview={isPreview}
|
||||
tableName={selectedColumn?.tableName}
|
||||
/>
|
||||
{/* 카드 하단에 구분자 설정 (마지막 파트 제외) */}
|
||||
{index < currentRule.parts.length - 1 && (
|
||||
|
|
|
|||
|
|
@ -619,45 +619,40 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
|
|||
try {
|
||||
// 채번 규칙 ID 캐싱 (한 번만 조회)
|
||||
if (!numberingRuleIdRef.current) {
|
||||
const { getTableColumns } = await import("@/lib/api/tableManagement");
|
||||
const columnsResponse = await getTableColumns(tableName);
|
||||
// table_name + column_name 기반으로 채번 규칙 조회
|
||||
try {
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
const ruleResponse = await apiClient.get(`/numbering-rules/by-column/${tableName}/${columnName}`);
|
||||
if (ruleResponse.data?.success && ruleResponse.data?.data?.ruleId) {
|
||||
numberingRuleIdRef.current = ruleResponse.data.data.ruleId;
|
||||
|
||||
if (!columnsResponse.success || !columnsResponse.data) {
|
||||
console.warn("테이블 컬럼 정보 조회 실패:", columnsResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = columnsResponse.data.columns || columnsResponse.data;
|
||||
const targetColumn = columns.find((col: { columnName: string }) => col.columnName === columnName);
|
||||
|
||||
if (!targetColumn) {
|
||||
console.warn("컬럼 정보를 찾을 수 없습니다:", columnName);
|
||||
return;
|
||||
}
|
||||
|
||||
// detailSettings에서 numberingRuleId 추출
|
||||
if (targetColumn.detailSettings) {
|
||||
try {
|
||||
// 문자열이면 파싱, 객체면 그대로 사용
|
||||
const parsed = typeof targetColumn.detailSettings === "string"
|
||||
? JSON.parse(targetColumn.detailSettings)
|
||||
: targetColumn.detailSettings;
|
||||
numberingRuleIdRef.current = parsed.numberingRuleId || null;
|
||||
|
||||
// 🆕 채번 규칙 ID를 formData에 저장 (저장 시 allocateCode 호출을 위해)
|
||||
if (parsed.numberingRuleId && onFormDataChange && columnName) {
|
||||
onFormDataChange(`${columnName}_numberingRuleId`, parsed.numberingRuleId);
|
||||
if (onFormDataChange && columnName) {
|
||||
onFormDataChange(`${columnName}_numberingRuleId`, ruleResponse.data.data.ruleId);
|
||||
}
|
||||
} catch {
|
||||
// JSON 파싱 실패
|
||||
}
|
||||
} catch {
|
||||
// by-column 조회 실패 시 detailSettings fallback
|
||||
try {
|
||||
const { getTableColumns } = await import("@/lib/api/tableManagement");
|
||||
const columnsResponse = await getTableColumns(tableName);
|
||||
if (columnsResponse.success && columnsResponse.data) {
|
||||
const columns = columnsResponse.data.columns || columnsResponse.data;
|
||||
const targetColumn = columns.find((col: { columnName: string }) => col.columnName === columnName);
|
||||
if (targetColumn?.detailSettings) {
|
||||
const parsed = typeof targetColumn.detailSettings === "string"
|
||||
? JSON.parse(targetColumn.detailSettings)
|
||||
: targetColumn.detailSettings;
|
||||
numberingRuleIdRef.current = parsed.numberingRuleId || null;
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
const numberingRuleId = numberingRuleIdRef.current;
|
||||
|
||||
if (!numberingRuleId) {
|
||||
console.warn("채번 규칙 ID가 설정되지 않았습니다. 테이블 관리에서 설정하세요.", { tableName, columnName });
|
||||
console.warn("채번 규칙을 찾을 수 없습니다. 옵션설정 > 채번설정에서 규칙을 생성하세요.", { tableName, columnName });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,14 +35,16 @@ export const StatusCountComponent: React.FC<StatusCountComponentProps> = ({
|
|||
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await apiClient.get(`/table-management/data/${tableName}`, {
|
||||
params: {
|
||||
autoFilter: "true",
|
||||
[relationColumn]: parentValue,
|
||||
},
|
||||
const res = await apiClient.post(`/table-management/tables/${tableName}/data`, {
|
||||
page: 1,
|
||||
size: 9999,
|
||||
search: relationColumn ? { [relationColumn]: parentValue } : {},
|
||||
});
|
||||
|
||||
const rows: any[] = res.data?.data || res.data?.rows || res.data || [];
|
||||
const responseData = res.data?.data;
|
||||
const rows: any[] = Array.isArray(responseData)
|
||||
? responseData
|
||||
: (responseData?.data || responseData?.rows || []);
|
||||
const grouped: Record<string, number> = {};
|
||||
|
||||
for (const row of rows) {
|
||||
|
|
@ -69,7 +71,7 @@ export const StatusCountComponent: React.FC<StatusCountComponentProps> = ({
|
|||
};
|
||||
|
||||
const getCount = (item: StatusCountItem) => {
|
||||
if (item.value === "__TOTAL__") {
|
||||
if (item.value === "__TOTAL__" || item.value === "__ALL__") {
|
||||
return Object.values(counts).reduce((sum, c) => sum + c, 0);
|
||||
}
|
||||
const values = item.value.split(",").map((v) => v.trim());
|
||||
|
|
|
|||
|
|
@ -233,6 +233,47 @@ export const StatusCountConfigPanel: React.FC<StatusCountConfigPanelProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
// 상태 컬럼의 카테고리 값 로드
|
||||
const [statusCategoryValues, setStatusCategoryValues] = useState<Array<{ value: string; label: string }>>([]);
|
||||
const [loadingCategoryValues, setLoadingCategoryValues] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!config.tableName || !config.statusColumn) {
|
||||
setStatusCategoryValues([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const loadCategoryValues = async () => {
|
||||
setLoadingCategoryValues(true);
|
||||
try {
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
const response = await apiClient.get(
|
||||
`/table-categories/${config.tableName}/${config.statusColumn}/values`
|
||||
);
|
||||
if (response.data?.success && response.data?.data) {
|
||||
const flatValues: Array<{ value: string; label: string }> = [];
|
||||
const flatten = (items: any[]) => {
|
||||
for (const item of items) {
|
||||
flatValues.push({
|
||||
value: item.valueCode || item.value_code,
|
||||
label: item.valueLabel || item.value_label,
|
||||
});
|
||||
if (item.children?.length > 0) flatten(item.children);
|
||||
}
|
||||
};
|
||||
flatten(response.data.data);
|
||||
setStatusCategoryValues(flatValues);
|
||||
}
|
||||
} catch {
|
||||
setStatusCategoryValues([]);
|
||||
} finally {
|
||||
setLoadingCategoryValues(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadCategoryValues();
|
||||
}, [config.tableName, config.statusColumn]);
|
||||
|
||||
const tableComboItems = tables.map((t) => ({
|
||||
value: t.tableName,
|
||||
label: t.displayName,
|
||||
|
|
@ -370,15 +411,52 @@ export const StatusCountConfigPanel: React.FC<StatusCountConfigPanelProps> = ({
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
{loadingCategoryValues && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Loader2 className="h-3 w-3 animate-spin" /> 카테고리 값 로딩...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{items.map((item: StatusCountItem, i: number) => (
|
||||
<div key={i} className="space-y-1 rounded-md border p-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
value={item.value}
|
||||
onChange={(e) => handleItemChange(i, "value", e.target.value)}
|
||||
placeholder="상태값 (예: IN_USE)"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
{statusCategoryValues.length > 0 ? (
|
||||
<Select
|
||||
value={item.value || ""}
|
||||
onValueChange={(v) => {
|
||||
handleItemChange(i, "value", v);
|
||||
if (v === "__ALL__" && !item.label) {
|
||||
handleItemChange(i, "label", "전체");
|
||||
} else {
|
||||
const catVal = statusCategoryValues.find((cv) => cv.value === v);
|
||||
if (catVal && !item.label) {
|
||||
handleItemChange(i, "label", catVal.label);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 flex-1 text-xs">
|
||||
<SelectValue placeholder="카테고리 값 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__ALL__" className="text-xs font-medium">
|
||||
전체
|
||||
</SelectItem>
|
||||
{statusCategoryValues.map((cv) => (
|
||||
<SelectItem key={cv.value} value={cv.value} className="text-xs">
|
||||
{cv.label} ({cv.value})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<Input
|
||||
value={item.value}
|
||||
onChange={(e) => handleItemChange(i, "value", e.target.value)}
|
||||
placeholder="상태값 (예: IN_USE)"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
|
@ -418,6 +496,12 @@ export const StatusCountConfigPanel: React.FC<StatusCountConfigPanelProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{!loadingCategoryValues && statusCategoryValues.length === 0 && config.tableName && config.statusColumn && (
|
||||
<p className="text-[10px] text-amber-600">
|
||||
카테고리 값이 없습니다. 옵션설정 > 카테고리설정에서 값을 추가하거나 직접 입력하세요.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@
|
|||
* 코드 파트 유형 (5가지)
|
||||
*/
|
||||
export type CodePartType =
|
||||
| "sequence" // 순번 (자동 증가 숫자)
|
||||
| "number" // 숫자 (고정 자릿수)
|
||||
| "date" // 날짜 (다양한 날짜 형식)
|
||||
| "text" // 문자 (텍스트)
|
||||
| "category"; // 카테고리 (카테고리 값에 따른 형식)
|
||||
| "sequence" // 순번 (자동 증가 숫자)
|
||||
| "number" // 숫자 (고정 자릿수)
|
||||
| "date" // 날짜 (다양한 날짜 형식)
|
||||
| "text" // 문자 (텍스트)
|
||||
| "category" // 카테고리 (카테고리 값에 따른 형식)
|
||||
| "reference"; // 참조 (다른 컬럼의 값을 가져옴, 마스터-디테일 분번용)
|
||||
|
||||
/**
|
||||
* 생성 방식
|
||||
|
|
@ -77,6 +78,9 @@ export interface NumberingRulePart {
|
|||
// 카테고리용
|
||||
categoryKey?: string; // 카테고리 키 (테이블.컬럼 형식, 예: "item_info.type")
|
||||
categoryMappings?: CategoryFormatMapping[]; // 카테고리 값별 형식 매핑
|
||||
|
||||
// 참조용 (마스터-디테일 분번)
|
||||
referenceColumnName?: string; // 참조할 컬럼명 (FK 컬럼 등, 해당 컬럼의 값을 코드에 포함)
|
||||
};
|
||||
|
||||
// 직접 입력 설정
|
||||
|
|
@ -132,6 +136,7 @@ export const CODE_PART_TYPE_OPTIONS: Array<{ value: CodePartType; label: string;
|
|||
{ value: "date", label: "날짜", description: "날짜 형식 (2025-11-04)" },
|
||||
{ value: "text", label: "문자", description: "텍스트 또는 코드" },
|
||||
{ value: "category", label: "카테고리", description: "카테고리 값에 따른 형식" },
|
||||
{ value: "reference", label: "참조", description: "다른 컬럼 값 참조 (마스터 키 분번)" },
|
||||
];
|
||||
|
||||
export const DATE_FORMAT_OPTIONS: Array<{ value: DateFormat; label: string; example: string }> = [
|
||||
|
|
|
|||
Loading…
Reference in New Issue