Files
king 269e779129 feat(vacation): 휴가 관리 모듈 — 월간 달력 bar/연차·반차 신청/승인 워크플로/공휴일·연차 설정
- vacation_db 전용 저장소(VacationDBStore), VACATION_DB_URL 미설정 시 '설정 필요' 안내
- 월간 달력: 구글식 휴가 bar(주 단위 lane 배정), 일/공휴일 빨강·토요일 파랑
- 신청 폼: 종류/기간/시작·종료 구분(종일/오전/오후)/사유 + 예상 일수 미리보기
- 일수 계산은 서버(compute_days)에서 주말+공휴일(is_red) 제외 재계산
- 워크플로: 작성중/반려→제출→승인|반려, 취소 soft delete, 본인만 수정(작성중/반려)
- 권한: vacation(접근)/vacation_approver(승인), admin 통과. 설정은 admin 전용
- 공휴일(vacation_holidays) DB 기반 provider + settings CRUD, 연차잔여(vacation_balances)
- 엑셀 내보내기(openpyxl), scripts/sql/vacation_db_init.sql(멱등, 2026 공휴일 seed)
- main.py 등록, 메뉴 ready 전환, docs/.env.example/CLAUDE.md 갱신

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 08:20:20 +09:00

10 KiB

Databases

PostgreSQL DB 목록

DB명 용도
itemcode_db 상품코드, 단품/세트 구성, 채널 ↔ 사내 코드 매칭
orderlist_db 주문 수집·분석·관리 (구 orderlist_app)
return_db 반품·교환·CS 데이터
expense_db 개인경비 / 법인카드 사용내역 / 정산
cupang_db 쿠팡 밀크런 출고 묶음 / 출고 라인 / 입고센터 / 박스 입수량 규칙

명명 규칙

  • 모든 DB 이름은 소문자 + 언더스코어, _db로 끝낸다.
    • inventory_db, cs_db
    • inventoryApp, cs-database
  • 신규 DB가 필요하면 사용자 승인 후 생성한다.
  • 신규 DB 생성 시 함께 정리할 항목:
    • 용도와 책임 모듈
    • 소유자(OWNER) 계정
    • 백업 주기와 위치
    • .env의 연결 정보 변수명

레거시 이름 매핑

예전 이름 현재 기준
orderlist_app orderlist_db

코드/문서/설정에서 orderlist_app을 발견하면 orderlist_db로 수정한다 (수정 전 영향 범위 확인).


DB 작업 원칙

  1. 작업 전 백업 우선. 백업 없는 변경은 진행하지 않는다.
  2. 테이블 owner, 권한, sequence 권한을 확인한다.
  3. 운영 DB의 DROP, TRUNCATE, 조건 없는 대량 DELETE/UPDATE사용자 확인 없이 실행 금지.
  4. 스키마 변경은 마이그레이션 스크립트(scripts/ 또는 alembic 등)로 관리한다.
  5. 운영 DB와 개발 DB의 접속 정보를 혼동하지 않는다 (.env로 분리).

위험 명령 (사용자 승인 필수)

명령 비고
DROP DATABASE 복구 불가. 백업 없으면 절대 실행 금지
DROP TABLE / DROP SCHEMA 의존 객체 확인 필수
TRUNCATE FK CASCADE 시 광범위 삭제 위험
조건 없는 DELETE / UPDATE WHERE 없는 문 차단
docker volume rm <postgres_volume> 운영 데이터 영구 손실
docker compose down -v 볼륨까지 제거. 운영에서 금지

실행 전 반드시:

  1. 백업 확인 (pg_dump, 컨테이너 외부 마운트)
  2. 영향 범위 설명
  3. 사용자 명시 승인

자주 쓰는 점검 명령

# 컨테이너/네트워크
docker ps
docker network ls
docker inspect <postgres_container_name>

# DB 목록 / 접속
sudo -u postgres psql -l
docker exec -it <postgres_container_name> psql -U postgres -l

# 특정 DB 접속
docker exec -it <postgres_container_name> psql -U <user> -d itemcode_db

# 테이블/권한 확인
\dt
\dn+
\du
\z <table_name>

expense_db 스키마 / 초기화

