관계 정의 (relationships)
관계 정의
{
"sourceRef": "hcm_items",
"targetRef": "hcm_bom",
"relationType": "1:N",
"relationName": "품목→BOM"
}
관계 필드 명세
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
sourceRef | string | 필수 | 출발 테이블 refId |
targetRef | string | 필수 | 도착 테이블 refId |
relationType | string | 필수 | "1:1", "1:N", "M:N" 중 택1 |
relationName | string | 선택 | 한글 관계명 (예: "품목→BOM") |
fkBindings | array | 선택 | FK 컬럼 명시적 바인딩 |
관계 타입
| 타입 | 의미 | 예시 |
|---|---|---|
1:1 | 일대일 | 사원 → 사원상세 |
1:N | 일대다 (가장 일반적) | 거래처 → 발주서, 작업지시 → 실적 |
M:N | 다대다 | 품목 ↔ 공급업체 (중간 테이블 필요) |
fkBindings (선택적 FK 바인딩)
{
"sourceRef": "hcm_purchase_orders",
"targetRef": "hcm_partners",
"relationType": "1:N",
"fkBindings": [
{
"sourceColumn": "partner_id",
"targetColumn": "id",
"displayColumn": "partner_name"
}
]
}
- 보통 생략 가능 — 시스템이 FK 컬럼의
fkRef에서 자동 추론 - 하나의 관계에 여러 FK 바인딩이 있을 때 명시적으로 사용
관계 규칙
sourceRef와targetRef는 같은 JSON 파일 내 테이블refId만 가능- 자기 참조 가능 (예:
hcm_item_categories→hcm_item_categories) - 모든 FK 컬럼에 대응하는 관계가 하나 이상 존재해야 함
M:N관계는 반드시 중간 테이블 정의 필요
CascadeUpdate Rules (연쇄 갱신 규칙)
CascadeUpdateService는 특정 테이블에 INSERT 또는 UPDATE가 발생할 때, 관련 상위 테이블의 수량/상태를 자동으로 연쇄 갱신하는 서비스입니다. 관계 정의와 함께 사용되어 데이터 정합성을 보장합니다.
구현된 규칙 (5건)
| 규칙 | 트리거 | 대상 테이블 | 갱신 내용 |
|---|---|---|---|
| Rule 1 | production_results INSERT | work_orders | good_qty, defect_qty 집계 갱신 |
| Rule 2 | production_results INSERT | production_orders | actual_qty, progress 재계산 |
| Rule 3 | shipments 확정 (confirm) | sales_order_items | shipped_qty 증가 |
| Rule 4 | shipments 취소 (cancel) | sales_order_items | shipped_qty 롤백 |
| Rule 5 | sales_order_items shipped_qty 변경 | sales_orders | status 자동 갱신 (부분출하/완료) |
트랜잭션 패턴
모든 연쇄 갱신은 QueryRunner를 사용한 단일 트랜잭션 내에서 실행됩니다. 대상 행에 FOR UPDATE 잠금을 걸어 동시성 충돌을 방지합니다.
// CascadeUpdateService 내부 트랜잭션 패턴
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. 대상 행 잠금
const target = await queryRunner.query(
`SELECT * FROM work_orders WHERE id = $1 FOR UPDATE`,
[workOrderId]
);
// 2. 집계 계산
const aggregated = await queryRunner.query(
`SELECT SUM(good_qty) as total_good, SUM(defect_qty) as total_defect
FROM production_results WHERE work_order_id = $1`,
[workOrderId]
);
// 3. 상위 테이블 갱신
await queryRunner.query(
`UPDATE work_orders SET good_qty = $1, defect_qty = $2 WHERE id = $3`,
[aggregated.total_good, aggregated.total_defect, workOrderId]
);
// 4. audit_trail 자동 삽입
await queryRunner.query(
`INSERT INTO audit_trail
(entity_type, entity_id, field_name, old_value, new_value,
changed_by, source_entity_type, source_entity_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
['work_orders', workOrderId, 'good_qty',
oldValue, newValue, userId,
'production_results', resultId]
);
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
audit_trail 자동 삽입
연쇄 갱신이 발생할 때마다 audit_trail 테이블에 변경 이력이 자동으로 기록됩니다.
source_entity_type/source_entity_id: 변경을 유발한 원본 엔티티 정보entity_type/entity_id: 실제 갱신된 대상 엔티티 정보- 각 cascade 단계마다 별도 행 삽입 (예: Rule 1 + Rule 2가 연쇄 실행되면 2건의 audit_trail 생성)
규칙 정의 예시 (Rule 3: 출하 확정 → 수주항목 출하수량)
// 출하 확정 시 수주항목의 shipped_qty를 증가시키는 규칙
{
trigger: {
table: 'shipments',
event: 'confirm', // status가 '확정'으로 변경될 때
},
cascade: {
targetTable: 'sales_order_items',
joinColumn: 'sales_order_item_id', // shipment_items → sales_order_items FK
updateExpression: 'shipped_qty = shipped_qty + NEW.ship_qty',
},
audit: {
sourceEntityType: 'shipments',
fieldName: 'shipped_qty',
}
}
참고: CascadeUpdate 규칙은 관계 정의(
relationships)의 FK 바인딩과 독립적으로 동작합니다. 관계 정의는 캔버스 시각화와 DDL 생성에 사용되고, CascadeUpdate 규칙은 런타임 데이터 정합성에 사용됩니다.
Document Conversion Rules (문서 전환 규칙)
CascadeUpdateService는 연쇄 갱신 외에도 문서 간 전환(견적→수주, 수주→생산오더, 생산오더→작업지시)을 트랜잭션 안전하게 처리합니다. 각 전환은 화면의 actionButtons에서 트리거됩니다.
구현된 전환 규칙 (3건)
| 규칙 | API 엔드포인트 | 소스 테이블 | 대상 테이블 | 동작 |
|---|---|---|---|---|
| DC-1 | POST :tableName/_convert-quotation | quotations + quotation_items | sales_orders + sales_order_items | 견적서 → 수주 전환 |
| DC-2 | POST :tableName/_create-production-orders | sales_orders + sales_order_items | production_orders | 수주 → 생산오더 발행 |
| DC-3 | POST :tableName/_create-work-orders | production_orders | work_orders | 생산오더 → 작업지시 발행 |