Files
dbx-main/docs/DATABASES.md
T
king 550867ba0c feat(expense+admin): 결재 워크플로 + 첨부 + 집계 + 엑셀 + 사용자 직접 등록
[admin]
- POST /api/users 사용자 직접 등록 (이메일만으로). UserStore.create_user 추가
- admin.html: 등록 폼 + 삭제 버튼 + 한글 라벨 + 신규 권한 표시
- 신규 권한 키: expense_approver, vacation_approver (APPROVER_KEYS)
- is_approver(user_rec, kind) 헬퍼

[expense 워크플로]
- 상태 전이: 작성중 → 제출 → 승인/반려 → 정산완료
- API: /api/items/{id}/{submit,revert,approve,reject,settle}
- 권한: submit/revert=owner, approve/reject/settle=expense_approver 또는 admin
- expense_items 컬럼: approver_email, decided_at, reject_reason
- 작성중/반려 상태에서만 수정/삭제/첨부 가능

[expense 첨부]
- 영수증/기타 (kind: receipt|other). 최대 20MB, 확장자 화이트리스트
- 저장: DATA_DIR/uploads/expense/{item_id}/{att_id}_{filename}
- expense_attachments 테이블 (CASCADE on item delete)
- API: 업로드/목록/다운로드/삭제

[expense 집계]
- /expense/reports: 연도별 월×카테고리 피벗 표

[expense 엑셀]
- GET /expense/api/export.xlsx?from&to&scope=mine|all
- scope=all 은 승인자/관리자만

[DDL]
- expense_db_init.sql: 신규 컬럼/테이블 포함 (멱등)
- expense_db_002_workflow_attachments.sql: 운영 DB 마이그레이션용

[deps]
- openpyxl>=3.1, python-multipart>=0.0.20

[docs]
- DATABASES.md: 워크플로 다이어그램, attachments 테이블, 마이그레이션
- PROJECT_OVERVIEW.md: 권한 키 표
- DEPLOYMENT.md: DATA_DIR/uploads 안내
2026-05-29 02:52:44 +09:00

219 lines
6.6 KiB
Markdown

# Databases
## PostgreSQL DB 목록
| DB명 | 용도 |
| --- | --- |
| `itemcode_db` | 상품코드, 단품/세트 구성, 채널 ↔ 사내 코드 매칭 |
| `orderlist_db` | 주문 수집·분석·관리 (구 `orderlist_app`) |
| `return_db` | 반품·교환·CS 데이터 |
| `expense_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. 사용자 명시 승인
---
## 자주 쓰는 점검 명령
```bash
# 컨테이너/네트워크
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 에 신규 컬럼/테이블 적용:
```bash
docker exec -i postgres-db psql -U postgres -d expense_db \
< scripts/sql/expense_db_002_workflow_attachments.sql
```
신규 설치는 `expense_db_init.sql` 하나로 충분 (둘 다 멱등).
### 운영 서버 초기화 (1회)
```bash
# 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 마이그레이션
```bash
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 은 건드리지 않는다.
---
## 백업 / 복구 (안전 절차)
### 백업
```bash
# 단일 DB 덤프 (운영 권장)
docker exec -t <postgres_container_name> \
pg_dump -U <user> -F c -d orderlist_db \
> /var/backups/postgres/orderlist_db_$(date +%F).dump
```
### 복구 (덮어쓰기 위험 → 사용자 승인 필수)
```bash
# 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`로 관리한다.
- `.env`**Git에 올리지 않는다**. `.env.example`만 커밋한다.
- 비밀값 유출이 의심되면 즉시 회전(비밀번호/키 변경)을 진행한다.