ERP-node/backend-node/src/services/numberingRuleService.ts

451 lines
13 KiB
TypeScript
Raw Normal View History

2025-11-04 13:58:21 +09:00
/**
*
*/
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
interface NumberingRulePart {
id?: number;
order: number;
partType: string;
generationMethod: string;
autoConfig?: any;
manualConfig?: any;
generatedValue?: string;
}
interface NumberingRuleConfig {
ruleId: string;
ruleName: string;
description?: string;
parts: NumberingRulePart[];
separator?: string;
resetPeriod?: string;
currentSequence?: number;
tableName?: string;
columnName?: string;
companyCode?: string;
createdAt?: string;
updatedAt?: string;
createdBy?: string;
}
class NumberingRuleService {
/**
*
*/
async getRuleList(companyCode: string): Promise<NumberingRuleConfig[]> {
try {
logger.info("채번 규칙 목록 조회 시작", { companyCode });
const pool = getPool();
const query = `
SELECT
rule_id AS "ruleId",
rule_name AS "ruleName",
description,
separator,
reset_period AS "resetPeriod",
current_sequence AS "currentSequence",
table_name AS "tableName",
column_name AS "columnName",
company_code AS "companyCode",
created_at AS "createdAt",
updated_at AS "updatedAt",
created_by AS "createdBy"
FROM numbering_rules
WHERE company_code = $1 OR company_code = '*'
ORDER BY created_at DESC
`;
const result = await pool.query(query, [companyCode]);
// 각 규칙의 파트 정보 조회
for (const rule of result.rows) {
const partsQuery = `
SELECT
id,
part_order AS "order",
part_type AS "partType",
generation_method AS "generationMethod",
auto_config AS "autoConfig",
manual_config AS "manualConfig"
FROM numbering_rule_parts
WHERE rule_id = $1 AND (company_code = $2 OR company_code = '*')
ORDER BY part_order
`;
const partsResult = await pool.query(partsQuery, [rule.ruleId, companyCode]);
rule.parts = partsResult.rows;
}
logger.info(`채번 규칙 목록 조회 완료: ${result.rows.length}`, { companyCode });
return result.rows;
} catch (error: any) {
logger.error(`채번 규칙 목록 조회 중 에러: ${error.message}`);
throw error;
}
}
/**
*
*/
async getRuleById(ruleId: string, companyCode: string): Promise<NumberingRuleConfig | null> {
const pool = getPool();
const query = `
SELECT
rule_id AS "ruleId",
rule_name AS "ruleName",
description,
separator,
reset_period AS "resetPeriod",
current_sequence AS "currentSequence",
table_name AS "tableName",
column_name AS "columnName",
company_code AS "companyCode",
created_at AS "createdAt",
updated_at AS "updatedAt",
created_by AS "createdBy"
FROM numbering_rules
WHERE rule_id = $1 AND (company_code = $2 OR company_code = '*')
`;
const result = await pool.query(query, [ruleId, companyCode]);
if (result.rowCount === 0) return null;
const rule = result.rows[0];
const partsQuery = `
SELECT
id,
part_order AS "order",
part_type AS "partType",
generation_method AS "generationMethod",
auto_config AS "autoConfig",
manual_config AS "manualConfig"
FROM numbering_rule_parts
WHERE rule_id = $1 AND (company_code = $2 OR company_code = '*')
ORDER BY part_order
`;
const partsResult = await pool.query(partsQuery, [ruleId, companyCode]);
rule.parts = partsResult.rows;
return rule;
}
/**
*
*/
async createRule(
config: NumberingRuleConfig,
companyCode: string,
userId: string
): Promise<NumberingRuleConfig> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
// 마스터 삽입
const insertRuleQuery = `
INSERT INTO numbering_rules (
rule_id, rule_name, description, separator, reset_period,
current_sequence, table_name, column_name, company_code, created_by
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING
rule_id AS "ruleId",
rule_name AS "ruleName",
description,
separator,
reset_period AS "resetPeriod",
current_sequence AS "currentSequence",
table_name AS "tableName",
column_name AS "columnName",
company_code AS "companyCode",
created_at AS "createdAt",
updated_at AS "updatedAt",
created_by AS "createdBy"
`;
const ruleResult = await client.query(insertRuleQuery, [
config.ruleId,
config.ruleName,
config.description || null,
config.separator || "-",
config.resetPeriod || "none",
config.currentSequence || 1,
config.tableName || null,
config.columnName || null,
companyCode,
userId,
]);
// 파트 삽입
const parts: NumberingRulePart[] = [];
for (const part of config.parts) {
const insertPartQuery = `
INSERT INTO numbering_rule_parts (
rule_id, part_order, part_type, generation_method,
auto_config, manual_config, company_code
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING
id,
part_order AS "order",
part_type AS "partType",
generation_method AS "generationMethod",
auto_config AS "autoConfig",
manual_config AS "manualConfig"
`;
const partResult = await client.query(insertPartQuery, [
config.ruleId,
part.order,
part.partType,
part.generationMethod,
JSON.stringify(part.autoConfig || {}),
JSON.stringify(part.manualConfig || {}),
companyCode,
]);
parts.push(partResult.rows[0]);
}
await client.query("COMMIT");
logger.info("채번 규칙 생성 완료", { ruleId: config.ruleId, companyCode });
return { ...ruleResult.rows[0], parts };
} catch (error: any) {
await client.query("ROLLBACK");
logger.error("채번 규칙 생성 실패", { error: error.message });
throw error;
} finally {
client.release();
}
}
/**
*
*/
async updateRule(
ruleId: string,
updates: Partial<NumberingRuleConfig>,
companyCode: string
): Promise<NumberingRuleConfig> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const updateRuleQuery = `
UPDATE numbering_rules
SET
rule_name = COALESCE($1, rule_name),
description = COALESCE($2, description),
separator = COALESCE($3, separator),
reset_period = COALESCE($4, reset_period),
table_name = COALESCE($5, table_name),
column_name = COALESCE($6, column_name),
updated_at = NOW()
WHERE rule_id = $7 AND company_code = $8
RETURNING
rule_id AS "ruleId",
rule_name AS "ruleName",
description,
separator,
reset_period AS "resetPeriod",
current_sequence AS "currentSequence",
table_name AS "tableName",
column_name AS "columnName",
company_code AS "companyCode",
created_at AS "createdAt",
updated_at AS "updatedAt",
created_by AS "createdBy"
`;
const ruleResult = await client.query(updateRuleQuery, [
updates.ruleName,
updates.description,
updates.separator,
updates.resetPeriod,
updates.tableName,
updates.columnName,
ruleId,
companyCode,
]);
if (ruleResult.rowCount === 0) {
throw new Error("규칙을 찾을 수 없거나 권한이 없습니다");
}
// 파트 업데이트
let parts: NumberingRulePart[] = [];
if (updates.parts) {
await client.query(
"DELETE FROM numbering_rule_parts WHERE rule_id = $1 AND company_code = $2",
[ruleId, companyCode]
);
for (const part of updates.parts) {
const insertPartQuery = `
INSERT INTO numbering_rule_parts (
rule_id, part_order, part_type, generation_method,
auto_config, manual_config, company_code
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING
id,
part_order AS "order",
part_type AS "partType",
generation_method AS "generationMethod",
auto_config AS "autoConfig",
manual_config AS "manualConfig"
`;
const partResult = await client.query(insertPartQuery, [
ruleId,
part.order,
part.partType,
part.generationMethod,
JSON.stringify(part.autoConfig || {}),
JSON.stringify(part.manualConfig || {}),
companyCode,
]);
parts.push(partResult.rows[0]);
}
}
await client.query("COMMIT");
logger.info("채번 규칙 수정 완료", { ruleId, companyCode });
return { ...ruleResult.rows[0], parts };
} catch (error: any) {
await client.query("ROLLBACK");
logger.error("채번 규칙 수정 실패", { error: error.message });
throw error;
} finally {
client.release();
}
}
/**
*
*/
async deleteRule(ruleId: string, companyCode: string): Promise<void> {
const pool = getPool();
const query = `
DELETE FROM numbering_rules
WHERE rule_id = $1 AND company_code = $2
`;
const result = await pool.query(query, [ruleId, companyCode]);
if (result.rowCount === 0) {
throw new Error("규칙을 찾을 수 없거나 권한이 없습니다");
}
logger.info("채번 규칙 삭제 완료", { ruleId, companyCode });
}
/**
*
*/
async generateCode(ruleId: string, companyCode: string): Promise<string> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const rule = await this.getRuleById(ruleId, companyCode);
if (!rule) throw new Error("규칙을 찾을 수 없습니다");
const parts = rule.parts
.sort((a: any, b: any) => a.order - b.order)
.map((part: any) => {
if (part.generationMethod === "manual") {
return part.manualConfig?.value || "";
}
const autoConfig = part.autoConfig || {};
switch (part.partType) {
case "prefix":
return autoConfig.prefix || "PREFIX";
case "sequence": {
const length = autoConfig.sequenceLength || 4;
return String(rule.currentSequence || 1).padStart(length, "0");
}
case "date":
return this.formatDate(new Date(), autoConfig.dateFormat || "YYYYMMDD");
case "year": {
const format = autoConfig.dateFormat || "YYYY";
const year = new Date().getFullYear();
return format === "YY" ? String(year).slice(-2) : String(year);
}
case "month":
return String(new Date().getMonth() + 1).padStart(2, "0");
case "custom":
return autoConfig.value || "CUSTOM";
default:
return "";
}
});
const generatedCode = parts.join(rule.separator || "");
const hasSequence = rule.parts.some((p: any) => p.partType === "sequence");
if (hasSequence) {
await client.query(
"UPDATE numbering_rules SET current_sequence = current_sequence + 1 WHERE rule_id = $1 AND company_code = $2",
[ruleId, companyCode]
);
}
await client.query("COMMIT");
logger.info("코드 생성 완료", { ruleId, generatedCode });
return generatedCode;
} catch (error: any) {
await client.query("ROLLBACK");
logger.error("코드 생성 실패", { error: error.message });
throw error;
} finally {
client.release();
}
}
private formatDate(date: Date, format: string): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
switch (format) {
case "YYYY": return String(year);
case "YY": return String(year).slice(-2);
case "YYYYMM": return `${year}${month}`;
case "YYMM": return `${String(year).slice(-2)}${month}`;
case "YYYYMMDD": return `${year}${month}${day}`;
case "YYMMDD": return `${String(year).slice(-2)}${month}${day}`;
default: return `${year}${month}${day}`;
}
}
async resetSequence(ruleId: string, companyCode: string): Promise<void> {
const pool = getPool();
await pool.query(
"UPDATE numbering_rules SET current_sequence = 1, updated_at = NOW() WHERE rule_id = $1 AND company_code = $2",
[ruleId, companyCode]
);
logger.info("시퀀스 초기화 완료", { ruleId, companyCode });
}
}
export const numberingRuleService = new NumberingRuleService();