DDL: scripts/sql/expense_db_init.sql (멱등). DB·역할·테이블·인덱스·트리거를 한 번에 생성.

테이블 expense_items

컬럼 타입 비고
id TEXT PK 12자 hex (uuid4 앞 12자)
owner TEXT 소유자 email (소문자)
spent_at DATE 사용일
category TEXT 식대/교통/숙박/비품/접대/통신/기타
method TEXT 법인카드/개인지출/현금
merchant TEXT 가맹점
amount BIGINT 원 단위, ≥ 0
memo TEXT 비고
status TEXT 작성중/제출/승인/반려/정산완료
approver_email TEXT 결재자 email (승인/반려/정산 시 기록)
decided_at TIMESTAMPTZ 결재 시점
reject_reason TEXT 반려 사유
created_at / updated_at TIMESTAMPTZ 트리거로 자동 갱신

인덱스: (owner, spent_at DESC), (status), (created_at DESC).

테이블 expense_attachments

영수증/기타파일 메타데이터. 실제 파일은 DATA_DIR/uploads/expense/{item_id}/ 에 저장.

컬럼 타입 비고
id TEXT PK 12자 hex
item_id TEXT FK expense_items(id) ON DELETE CASCADE
owner TEXT 업로드한 사용자 email
kind TEXT receipt 또는 other (CHECK)
filename TEXT 원본 파일명
stored_path TEXT 디스크 경로 (절대)
content_type TEXT MIME
size_bytes BIGINT 바이트
uploaded_at TIMESTAMPTZ 업로드 시각

인덱스: (item_id).

결재 워크플로

작성중 ─submit──▶ 제출 ─approve──▶ 승인 ─settle──▶ 정산완료
   ▲                │
   └─revert/reject──┴─reject──▶ 반려 ─revert──▶ 작성중
  • submit/revert: owner 본인
  • approve/reject/settle: expense_approver 또는 admin
  • 첨부 추가/항목 수정/삭제: 작성중 또는 반려 상태에서만

마이그레이션

기존 운영 DB 에 신규 컬럼/테이블 적용:

docker exec -i postgres-db psql -U postgres -d expense_db \
  < scripts/sql/expense_db_002_workflow_attachments.sql

신규 설치는 expense_db_init.sql 하나로 충분 (둘 다 멱등).

운영 서버 초기화 (1회)

# 1) 비밀번호 변수 준비 (셸 히스토리에 남지 않게 환경변수 사용)
read -s -p "expense_app password: " APP_PWD; echo

# 2) PostgreSQL 컨테이너에 DDL 적용
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/expense_db_init.sql

# 3) main-app .env 에 EXPENSE_DB_URL 추가
#    EXPENSE_DB_URL=postgresql://expense_app:<APP_PWD>@postgres-db:5432/expense_db

# 4) main-app 재기동
docker compose up -d --build

JSON → DB 마이그레이션

docker exec -e EXPENSE_DB_URL="$EXPENSE_DB_URL" -it dbx-main \
  python scripts/migrate_expense_json_to_db.py \
    --json /data/expense.json --dry-run
# 결과 확인 후
docker exec -e EXPENSE_DB_URL="$EXPENSE_DB_URL" -it dbx-main \
  python scripts/migrate_expense_json_to_db.py --json /data/expense.json

멱등 INSERT(ON CONFLICT DO NOTHING). 원본 JSON 은 건드리지 않는다.


cupang_db 스키마 / 초기화

DDL: scripts/sql/cupang_db_init.sql (멱등). DB·역할(cupang_app)·테이블·인덱스·트리거·센터 seed 를 한 번에 생성. JSON 폴백 없음CUPANG_DB_URL 미설정 시 모듈이 "설정 필요" 안내만 표시.

테이블:

테이블 용도
cupang_centers 입고센터. active=false 로 비활성화(사용 중이면 hard delete 금지)
cupang_box_rules 제품코드별 박스당 입수량(units_per_box). product_code UNIQUE
cupang_shipments 출고 묶음 헤더 (작성일/출고일/센터입고일/센터/출고방식/상태/작업자/메모)
cupang_shipment_lines 출고 라인. shipment_id FK ON DELETE CASCADE. UNIQUE(shipment_id, line_no)

