간단 입·출고 + 결재 시스템 설계서 (최종 v4)
버전: 2025-09-18 16:22:30Z (UTC)
요약: 벤더 ↔ 창고 ↔ 고객사 간 물품 이동(입고/출고)을 관리하는 최소구성 시스템.
- 트랜잭션당 1개의 결재(1:1), 승인자 순서 기반의 순차 결재 지원.
- 다음 승인자로 넘어가면 안 되는 상태를
approval_statuses.is_blocking_next로 제어.
- 모든 테이블(타입/코드 테이블 포함)에 공통 컬럼 적용:
is_active, is_deleted, created_at, updated_at.
- 벤더는 트랜잭션 헤더에 연결하지 않음(벤더는 제품을 통해서만 추적).
- customer_roles 제거: 트랜잭션-고객은 역할 없이 다수 연결만 허용.
- 타입값은 별도 테이블로 분리하며
*_code/정렬순서 미사용, ID 기반 참조.
- 메뉴 접근은
groups와 group_menu_permissions를 통해 제어되며, 모든 직원은 정확히 하나의 그룹에 속함.
0) 핵심 비즈니스 규칙
- 제품 1개는 반드시 1개의 벤더에 소속 (
products.vendor_id 필수).
- 트랜잭션 1건당 결재 1건(1:1, 소프트삭제 제외).
- 결재는 승인자 순서(
approval_steps.step_order)대로만 진행.
- 각 단계 상태가 blocking이면 다음 단계로 이동 불가.
- 트랜잭션에는 여러 고객사를 연결할 수 있음(역할 없음).
- 모든 직원은 그룹에 속하며(
employees.group_id), 그룹-메뉴 권한(group_menu_permissions)으로 메뉴별 CRUD 가능 여부가 결정됨.
- 고객사는 유형을
is_partner/is_general 플래그로 구분하며 둘 중 하나 이상이 true여야 함(기본: 일반 true, 파트너 false).
- 고객사는 담당자 이름(
contact_name)을 별도 관리하며 고객 응답에 항상 포함.
- 반복되는 결재 라인은 결재 템플릿으로 저장 후 호출하여 재사용 가능.
- 모든 삭제는 소프트 삭제(
is_deleted=true)이며, 삭제 시 is_active=false로 내림.
1) 개념 ERD
2) 공통 컬럼 (모든 테이블 공통 적용)
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| note |
비고 |
text |
- |
- |
N |
N |
N |
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
N |
N |
- |
| is_deleted |
삭제여부(소프트) |
boolean |
- |
false |
Y |
N |
N |
- |
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
N |
N |
- |
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
N |
N |
- |
모든 테이블(타입/코드 테이블 포함)에 위 4개 컬럼을 명시적으로 포함.
note는 테이블별 메모/추가 설명을 저장하는 자유 텍스트 필드.
updated_at은 UPDATE 시 자동 갱신(트리거/생성 컬럼 권장). 삭제 시 is_deleted=true, is_active=false 처리.
3) 테이블 정의
3.1 vendors (벤더)
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
벤더ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| vendor_code |
벤더코드 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| vendor_name |
벤더명 |
varchar |
100 |
- |
Y |
|
|
|
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
zipcodes 테이블이 우편번호 마스터를 보유하며, 참조 측 테이블은 zipcode_id → zipcodes.id FK만 저장한다. 응답 시에는 FK를 통해 조회한 우편번호 요약 정보를 포함한다.
3.2 warehouses (창고)
| 영문테이블명 |
한글테이블명 |
| warehouses |
창고 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
창고ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| warehouse_code |
창고코드 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| warehouse_name |
창고명 |
varchar |
100 |
- |
Y |
|
|
|
| zipcode_id |
우편번호ID |
bigint |
- |
- |
N |
|
|
zipcodes.id |
| address_detail |
상세주소 |
varchar |
200 |
- |
N |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.3 customers (고객사)
| 영문테이블명 |
한글테이블명 |
| customers |
고객사 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
고객사ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| customer_code |
고객사코드 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| customer_name |
고객사명 |
varchar |
100 |
- |
Y |
|
|
|
| contact_name |
담당자명 |
varchar |
100 |
- |
N |
|
|
- |
| is_partner |
파트너여부 |
boolean |
- |
false |
Y |
|
|
- |
| is_general |
일반여부 |
boolean |
- |
true |
Y |
|
|
- |
| email |
이메일 |
varchar |
100 |
- |
N |
|
|
- |
| mobile_no |
모바일번호 |
varchar |
20 |
- |
N |
|
|
- |
| zipcode_id |
우편번호ID |
bigint |
- |
- |
N |
|
|
zipcodes.id |
| address_detail |
상세주소 |
varchar |
200 |
- |
N |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
contact_name은 고객사의 대표 연락 창구로 사용하는 담당자 실명. 필수는 아니며 미입력 시 null 저장.
고객/창고 모두 zipcode_id를 통해 zipcodes.id와 연결하며, API 응답은 FK가 가리키는 zipcodes 행에서 필요한 우편번호 메타 정보를 추출해 제공한다.
3.4 employees (사원)
| 영문테이블명 |
한글테이블명 |
| employees |
사원 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
사원ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| employee_no |
사번 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| employee_name |
성명 |
varchar |
100 |
- |
Y |
|
|
|
| email |
이메일 |
varchar |
100 |
- |
N |
Y |
|
|
| mobile_no |
모바일번호 |
varchar |
20 |
- |
N |
|
|
|
| group_id |
그룹ID |
bigint |
- |
- |
Y |
|
|
groups.id |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.5 zipcodes (우편번호)
| 영문테이블명 |
한글테이블명 |
| zipcodes |
우편번호 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
우편번호ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| zipcode |
우편번호 |
varchar |
5 |
- |
Y |
|
N |
- |
| sido |
시도 |
varchar |
50 |
- |
Y |
|
|
- |
| sido_eng |
시도영문 |
varchar |
100 |
- |
N |
|
|
- |
| sigungu |
시군구 |
varchar |
100 |
- |
Y |
|
|
- |
| sigungu_eng |
시군구영문 |
varchar |
100 |
- |
N |
|
|
- |
| eupmyeon |
읍면 |
varchar |
100 |
- |
N |
|
|
- |
| eupmyeon_eng |
읍면영문 |
varchar |
100 |
- |
N |
|
|
- |
| road_code |
도로명코드 |
varchar |
12 |
- |
Y |
|
|
- |
| road_name |
도로명 |
varchar |
200 |
- |
Y |
|
|
- |
| road_name_eng |
도로명영문 |
varchar |
200 |
- |
N |
|
|
- |
| underground_flag |
지하여부 |
varchar |
1 |
'N' |
Y |
|
|
- |
| building_main_no |
건물번호본번 |
integer |
- |
0 |
Y |
|
|
- |
| building_sub_no |
건물번호부번 |
integer |
- |
0 |
N |
|
|
- |
| building_mgmt_no |
건물관리번호 |
varchar |
25 |
- |
Y |
|
|
- |
| bulk_receiver |
다량배달처명 |
varchar |
200 |
- |
N |
|
|
- |
| sigungu_building_name |
시군구용건물명 |
varchar |
200 |
- |
N |
|
|
- |
| legal_dong_code |
법정동코드 |
varchar |
10 |
- |
Y |
|
|
- |
| legal_dong_name |
법정동명 |
varchar |
100 |
- |
Y |
|
|
- |
| ri_name |
리명 |
varchar |
100 |
- |
N |
|
|
- |
| admin_dong_name |
행정동명 |
varchar |
100 |
- |
N |
|
|
- |
| mountain_flag |
산여부 |
varchar |
1 |
'N' |
Y |
|
|
- |
| land_main_no |
지번본번 |
integer |
- |
0 |
Y |
|
|
- |
| town_serial_no |
읍면동일련번호 |
integer |
- |
0 |
N |
|
|
- |
| land_sub_no |
지번부번 |
integer |
- |
0 |
N |
|
|
- |
| old_zipcode |
구우편번호 |
varchar |
6 |
- |
N |
|
|
- |
| zipcode_serial_no |
우편번호일련번호 |
integer |
- |
0 |
Y |
|
|
- |
| search_text |
검색텍스트 |
text |
- |
- |
N |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
도로명 주소 데이터와 매핑되는 5자리 우편번호 기준. id는 내부용 서러겟 PK이며, zipcode는 동일 코드가 여러 주소 행에 등장할 수 있다.
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
메뉴ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| menu_code |
메뉴코드 |
varchar |
50 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| menu_name |
메뉴명 |
varchar |
100 |
- |
Y |
|
|
|
| parent_menu_id |
상위메뉴ID |
bigint |
- |
- |
N |
|
|
menus.id |
| route_path |
경로 |
varchar |
255 |
- |
N |
|
|
- |
| display_order |
표시순서 |
integer |
- |
0 |
Y |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
메뉴는 계층 구조를 지원하며 parent_menu_id가 NULL이면 1차 메뉴로 간주.
3.8 groups (그룹)
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
그룹ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| group_name |
그룹명 |
varchar |
100 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| group_description |
그룹설명 |
varchar |
255 |
- |
N |
|
|
- |
| is_default |
기본그룹여부 |
boolean |
- |
false |
Y |
|
|
|
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
group_menu_permissions를 통해 각 그룹별 메뉴 CRUD 권한을 정의하며, 사원은 employees.group_id로 그룹에 연결됨.
| 영문테이블명 |
한글테이블명 |
| group_menu_permissions |
그룹_메뉴_권한 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
메뉴그룹권한ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| group_id |
그룹ID |
bigint |
- |
- |
Y |
(복합유니크: group_id, menu_id, is_deleted) |
N |
groups.id |
| menu_id |
메뉴ID |
bigint |
- |
- |
Y |
(복합유니크: group_id, menu_id, is_deleted) |
N |
menus.id |
| can_create |
생성권한 |
boolean |
- |
false |
Y |
|
|
- |
| can_read |
조회권한 |
boolean |
- |
true |
Y |
|
|
- |
| can_update |
수정권한 |
boolean |
- |
false |
Y |
|
|
- |
| can_delete |
삭제권한 |
boolean |
- |
false |
Y |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
각 메뉴에 대한 CRUD 권한을 그룹 단위로 정의하며, 권한 미설정 시 기본적으로 조회만 허용.
3.10 uoms (단위) — 타입 테이블
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
단위ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| uom_name |
단위명 |
varchar |
100 |
- |
Y |
(선택) 부분유니크 |
N |
- |
| is_default |
기본여부 |
boolean |
- |
false |
Y |
|
|
|
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
예시 값: EA(기본 단위), BOX, KG, LITER 등.
3.11 products (제품)
| 영문테이블명 |
한글테이블명 |
| products |
제품 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
제품ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| product_code |
제품코드 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| product_name |
제품명 |
varchar |
100 |
- |
Y |
|
|
|
| vendor_id |
벤더ID |
bigint |
- |
- |
Y |
|
|
vendors.id |
| uom_id |
단위ID |
bigint |
- |
- |
Y |
|
|
uoms.id |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.12 transaction_types (입출고_유형) — 타입 테이블
| 영문테이블명 |
한글테이블명 |
| transaction_types |
입출고_유형 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
유형ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| type_name |
유형명 |
varchar |
100 |
- |
Y |
(선택) 부분유니크 |
N |
- |
| is_default |
기본여부 |
boolean |
- |
false |
Y |
|
|
|
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
예시 값: 입고(is_default=true), 출고.
3.13 transaction_statuses (트랜잭션_상태) — 타입 테이블
| 영문테이블명 |
한글테이블명 |
| transaction_statuses |
트랜잭션_상태 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
상태ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| status_name |
상태명 |
varchar |
100 |
- |
Y |
(선택) 부분유니크 |
N |
- |
| is_default |
기본여부 |
boolean |
- |
false |
Y |
|
|
|
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
예시 값: 정상(is_default), 반품, 폐기.
3.14 stock_transactions (입출고_트랜잭션)
| 영문테이블명 |
한글테이블명 |
| stock_transactions |
입출고_트랜잭션 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
트랜잭션ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| transaction_no |
트랜잭션번호 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| transaction_type_id |
입출고유형ID |
bigint |
- |
- |
Y |
|
|
transaction_types.id |
| transaction_status_id |
트랜잭션상태ID |
bigint |
- |
- |
Y |
|
|
transaction_statuses.id |
| warehouse_id |
창고ID |
bigint |
- |
- |
Y |
|
|
warehouses.id |
| transaction_date |
처리일자 |
date |
- |
current_date |
Y |
|
|
- |
| created_by_id |
작성자ID |
bigint |
- |
- |
N |
|
|
employees.id |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
주의: 벤더ID 없음. 벤더 정보는 라인의 product_id가 가리키는 products.vendor_id로 파생.
목록 조회는 customer_id 쿼리 파라미터를 지원해 특정 고객이 연결된 트랜잭션만 필터링할 수 있다. (2024-10 갱신)
3.15 transaction_lines (트랜잭션_라인)
| 영문테이블명 |
한글테이블명 |
| transaction_lines |
트랜잭션_라인 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
라인ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| transaction_id |
트랜잭션ID |
bigint |
- |
- |
Y |
|
|
stock_transactions.id |
| line_no |
라인번호 |
integer |
- |
1 |
Y |
(복합유니크: transaction_id, line_no, is_deleted) |
N |
- |
| product_id |
제품ID |
bigint |
- |
- |
Y |
|
|
products.id |
| quantity |
수량 |
numeric |
20,6 |
0 |
Y |
|
|
- |
| unit_price |
단가 |
numeric |
20,6 |
0 |
N |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.16 transaction_customers (트랜잭션_고객사)
| 영문테이블명 |
한글테이블명 |
| transaction_customers |
트랜잭션_고객사 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
키 |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| transaction_id |
트랜잭션ID |
bigint |
- |
- |
Y |
|
|
stock_transactions.id |
| customer_id |
고객사ID |
bigint |
- |
- |
Y |
(복합유니크: transaction_id, customer_id, is_deleted) |
N |
customers.id |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.17 approval_statuses (결재_상태) — 타입 테이블
| 영문테이블명 |
한글테이블명 |
| approval_statuses |
결재_상태 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
상태ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| status_name |
상태명 |
varchar |
100 |
- |
Y |
(선택) 부분유니크 |
N |
- |
| is_default |
기본여부 |
boolean |
- |
false |
Y |
|
|
|
| is_blocking_next |
차기이동차단 |
boolean |
- |
true |
Y |
|
|
|
| is_terminal |
종결여부 |
boolean |
- |
false |
Y |
|
|
|
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
예시 값:
대기(is_default=true, is_blocking_next=true, is_terminal=false)
진행중(is_blocking_next=true, is_terminal=false)
보류(is_blocking_next=true, is_terminal=false)
승인(is_blocking_next=false, is_terminal=false)
반려(is_blocking_next=true, is_terminal=true).
3.18 approval_actions (결재_행위) — 타입 테이블
| 영문테이블명 |
한글테이블명 |
| approval_actions |
결재_행위 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
행위ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| action_name |
행위명 |
varchar |
100 |
- |
Y |
(선택) 부분유니크 |
N |
- |
| is_default |
기본여부 |
boolean |
- |
false |
Y |
|
|
|
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
예시 값: approve(승인), reject(반려), comment(코멘트).
3.19 approvals (결재)
| 영문테이블명 |
한글테이블명 |
| approvals |
결재 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
결재ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| transaction_id |
트랜잭션ID |
bigint |
- |
- |
Y |
(부분유니크: is_deleted=false) |
N |
stock_transactions.id |
| approval_no |
결재번호 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| approval_status_id |
전체결재상태ID |
bigint |
- |
- |
Y |
|
|
approval_statuses.id |
| current_step_id |
현재단계ID |
bigint |
- |
- |
N |
|
|
approval_steps.id |
| requested_by_id |
상신자ID |
bigint |
- |
- |
Y |
|
|
employees.id |
| requested_at |
상신일시 |
timestamp |
- |
now() |
Y |
|
|
- |
| decided_at |
최종결정일시 |
timestamp |
- |
- |
N |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.20 approval_steps (결재_단계)
| 영문테이블명 |
한글테이블명 |
| approval_steps |
결재_단계 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
단계ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| approval_id |
결재ID |
bigint |
- |
- |
Y |
|
|
approvals.id |
| step_order |
단계순서 |
integer |
- |
1 |
Y |
(복합유니크: approval_id, step_order, is_deleted) |
N |
- |
| approver_id |
승인자ID |
bigint |
- |
- |
Y |
|
|
employees.id |
| step_status_id |
단계상태ID |
bigint |
- |
- |
Y |
|
|
approval_statuses.id |
| assigned_at |
배정일시 |
timestamp |
- |
now() |
Y |
|
|
- |
| decided_at |
결정일시 |
timestamp |
- |
- |
N |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.21 approval_histories (결재_승인이력)
| 영문테이블명 |
한글테이블명 |
| approval_histories |
결재_승인이력 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
이력ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| approval_id |
결재ID |
bigint |
- |
- |
Y |
|
|
approvals.id |
| approval_step_id |
결재단계ID |
bigint |
- |
- |
Y |
|
|
approval_steps.id |
| approver_id |
승인자ID |
bigint |
- |
- |
Y |
|
|
employees.id |
| approval_action_id |
결재행위ID |
bigint |
- |
- |
Y |
|
|
approval_actions.id |
| from_status_id |
변경전상태ID |
bigint |
- |
- |
N |
|
|
approval_statuses.id |
| to_status_id |
변경후상태ID |
bigint |
- |
- |
Y |
|
|
approval_statuses.id |
| action_at |
작업일시 |
timestamp |
- |
now() |
Y |
|
|
- |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.22 approval_templates (결재_템플릿)
| 영문테이블명 |
한글테이블명 |
| approval_templates |
결재_템플릿 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
템플릿ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| template_code |
템플릿코드 |
varchar |
30 |
- |
Y |
(부분유니크: is_deleted=false) |
N |
- |
| template_name |
템플릿명 |
varchar |
100 |
- |
Y |
|
|
|
| description |
설명 |
varchar |
255 |
- |
N |
|
|
- |
| created_by_id |
작성자ID |
bigint |
- |
- |
Y |
|
|
employees.id |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
3.23 approval_template_steps (결재_템플릿_단계)
| 영문테이블명 |
한글테이블명 |
| approval_template_steps |
결재_템플릿_단계 |
| 영문필드명 |
한글필드명 |
타입 |
길이 |
기본값 |
NOT NULL |
UNIQUE |
PK |
FK |
| id |
템플릿단계ID |
bigint |
- |
identity |
Y |
Y |
Y |
- |
| template_id |
템플릿ID |
bigint |
- |
- |
Y |
(복합유니크: template_id, step_order, is_deleted) |
N |
approval_templates.id |
| step_order |
단계순서 |
integer |
- |
1 |
Y |
(복합유니크: template_id, step_order, is_deleted) |
N |
- |
| approver_id |
승인자ID |
bigint |
- |
- |
Y |
|
|
employees.id |
| note |
비고 |
text |
- |
- |
N |
|
|
- |
| is_active |
사용여부 |
boolean |
- |
true |
Y |
|
|
|
| is_deleted |
삭제여부 |
boolean |
- |
false |
Y |
|
|
|
| created_at |
생성일시 |
timestamp |
- |
now() |
Y |
|
|
|
| updated_at |
변경일시 |
timestamp |
- |
now() |
Y |
|
|
|
템플릿 단계는 실제 결재 단계 생성 시 그대로 복제되며, step_order 순서대로 승인자가 배치됨.
4) FK 관계 (source → target)
menus.parent_menu_id → menus.id
employees.group_id → groups.id
group_menu_permissions.group_id → groups.id
group_menu_permissions.menu_id → menus.id
products.vendor_id → vendors.id
products.uom_id → uoms.id
stock_transactions.warehouse_id → warehouses.id
stock_transactions.created_by_id → employees.id
stock_transactions.transaction_type_id → transaction_types.id
stock_transactions.transaction_status_id → transaction_statuses.id
transaction_lines.transaction_id → stock_transactions.id
transaction_lines.product_id → products.id
transaction_customers.transaction_id → stock_transactions.id
transaction_customers.customer_id → customers.id
approvals.transaction_id → stock_transactions.id
approvals.approval_status_id → approval_statuses.id
approvals.current_step_id → approval_steps.id
approvals.requested_by_id → employees.id
approval_steps.approval_id → approvals.id
approval_steps.approver_id → employees.id
approval_steps.step_status_id → approval_statuses.id
approval_histories.approval_id → approvals.id
approval_histories.approval_step_id → approval_steps.id
approval_histories.approver_id → employees.id
approval_histories.approval_action_id → approval_actions.id
approval_histories.from_status_id → approval_statuses.id
approval_histories.to_status_id → approval_statuses.id
approval_templates.created_by_id → employees.id
approval_template_steps.template_id → approval_templates.id
approval_template_steps.approver_id → employees.id
5) 비즈니스/검증 규칙
- 제품 등록 시
vendor_id 필수.
- 입고(
transaction_type_id=입고) 트랜잭션의 공급자 정보는 라인 제품의 벤더로만 해석. (헤더에 벤더 금지)
- 출고 트랜잭션은
transaction_customers 최소 1건 필요.
- 결재는 트랜잭션당 1건(미삭제 기준)만 허용.
- 단계 전이는 현재 단계에서만 수행 가능. blocking 상태에서는 차기 이동 불가.
- 수량/단가 음수 금지(CHECK).
- 그룹이 비활성(
is_active=false) 또는 삭제되면 해당 그룹 권한/구성원은 즉시 무효 처리.
- 사원의 소속 그룹(
employees.group_id)에서 해당 메뉴에 대한 can_create|can_update|can_delete 중 하나라도 true이면 그 동작을 수행할 수 있음.
6) 인덱스/유니크 권장
- 부분 유니크(또는 복합 유니크)로 소프트 삭제와 공존:
vendors(vendor_code), warehouses(warehouse_code), customers(customer_code), employees(employee_no), menus(menu_code), groups(group_name), products(product_code), stock_transactions(transaction_no), approvals(approval_no)
group_menu_permissions(group_id, menu_id, is_deleted)
approvals(transaction_id) — 미삭제 조건에서 1:1 보장
transaction_lines(transaction_id, line_no, is_deleted)
transaction_customers(transaction_id, customer_id, is_deleted)
- FK 및 조회 인덱스: 모든
*_id, updated_at, is_deleted, is_active.
7) 에러 규격(예시)
400 BAD_REQUEST — 필수 필드 누락, 형식 오류
409 CONFLICT — 유니크 충돌(코드/번호/조합), 현재 단계 아님
422 UNPROCESSABLE_ENTITY — 비즈니스 규칙 위반(출고인데 고객 없음, blocking 상태에서 이동 등)
404 NOT_FOUND — 리소스 없음 또는 삭제됨(deleted=false 기본 필터로 미노출)
8) 마이그레이션 가이드(요약)
stock_transactions에서 vendor_id 드롭.
customer_roles 테이블 및 관련 컬럼 드롭.
- 모든 타입/코드 테이블에 공통 컬럼 4종 추가(미존재 시).
- 부분 유니크 인덱스(
WHERE is_deleted=false) 또는 (컬럼, is_deleted) 복합 유니크 구성.
- 기존 결재 이력은
approval_step_id 매핑(없으면 1단계로 귀속).
approval_statuses에 is_blocking_next, is_terminal 값 시드.
menus, groups, group_menu_permissions 신규 생성 및 기존 관리자 권한/사원-그룹 매핑을 employees.group_id로 이관.
zipcodes 테이블 생성 및 도로명 주소 기준 데이터 적재.
- 모든 테이블에
note(text) 컬럼 추가 및 필요한 경우 기본값 NULL 유지.
9) 초기 시드 값(예시)
transaction_types: [입고, 출고] (is_default: 입고)
transaction_statuses: [초안, 상신, 승인, 반려, 완료] (is_default: 초안)
approval_statuses: [대기(pending, default, blocking), 진행중(in_progress, blocking), 보류(on_hold, blocking), 승인(approved, !blocking), 반려(rejected, blocking+terminal)]
approval_actions: [승인(approve), 반려(reject), 코멘트(comment)]
uoms: [EA(기본), BOX, KG ...]
menus: [대시보드, 입출고 관리, 결재 관리, 레포트 등] — 상위/하위 메뉴 구조 포함
groups: [전사 관리자(기본), 창고 관리자, 결재 담당자]
group_menu_permissions: 기본 그룹별 메뉴 권한(CRUD 플래그); 전사 관리자는 모든 메뉴 can_*=true, 역할별로 세분화 설정
zipcodes: 행정안전부 도로명 주소 DB(5자리) 최신본을 기준으로 일괄 적재
10) 구현 팁
updated_at 자동 갱신 트리거, 소프트 삭제 처리 트리거 권장.
- 낙관적 잠금(선택):
version(int) + ETag.
- 병렬 결재 확장(선택):
approval_steps에 group_no, approval_mode(all|any) 도입.