feat(부서관리): 다중 결재/부서 관리자 + 조직장 (DEPT_MANAGERS 매핑 테이블)
- 마이그레이션 V022/RUN_088: DEPT_MANAGERS 신규 (role: approval/dept/org_leader, PK 3-tuple, FK CASCADE) - StartupSchemaMigrator 에 V022 idempotent CREATE 추가 → 테넌트 DB 자동 동기화 - mapper.xml: SELECT 에 3 json_agg ::TEXT 컬럼 추가, insertDeptManagers + deleteDeptManagersByDeptAndRole 신규 - service: parseManagersJson + syncManagers (delete-all + insert-all, 최대 10명, 역할 한글 메시지) - frontend: types 3 필드, DeptDetailDraft 확장, ManagerChipsField (chip+UserSearchModal 재사용), 조직장 Row 신규 기존 DEPT_INFO.APPROVAL_MANAGER / DEPT_MANAGER 단일 컬럼은 호환을 위해 유지.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# 088 마이그레이션 — DEPT_MANAGERS 테이블 추가 (다중 관리자 + 조직장)
|
||||
|
||||
작성일: 2026-05-14
|
||||
작성자: johngreen
|
||||
관련: RPS 더존 ERP UJA1040 레퍼런스 대비 누락 기능 (A 단계 — 다중 관리자 + 조직장)
|
||||
|
||||
## 목적
|
||||
|
||||
부서별로 결재 관리자 / 부서 관리자 / 조직장을 각각 **다중 등록 (최대 10명)** 할 수 있도록 매핑 테이블 신설.
|
||||
|
||||
- 기존 `DEPT_INFO.APPROVAL_MANAGER` / `DEPT_INFO.DEPT_MANAGER` 컬럼은 단일 `user_id` 만 저장 가능
|
||||
- 신규 `DEPT_MANAGERS` 매핑 테이블이 SoT(source of truth). `ROLE` 컬럼으로 3 종류 구분
|
||||
- `approval` = 결재 관리자 (자동 결재라인 등록 시 호출)
|
||||
- `dept` = 부서 관리자 (행정 책임자)
|
||||
- `org_leader` = 조직장 (본인 부서 + 하위 부서의 경비/근태 조회·승인 권한)
|
||||
- 기존 단일 컬럼은 **호환 위해 일단 유지**. 향후 cleanup PR 에서 제거 예정
|
||||
|
||||
## 스키마
|
||||
|
||||
### DEPT_MANAGERS (신규)
|
||||
|
||||
| 컬럼 | 타입 | 제약 | 설명 |
|
||||
|---|---|---|---|
|
||||
| `DEPT_CODE` | VARCHAR(1024) | NOT NULL, FK → DEPT_INFO ON DELETE CASCADE | 부서 코드 |
|
||||
| `USER_ID` | VARCHAR(50) | NOT NULL | 사용자 ID |
|
||||
| `ROLE` | VARCHAR(20) | NOT NULL, CHECK | `approval` \| `dept` \| `org_leader` |
|
||||
| `SORT_ORDER` | INTEGER | NOT NULL DEFAULT 1 | 표시 순서 |
|
||||
| `CREATED_AT` | TIMESTAMP | NOT NULL DEFAULT NOW() | 등록 시각 |
|
||||
|
||||
PK: `(DEPT_CODE, USER_ID, ROLE)` — 같은 사용자가 같은 부서에 같은 role 로 중복 등록 차단.
|
||||
인덱스: `(DEPT_CODE, ROLE, SORT_ORDER)` — 부서별 role 조회 + 정렬 가속.
|
||||
|
||||
## SQL
|
||||
|
||||
```sql
|
||||
-- =================================================================
|
||||
-- 088: DEPT_MANAGERS 테이블 (idempotent)
|
||||
-- =================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS DEPT_MANAGERS (
|
||||
DEPT_CODE VARCHAR(1024) NOT NULL,
|
||||
USER_ID VARCHAR(50) NOT NULL,
|
||||
ROLE VARCHAR(20) NOT NULL,
|
||||
SORT_ORDER INTEGER NOT NULL DEFAULT 1,
|
||||
CREATED_AT TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (DEPT_CODE, USER_ID, ROLE),
|
||||
CONSTRAINT chk_dept_managers_role
|
||||
CHECK (ROLE IN ('approval', 'dept', 'org_leader')),
|
||||
CONSTRAINT fk_dept_managers_dept
|
||||
FOREIGN KEY (DEPT_CODE) REFERENCES DEPT_INFO(DEPT_CODE) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_dept_managers_role
|
||||
ON DEPT_MANAGERS (DEPT_CODE, ROLE, SORT_ORDER);
|
||||
```
|
||||
|
||||
부팅 시 `StartupSchemaMigrator` 가 메타 DB + 모든 활성 테넌트 DB 에 동일 DDL 을 `IF NOT EXISTS` 로 적용하므로 일반적으로는 별도 수동 실행이 필요 없음.
|
||||
|
||||
## 사전 점검
|
||||
|
||||
```sql
|
||||
-- A. 테이블 사전 상태
|
||||
SELECT table_name FROM information_schema.tables WHERE table_name = 'dept_managers';
|
||||
-- 빈 결과여야 정상. 이미 있으면 CREATE 의 IF NOT EXISTS 가 안전.
|
||||
|
||||
-- B. DEPT_INFO 행수 (FK 영향 범위)
|
||||
SELECT COUNT(*) FROM DEPT_INFO;
|
||||
```
|
||||
|
||||
## 사후 검증
|
||||
|
||||
```sql
|
||||
-- C. 테이블 추가 확인
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'dept_managers'
|
||||
ORDER BY ordinal_position;
|
||||
-- 기대: 5 행 (DEPT_CODE/USER_ID/ROLE/SORT_ORDER/CREATED_AT)
|
||||
|
||||
-- D. CHECK 제약 확인
|
||||
SELECT constraint_name, check_clause FROM information_schema.check_constraints
|
||||
WHERE constraint_name = 'chk_dept_managers_role';
|
||||
-- 기대: ROLE IN ('approval', 'dept', 'org_leader')
|
||||
|
||||
-- E. FK 동작 확인 (테스트)
|
||||
BEGIN;
|
||||
INSERT INTO DEPT_MANAGERS (DEPT_CODE, USER_ID, ROLE)
|
||||
VALUES ('NON_EXISTENT_DEPT', 'tester', 'approval');
|
||||
-- 기대: FK 위반 에러 (foreign key constraint "fk_dept_managers_dept")
|
||||
ROLLBACK;
|
||||
```
|
||||
|
||||
## 실행
|
||||
|
||||
```bash
|
||||
# 1) 메타 DB
|
||||
psql -h <host> -U postgres -d invyone -f RUN_088.sql
|
||||
|
||||
# 2) 각 테넌트 DB (StartupSchemaMigrator 가 부팅 시 자동 적용하므로 통상 생략 가능)
|
||||
for db in $(psql -tA -d invyone -c "SELECT db_name FROM company_mng WHERE db_status='active'"); do
|
||||
echo "=== $db ==="
|
||||
psql -h <host> -U postgres -d "$db" -f RUN_088.sql
|
||||
done
|
||||
```
|
||||
|
||||
## 롤백
|
||||
|
||||
```sql
|
||||
-- DEPT_MANAGERS 테이블 제거 (저장된 다중 관리자 매핑 함께 삭제됨)
|
||||
DROP INDEX IF EXISTS idx_dept_managers_role;
|
||||
DROP TABLE IF EXISTS DEPT_MANAGERS;
|
||||
```
|
||||
|
||||
롤백 후엔 백엔드/프론트가 단일 `APPROVAL_MANAGER` / `DEPT_MANAGER` 컬럼만 사용하는 이전 동작으로 자연스럽게 복귀 (호환 컬럼 유지하기 때문).
|
||||
|
||||
## 적용 환경 체크리스트
|
||||
|
||||
- [ ] 로컬 docker `naengangi-pg` (관련 없음 — invyone DB 는 wace/운영에만 존재)
|
||||
- [ ] wace 개발서버 PostgreSQL
|
||||
- [ ] 운영 메타 DB (`invyone`)
|
||||
- [ ] 운영 각 테넌트 DB (loop or 부팅 시 자동)
|
||||
|
||||
## 관련 코드
|
||||
|
||||
- Flyway: `backend-spring/src/main/resources/db/migration/V022__create_dept_managers.sql`
|
||||
- StartupSchemaMigrator: `backend-spring/src/main/java/com/erp/migration/StartupSchemaMigrator.java` (마지막 항목으로 추가)
|
||||
- Mapper: `backend-spring/src/main/resources/mapper/department.xml`
|
||||
- `selectDepartments` / `selectDepartmentByCode` 의 SELECT 절에 `APPROVAL_MANAGERS`/`DEPT_MANAGERS`/`ORG_LEADERS` json_agg 컬럼 추가
|
||||
- 신규 query: `insertDeptManagers`, `deleteDeptManagersByDept`
|
||||
- Service: `DepartmentService.java`
|
||||
- `createDepartment` / `updateDepartment` 가 body 의 `approval_managers[]`/`dept_managers[]`/`org_leaders[]` 배열을 `DEPT_MANAGERS` 에 sync (트랜잭션, 최대 10명 검증)
|
||||
- Frontend: `frontend/app/(main)/admin/userMng/deptMngList/page.tsx`
|
||||
- BasicInfoForm 에 다중 chip UI + ManagerPicker 모달
|
||||
Reference in New Issue
Block a user