feat: 리비전 관리 시스템 및 구매확정 기능 구현

- 리비전 관리 라우터 및 서비스 추가 (revision_management.py, revision_comparison_service.py, revision_session_service.py)
- 구매확정 기능 구현: materials 테이블에 purchase_confirmed 필드 추가 및 업데이트 로직
- 리비전 비교 로직 구현: 구매확정된 자재 기반으로 신규/변경 자재 자동 분류
- 데이터베이스 스키마 확장: revision_sessions, revision_material_changes, inventory_transfers 테이블 추가
- 구매신청 생성 시 자재 상세 정보 저장 및 purchase_confirmed 자동 업데이트
- 프론트엔드: 리비전 관리 컴포넌트 및 hooks 추가
- 파일 목록 조회 API 추가 (/files/list)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2025-12-06 07:36:44 +09:00
parent c258303bb7
commit 17843e285f
12 changed files with 2759 additions and 83 deletions

View File

@@ -189,6 +189,62 @@ def add_missing_columns(cursor):
print(f"✅ material_purchase_tracking.{column_name} 컬럼 추가 완료")
else:
print(f"✅ material_purchase_tracking.{column_name} 컬럼이 이미 존재합니다")
# purchase_requests 테이블에 누락된 컬럼들 확인 및 추가
purchase_requests_columns = {
'file_id': 'INTEGER REFERENCES files(id)',
'category': 'VARCHAR(50)',
'material_count': 'INTEGER DEFAULT 0',
'excel_file_path': 'VARCHAR(500)'
}
for column_name, column_type in purchase_requests_columns.items():
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'purchase_requests' AND column_name = %s;
""", (column_name,))
if not cursor.fetchone():
print(f" purchase_requests 테이블에 {column_name} 컬럼 추가 중...")
cursor.execute(f"""
ALTER TABLE purchase_requests ADD COLUMN {column_name} {column_type};
""")
print(f"✅ purchase_requests.{column_name} 컬럼 추가 완료")
else:
print(f"✅ purchase_requests.{column_name} 컬럼이 이미 존재합니다")
# purchase_request_items 테이블에 누락된 컬럼들 확인 및 추가
purchase_request_items_columns = {
'user_requirement': 'TEXT',
'description': 'TEXT',
'category': 'VARCHAR(50)',
'subcategory': 'VARCHAR(100)',
'material_grade': 'VARCHAR(50)',
'size_spec': 'VARCHAR(50)',
'drawing_name': 'VARCHAR(100)',
'notes': 'TEXT',
'is_ordered': 'BOOLEAN DEFAULT FALSE',
'is_received': 'BOOLEAN DEFAULT FALSE'
}
for column_name, column_type in purchase_request_items_columns.items():
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'purchase_request_items' AND column_name = %s;
""", (column_name,))
if not cursor.fetchone():
print(f" purchase_request_items 테이블에 {column_name} 컬럼 추가 중...")
cursor.execute(f"""
ALTER TABLE purchase_request_items ADD COLUMN {column_name} {column_type};
""")
print(f"✅ purchase_request_items.{column_name} 컬럼 추가 완료")
else:
print(f"✅ purchase_request_items.{column_name} 컬럼이 이미 존재합니다")
print("✅ 모든 누락된 컬럼 추가 완료!")
except Exception as e:
print(f"⚠️ 컬럼 추가 실패: {e}")
@@ -328,7 +384,11 @@ def create_missing_tables():
CREATE TABLE purchase_requests (
request_id SERIAL PRIMARY KEY,
request_no VARCHAR(50) UNIQUE NOT NULL,
file_id INTEGER REFERENCES files(id),
job_no VARCHAR(50) NOT NULL,
category VARCHAR(50),
material_count INTEGER DEFAULT 0,
excel_file_path VARCHAR(500),
project_name VARCHAR(200),
requested_by INTEGER REFERENCES users(user_id),
requested_by_username VARCHAR(100),
@@ -387,6 +447,163 @@ def create_missing_tables():
print("✅ purchase_request_items 테이블 생성 완료")
else:
print("✅ purchase_request_items 테이블 이미 존재")
# 5. revision_sessions 테이블
print("📋 5. revision_sessions 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'revision_sessions'
);
""")
if not cursor.fetchone()[0]:
print(" revision_sessions 테이블 생성 중...")
cursor.execute("""
CREATE TABLE revision_sessions (
id SERIAL PRIMARY KEY,
job_no VARCHAR(50) NOT NULL,
current_file_id INTEGER REFERENCES files(id),
previous_file_id INTEGER REFERENCES files(id),
current_revision VARCHAR(20) NOT NULL,
previous_revision VARCHAR(20) NOT NULL,
status VARCHAR(20) DEFAULT 'processing',
total_materials INTEGER DEFAULT 0,
processed_materials INTEGER DEFAULT 0,
added_count INTEGER DEFAULT 0,
removed_count INTEGER DEFAULT 0,
changed_count INTEGER DEFAULT 0,
unchanged_count INTEGER DEFAULT 0,
purchase_cancel_count INTEGER DEFAULT 0,
inventory_transfer_count INTEGER DEFAULT 0,
additional_purchase_count INTEGER DEFAULT 0,
created_by VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP
);
CREATE INDEX idx_revision_sessions_job_no ON revision_sessions(job_no);
CREATE INDEX idx_revision_sessions_status ON revision_sessions(status);
""")
print("✅ revision_sessions 테이블 생성 완료")
else:
print("✅ revision_sessions 테이블 이미 존재")
# 6. revision_material_changes 테이블
print("📋 6. revision_material_changes 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'revision_material_changes'
);
""")
if not cursor.fetchone()[0]:
print(" revision_material_changes 테이블 생성 중...")
cursor.execute("""
CREATE TABLE revision_material_changes (
id SERIAL PRIMARY KEY,
session_id INTEGER REFERENCES revision_sessions(id) ON DELETE CASCADE,
material_id INTEGER REFERENCES materials(id),
previous_material_id INTEGER,
material_description TEXT NOT NULL,
category VARCHAR(50) NOT NULL,
change_type VARCHAR(20) NOT NULL,
previous_quantity NUMERIC(10,3),
current_quantity NUMERIC(10,3),
quantity_difference NUMERIC(10,3),
purchase_status VARCHAR(20) NOT NULL,
purchase_confirmed_at TIMESTAMP,
revision_action VARCHAR(30),
action_status VARCHAR(20) DEFAULT 'pending',
processed_by VARCHAR(100),
processed_at TIMESTAMP,
processing_notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_revision_changes_session ON revision_material_changes(session_id);
CREATE INDEX idx_revision_changes_action ON revision_material_changes(revision_action);
CREATE INDEX idx_revision_changes_status ON revision_material_changes(action_status);
""")
print("✅ revision_material_changes 테이블 생성 완료")
else:
print("✅ revision_material_changes 테이블 이미 존재")
# 7. inventory_transfers 테이블
print("📋 7. inventory_transfers 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'inventory_transfers'
);
""")
if not cursor.fetchone()[0]:
print(" inventory_transfers 테이블 생성 중...")
cursor.execute("""
CREATE TABLE inventory_transfers (
id SERIAL PRIMARY KEY,
revision_change_id INTEGER REFERENCES revision_material_changes(id),
material_description TEXT NOT NULL,
category VARCHAR(50) NOT NULL,
quantity NUMERIC(10,3) NOT NULL,
unit VARCHAR(10) NOT NULL,
inventory_location VARCHAR(100),
storage_notes TEXT,
transferred_by VARCHAR(100) NOT NULL,
transferred_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'transferred'
);
CREATE INDEX idx_inventory_transfers_material ON inventory_transfers(material_description);
CREATE INDEX idx_inventory_transfers_date ON inventory_transfers(transferred_at);
""")
print("✅ inventory_transfers 테이블 생성 완료")
else:
print("✅ inventory_transfers 테이블 이미 존재")
# 8. revision_action_logs 테이블
print("📋 8. revision_action_logs 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'revision_action_logs'
);
""")
if not cursor.fetchone()[0]:
print(" revision_action_logs 테이블 생성 중...")
cursor.execute("""
CREATE TABLE revision_action_logs (
id SERIAL PRIMARY KEY,
session_id INTEGER REFERENCES revision_sessions(id),
revision_change_id INTEGER REFERENCES revision_material_changes(id),
action_type VARCHAR(30) NOT NULL,
action_description TEXT,
executed_by VARCHAR(100) NOT NULL,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
result VARCHAR(20) NOT NULL,
result_message TEXT,
result_data JSONB
);
CREATE INDEX idx_revision_logs_session ON revision_action_logs(session_id);
CREATE INDEX idx_revision_logs_type ON revision_action_logs(action_type);
CREATE INDEX idx_revision_logs_date ON revision_action_logs(executed_at);
""")
print("✅ revision_action_logs 테이블 생성 완료")
else:
print("✅ revision_action_logs 테이블 이미 존재")
# 변경사항 커밋
conn.commit()