550867ba0c
[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 안내
37 lines
1.6 KiB
SQL
37 lines
1.6 KiB
SQL
-- =====================================================================
|
|
-- expense_db 마이그레이션 #002 — 결재 워크플로 + 첨부파일
|
|
-- =====================================================================
|
|
-- 실행:
|
|
-- docker exec -i postgres-db psql -U postgres -d expense_db \
|
|
-- < scripts/sql/expense_db_002_workflow_attachments.sql
|
|
--
|
|
-- 멱등. init.sql 도 동일 변경을 포함하므로 신규 설치는 init 하나로 충분.
|
|
-- 이미 운영 중인 DB 에만 별도로 적용한다.
|
|
-- =====================================================================
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
-- expense_items 컬럼 추가
|
|
ALTER TABLE expense_items
|
|
ADD COLUMN IF NOT EXISTS approver_email TEXT,
|
|
ADD COLUMN IF NOT EXISTS decided_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS reject_reason TEXT;
|
|
|
|
-- 첨부파일 테이블
|
|
CREATE TABLE IF NOT EXISTS expense_attachments (
|
|
id TEXT PRIMARY KEY,
|
|
item_id TEXT NOT NULL REFERENCES expense_items(id) ON DELETE CASCADE,
|
|
owner TEXT NOT NULL,
|
|
kind TEXT NOT NULL CHECK (kind IN ('receipt', 'other')),
|
|
filename TEXT NOT NULL,
|
|
stored_path TEXT NOT NULL,
|
|
content_type TEXT NOT NULL DEFAULT 'application/octet-stream',
|
|
size_bytes BIGINT NOT NULL DEFAULT 0 CHECK (size_bytes >= 0),
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_attach_item ON expense_attachments (item_id);
|
|
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON expense_attachments TO expense_app;
|
|
|
|
SELECT 'migration 002 applied' AS status;
|