status 허용값: 작성중, 출고준비, 출고완료, 센터입고완료, 취소. 삭제는 기본 soft delete(status='취소').

박스 계산은 서버(store.compute_boxes)에서 재계산: required_boxes = ceil(quantity / units_per_box). 클라이언트 계산은 미리보기용.

상품은 cupang_db 에 복제 저장하지 않는다. 라인에는 product_code + product_name_snapshot 만 보존(과거 명칭 보존). 상품 검색은 itemcode_db 읽기 전용(ITEMCODE_DB_URL, 미설정 시 수동 입력).

운영 서버 초기화 (1회, 사용자 승인 후)

read -s -p "cupang_app password: " APP_PWD; echo
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/cupang_db_init.sql
# main-app .env 에 추가:
#   CUPANG_DB_URL=postgresql://cupang_app:<APP_PWD>@postgres-db:5432/cupang_db
cd /opt/www/main && docker compose up -d --build

멱등 스크립트. 기존 DB 가 있으면 DROP 하지 않음. itemcode_db 는 건드리지 않음.


vacation_db 스키마 / 초기화

DDL: scripts/sql/vacation_db_init.sql (멱등). DB·역할(vacation_app)·테이블·인덱스·트리거·2026 공휴일 seed 를 한 번에 생성. JSON 폴백 없음VACATION_DB_URL 미설정 시 모듈이 "설정 필요" 안내만 표시.

테이블:

테이블 용도
vacation_requests 휴가 신청(헤더). 종류/기간/시작·종료 구분(full/am/pm)/일수/사유/상태/승인자/반려사유
vacation_holidays 공휴일(holiday_date UNIQUE). is_red=true 면 달력 빨강 + 일수 계산 제외. 관리자가 settings 에서 추가/수정/삭제
vacation_balances 사용자별 연차(UNIQUE(user_email, year)). total_days 설정, 사용일수는 승인 휴가 합계로 자동 계산

status 허용값: 작성중, 제출, 승인, 반려, 취소. 워크플로: 작성중/반려 → 제출 → 승인|반려. 삭제는 기본 soft delete(status='취소'). 수정은 작성중/반려 상태에서 본인만.

휴가 일수는 서버(store.compute_days)에서 재계산: 주말 + vacation_holidays(is_red) 제외, 오전/오후 반차 0.5일, 시작/종료 반차는 각 0.5 차감. 클라이언트 계산은 미리보기(주말만 제외)용.

권한: vacation(접근) / vacation_approver(승인·반려). admin 은 항상 통과. 공휴일·연차 설정은 admin 전용.

운영 서버 초기화 (1회, 사용자 승인 후)

read -s -p "vacation_app password: " APP_PWD; echo
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/vacation_db_init.sql
# main-app .env 에 추가:
#   VACATION_DB_URL=postgresql://vacation_app:<APP_PWD>@postgres-db:5432/vacation_db
cd /opt/www/main && docker compose up -d --build

멱등 스크립트. 기존 DB 가 있으면 DROP 하지 않음. 공휴일은 연도별로 다르므로 settings 화면에서 추가/수정.


백업 / 복구 (안전 절차)

백업

# 단일 DB 덤프 (운영 권장)
docker exec -t <postgres_container_name> \
  pg_dump -U <user> -F c -d orderlist_db \
  > /var/backups/postgres/orderlist_db_$(date +%F).dump

복구 (덮어쓰기 위험 → 사용자 승인 필수)

# 1) 신규 DB로 먼저 복구해 검증
docker exec -i <postgres_container_name> \
  pg_restore -U <user> -d <new_db_name> < backup.dump

# 2) 검증 완료 후 운영 DB 교체 (필요 시)

pg_restore --clean은 기존 객체를 삭제한다. 운영 대상 DB에서 절대 무단 실행 금지.


.env 관련

  • DB 접속 정보(*_HOST, *_PORT, *_USER, *_PASSWORD, *_NAME)는 모두 .env로 관리한다.
  • .envGit에 올리지 않는다. .env.example만 커밋한다.
  • 비밀값 유출이 의심되면 즉시 회전(비밀번호/키 변경)을 진행한다.