# 백엔드 수정 요청 사항 > **작성일**: 2025-08-31 > **요청자**: 프론트엔드 팀 > **우선순위**: 🔴 High (핵심 검색 기능 장애) ## 🚨 긴급 수정 요청 ### **1. 회사 검색 API 버그 (Critical)** #### **문제 상황** - **API**: `GET /api/v1/companies` - **증상**: `search` 파라미터와 `is_active` 파라미터를 동시에 사용할 때 항상 빈 결과 반환 - **영향**: 회사 관리 화면에서 검색 기능 완전 비활성화 상태 - **발생 빈도**: 100% (모든 검색 시도에서 발생) #### **재현 방법** ```bash # 🔴 문제가 되는 API 호출 curl -X GET "http://43.201.34.104:8080/api/v1/companies?page=1&per_page=10&search=wn&is_active=true" \ -H "Authorization: Bearer [JWT_TOKEN]" # 응답: {"data": [], "total": 0, "page": 1, "page_size": 10, "total_pages": 0} ``` #### **예상 원인 분석** ```yaml 가능한_원인: 1. SQL_쿼리_빌더_문제: - "WHERE name LIKE '%search%' AND is_active = true 조합 오류" - "LIKE 연산자와 boolean 조건 충돌" 2. 인덱스_문제: - "name 컬럼 인덱스와 is_active 복합 인덱스 누락" - "검색 성능 최적화 부족" 3. 대소문자_구분: - "PostgreSQL LIKE 연산자 case-sensitive 문제" - "ILIKE 사용 필요 가능성" 4. 파라미터_바인딩: - "Rust sqlx의 파라미터 바인딩 순서 또는 타입 문제" - "Option 처리 오류" ``` #### **예상 수정 위치** ```rust // 📍 예상 파일 위치 /Users/maximilian.j.sul/Documents/flutter/superport_api/src/handlers/company.rs /Users/maximilian.j.sul/Documents/flutter/superport_api/src/services/company_service.rs // 🔧 예상 수정 내용 // 현재 (추정): query = query.filter(companies::is_active.eq(true)) .filter(companies::name.like(format!("%{}%", search))); // 수정 필요: query = query.filter(companies::is_active.eq(is_active)) .filter(companies::name.ilike(format!("%{}%", search))); ``` #### **테스트 케이스** ```yaml 테스트_1_정상_동작_확인: 요청: "GET /companies?page=1&per_page=10" 예상결과: "정상 데이터 반환 (현재 10개 본사 존재 확인)" 테스트_2_is_active_필터만: 요청: "GET /companies?page=1&per_page=10&is_active=true" 예상결과: "활성 회사만 반환" 테스트_3_search_필터만: 요청: "GET /companies?page=1&per_page=10&search=wn" 예상결과: "이름에 'wn'이 포함된 회사 반환" 테스트_4_복합_필터: 요청: "GET /companies?page=1&per_page=10&search=wn&is_active=true" 현재결과: "빈 배열 (버그) ❌" 예상결과: "활성 상태이면서 이름에 'wn'이 포함된 회사 반환 ✅" 테스트_5_대소문자_무관: 요청: "GET /companies?search=WN"과 "GET /companies?search=wn" 예상결과: "동일한 결과 반환 (case-insensitive)" ``` ## 📊 API 스펙 확인 ### **현재 프론트엔드 호출 방식** ```typescript // CompanyRemoteDataSource.getCompanies() const queryParams = { 'page': page, 'per_page': perPage, if (search != null) 'search': search, // ✅ 조건부 포함 if (isActive != null) 'is_active': isActive, // ✅ 조건부 포함 }; // 실제 API 호출 GET /api/v1/companies?page=1&per_page=10&search=wn&is_active=true ``` ### **기대하는 백엔드 응답** ```json { "data": [ { "id": 1, "name": "월드와이드네트웍스", "address": "서울시 강남구 테헤란로 123", "contact_name": "김철수", "contact_phone": "02-1234-5678", "contact_email": "kim@wwn.co.kr", "is_active": true, "is_partner": true, "is_customer": false, "parent_company_id": null, "registered_at": "2024-01-15T09:00:00Z" } ], "total": 1, "page": 1, "page_size": 10, "total_pages": 1 } ``` ## 🔧 요청 수정 사항 ### **1차 수정 (필수)** ```yaml 수정_대상: - "회사 검색 API의 WHERE 조건 로직 수정" - "search + is_active 파라미터 조합 시 정상 작동" 확인_사항: - "LIKE → ILIKE 변경 (대소문자 무관 검색)" - "파라미터 바인딩 순서 확인" - "SQL 쿼리 로그 활성화하여 실제 실행 쿼리 확인" ``` ### **2차 개선 (권장)** ```yaml 성능_최적화: - "name 컬럼 + is_active 복합 인덱스 생성" - "검색 성능 향상" 추가_기능: - "부분 검색 개선 (초성 검색, 공백 무시 등)" - "검색 결과 정렬 옵션 추가" ``` ## 🧪 디버깅 도움 ### **SQL 쿼리 로그 활성화** ```rust // 디버깅을 위한 쿼리 로그 출력 tracing::info!("Executing query with search: {:?}, is_active: {:?}", search, is_active); // 실제 생성된 SQL 출력 (개발 환경) println!("Generated SQL: {}", query.debug_query()); ``` ### **수동 테스트** ```sql -- PostgreSQL에서 직접 테스트 SELECT * FROM companies WHERE name ILIKE '%wn%' AND is_active = true ORDER BY id LIMIT 10; -- 인덱스 확인 \d companies SELECT * FROM pg_indexes WHERE tablename = 'companies'; ``` ## 📞 연락처 - **프론트엔드 담당자**: Claude Code Assistant - **이슈 발견일**: 2025-08-31 - **긴급 연락**: 회사 검색 기능 완전 장애 상태 --- ## 📈 진행 상황 체크리스트 - [ ] 백엔드 개발자 이슈 확인 - [ ] SQL 쿼리 로그 분석 - [ ] 수정 사항 적용 - [ ] 테스트 케이스 검증 - [ ] 프론트엔드 검증 요청 - [ ] 배포 및 모니터링 **⚠️ 참고**: 프론트엔드는 이미 완벽하게 구현되어 있으며, 백엔드 수정 후 즉시 정상 작동할 예정입니다. --- ## 🚨 Critical: Equipment Number vs Serial Number 스키마 불일치 (URGENT) > **발견일**: 2025-09-02 > **우선순위**: 🔴 Critical (데이터 무결성 위험) > **영향**: 장비 관리 핵심 워크플로우 잠재적 실패 ### **Problem Statement** **현재 상황**: 프론트엔드와 백엔드 간 equipment 스키마에 심각한 불일치가 발견됨 ```yaml Frontend_Schema: "✅ 정확한 비즈니스 로직" equipment_number: "회사 내부 관리 번호 (예: EQ001, TOOL-001)" serial_number: "제조사 고유 식별 번호 (예: SN123456789)" Backend_Schema: "❌ 불완전한 구현" serial_number: "제조사 번호만 존재" equipment_number: "필드 자체가 누락됨" ``` ### **Impact Assessment** #### **Business Impact** 🔴 ```yaml Critical_Risks: - "장비 내부 관리 번호 체계 완전 부재" - "프론트엔드가 존재하지 않는 API 필드 요구" - "장비 식별 시스템 혼란 (제조사 번호 vs 회사 번호)" - "재고 관리 워크플로우 신뢰성 저하" Data_Integrity_Issues: - "equipment_number 저장 불가능" - "장비 검색 시 내부 번호 검색 불가능" - "프론트엔드 테스트에서 사용하는 'EQ001' 등 번호 저장 실패" ``` #### **Technical Impact** ⚠️ ```yaml Immediate_Problems: - "EquipmentResponse.equipment_number → 백엔드 매핑 실패" - "장비 생성 시 equipment_number 필드 무시됨" - "프론트엔드 테스트 케이스와 백엔드 스키마 불일치" Future_Risks: - "장비 번호 기반 검색 기능 구현 불가능" - "회사별 장비 번호 체계 구축 불가능" - "바코드 시스템과 내부 번호 연계 불가능" ``` ### **Root Cause Analysis** #### **Backend Missing Implementation** ```rust // 📍 File: /superport_api/src/entities/equipments.rs #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "equipments")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, // ❌ MISSING: equipment_number field #[sea_orm(unique)] pub serial_number: String, // 제조사 번호만 존재 // ... other fields } ``` #### **Frontend Correct Implementation** ```dart // 📍 File: equipment_response.dart @freezed class EquipmentResponse with _$EquipmentResponse { const factory EquipmentResponse({ @JsonKey(name: 'equipment_number') required String equipmentNumber, // ✅ 존재 @JsonKey(name: 'serial_number') String? serialNumber, // ✅ 존재 // ... other fields }) = _EquipmentResponse; } ``` ### **Required Database Schema Changes** #### **Migration Script** ```sql -- ⚠️ CRITICAL: Equipment 테이블 스키마 수정 필요 -- Step 1: equipment_number 컬럼 추가 ALTER TABLE equipments ADD COLUMN equipment_number VARCHAR(255) NOT NULL DEFAULT ''; -- Step 2: UNIQUE 제약조건 추가 (중복 방지) ALTER TABLE equipments ADD CONSTRAINT uk_equipments_equipment_number UNIQUE (equipment_number); -- Step 3: serial_number를 nullable로 변경 (제조사 번호는 선택적) ALTER TABLE equipments MODIFY COLUMN serial_number VARCHAR(255) NULL; -- Step 4: 인덱스 생성 (검색 성능 최적화) CREATE INDEX idx_equipments_equipment_number ON equipments(equipment_number); CREATE INDEX idx_equipments_serial_number ON equipments(serial_number); -- Step 5: 기존 데이터 migration (임시 equipment_number 생성) UPDATE equipments SET equipment_number = CONCAT('EQ', LPAD(id::TEXT, 6, '0')) WHERE equipment_number = ''; -- 예: EQ000001, EQ000002, EQ000003... ``` ### **Required Rust Code Changes** #### **Entity Update** ```rust // 📍 File: /superport_api/src/entities/equipments.rs #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "equipments")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, // 🆕 ADD: Equipment Number (회사 내부 관리 번호) #[sea_orm(unique)] pub equipment_number: String, // 🔧 MODIFY: Serial Number (제조사 번호, nullable) pub serial_number: Option, // ... existing fields } ``` #### **DTO Update** ```rust // 📍 File: /superport_api/src/dto/equipment.rs #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EquipmentResponse { pub id: i32, // 🆕 ADD: Equipment Number pub equipment_number: String, // 🔧 MODIFY: Serial Number (Optional) pub serial_number: Option, // ... existing fields } #[derive(Debug, Clone, Deserialize, Validate)] pub struct CreateEquipmentRequest { // 🆕 ADD: Equipment Number validation #[validate(length(min = 1, max = 255, message = "장비번호는 1-255자 사이여야 합니다"))] pub equipment_number: String, // 🔧 MODIFY: Serial Number (Optional) #[validate(length(max = 255, message = "시리얼번호는 255자 이하여야 합니다"))] pub serial_number: Option, // ... existing fields } ``` #### **Service Logic Update** ```rust // 📍 File: /superport_api/src/services/equipment_service.rs // 🔧 MODIFY: Duplicate check for equipment_number pub async fn create(&self, req: CreateEquipmentRequest) -> AppResult { // 🆕 equipment_number 중복 체크 let existing = Equipment::find() .filter(equipments::Column::EquipmentNumber.eq(&req.equipment_number)) .filter(equipments::Column::IsDeleted.eq(false)) .one(&self.db) .await?; if existing.is_some() { return Err(AppError::DuplicateError( format!("[equipments,equipment_number]: 장비번호 '{}' 가 이미 존재합니다", req.equipment_number) )); } // 🔧 serial_number 중복 체크 (optional) if let Some(ref serial_number) = req.serial_number { let existing = Equipment::find() .filter(equipments::Column::SerialNumber.eq(serial_number)) .filter(equipments::Column::IsDeleted.eq(false)) .one(&self.db) .await?; if existing.is_some() { return Err(AppError::DuplicateError( format!("[equipments,serial_number]: 시리얼번호 '{}' 가 이미 존재합니다", serial_number) )); } } let equipment = ActiveModel { equipment_number: Set(req.equipment_number), serial_number: Set(req.serial_number), // ... other fields }; // ... rest of create logic } // 🆕 ADD: Find by equipment number pub async fn find_by_equipment_number(&self, equipment_number: &str) -> AppResult { let equipment = Equipment::find() .filter(equipments::Column::EquipmentNumber.eq(equipment_number)) .filter(equipments::Column::IsDeleted.eq(false)) .one(&self.db) .await? .ok_or_else(|| AppError::NotFound( format!("[equipments,equipment_number]: 장비를 찾을 수 없습니다: {}", equipment_number) ))?; self.to_response_with_joins(equipment).await } // 🔧 MODIFY: Enhanced search to include both numbers pub async fn find_all(&self, query: EquipmentQuery) -> AppResult> { // ... existing code // 검색 필터 (equipment_number, serial_number, barcode 모두 포함) if let Some(search) = query.search { q = q.filter( equipments::Column::EquipmentNumber.contains(&search) .or(equipments::Column::SerialNumber.contains(&search)) .or(equipments::Column::Barcode.contains(&search)) ); } // ... rest of logic } ``` ### **New API Endpoints Required** ```yaml New_Search_Endpoints: - "GET /equipments/equipment-number/{equipment_number}" - "GET /equipments/serial/{serial_number}" (기존) - "GET /equipments/search?q={query}" (통합 검색) Enhanced_Validation: - "장비번호 중복 검사 강화" - "시리얼번호 중복 검사 (optional)" - "두 번호 동시 중복 방지" ``` ### **Migration Strategy & Risk Mitigation** #### **Phase 1: Database Schema Migration** (1-2 hours) ```yaml Steps: 1. "Backup current database" 2. "Run migration script (equipment_number 컬럼 추가)" 3. "Generate equipment_number for existing records" 4. "Validate migration success" Risk_Mitigation: - "Transaction-based migration (rollback 가능)" - "기존 데이터 100% 보존" - "Default equipment_number 자동 생성" ``` #### **Phase 2: Backend Code Update** (2-3 hours) ```yaml Files_to_Update: - "src/entities/equipments.rs" - "src/dto/equipment.rs" - "src/services/equipment_service.rs" - "src/handlers/equipments.rs" Testing_Required: - "Unit tests for new fields" - "Integration tests for API endpoints" - "Frontend compatibility testing" ``` #### **Phase 3: Validation & Deployment** (1 hour) ```yaml Validation_Checklist: - "✅ equipment_number 생성/조회/수정 정상" - "✅ serial_number nullable 처리 정상" - "✅ 중복 검사 정상 작동" - "✅ 프론트엔드 호환성 100%" - "✅ 기존 데이터 무결성 유지" ``` ### **Test Cases for Validation** ```yaml Test_1_Create_Equipment_With_Both_Numbers: Request: | POST /equipments { "equipment_number": "EQ001", "serial_number": "SN123456789", "companies_id": 1, "models_id": 1 } Expected: "201 Created, both fields saved correctly" Test_2_Create_Equipment_Without_Serial: Request: | POST /equipments { "equipment_number": "EQ002", "companies_id": 1, "models_id": 1 } Expected: "201 Created, serial_number = null" Test_3_Duplicate_Equipment_Number: Request: | POST /equipments { "equipment_number": "EQ001", // 중복 "companies_id": 1 } Expected: "409 Conflict, equipment_number duplicate error" Test_4_Search_By_Equipment_Number: Request: "GET /equipments/equipment-number/EQ001" Expected: "200 OK, correct equipment returned" Test_5_Unified_Search: Request: "GET /equipments?search=EQ001" Expected: "200 OK, finds equipment by equipment_number" Request: "GET /equipments?search=SN123456789" Expected: "200 OK, finds equipment by serial_number" Test_6_Frontend_Compatibility: Request: "Frontend equipment list rendering" Expected: "Both equipment_number and serial_number display correctly" ``` ### **Communication with Backend Developer** #### **Urgency Level: CRITICAL** 🔴 ```yaml Message_to_Backend_Developer: Subject: "🚨 CRITICAL: Equipment Schema Missing Equipment Number Field" Problem: | Frontend expects equipment_number field but backend only has serial_number. This creates data integrity issues and prevents proper equipment management. Business_Impact: | - Cannot store company-assigned equipment numbers (EQ001, TOOL-001, etc.) - Equipment identification system incomplete - Frontend tests failing due to schema mismatch Required_Action: | 1. Add equipment_number column to equipments table (UNIQUE, NOT NULL) 2. Make serial_number optional (manufacturer number not always available) 3. Update Rust entities and DTOs to match 4. Add equipment_number search endpoints Timeline: "ASAP - This blocks proper equipment management workflow" Support: "Frontend is ready and fully compatible - just needs backend schema fix" ``` --- ## 🚀 정식 버전 개선 요청사항 > **목적**: 현재는 프론트엔드에서 100% API 활용하지만, 정식버전에서는 백엔드 자동화로 개선 > **우선순위**: 🟡 Medium (현재 버전 안정화 후 적용) ### **1. Equipment History 자동 생성** #### **Equipment 생성 시 자동 입고 이력** ```rust // 현재 방식 (프론트엔드 2단계 호출) // 1단계: POST /equipments // 2단계: POST /equipment-history (입고 처리) // 💡 개선 요청: Equipment Service 내부에서 자동 처리 pub async fn create(&self, req: CreateEquipmentRequest) -> AppResult { let txn = self.db.begin().await?; // 장비 생성 let equipment = equipment.insert(&txn).await?; // 🆕 자동 입고 이력 생성 if let Some(warehouse_id) = req.initial_warehouse_id { let history_req = CreateEquipmentHistoryRequest { equipments_id: Some(equipment.id), warehouses_id: Some(warehouse_id), transaction_type: "I".to_string(), // 입고 quantity: req.initial_quantity.unwrap_or(1), transacted_at: Utc::now().into(), remark: Some("장비 등록 시 자동 입고".to_string()), company_ids: req.companies_id.map(|id| vec![id]), }; // Equipment History 자동 생성 self.create_history_internal(history_req, &txn).await?; } txn.commit().await?; Ok(equipment) } ``` #### **Equipment 상태 변경 시 자동 이력** ```rust // 💡 개선 요청: 상태 변경 감지 및 자동 이력 생성 pub async fn update(&self, id: i32, req: UpdateEquipmentRequest) -> AppResult { let old_equipment = self.find_by_id(id).await?; // 장비 정보 업데이트 let updated_equipment = /* 업데이트 로직 */; // 🆕 중요 변경사항 자동 이력 생성 if let Some(new_warehouse) = req.warehouses_id { if old_equipment.warehouses_id != Some(new_warehouse) { // 창고 이동 이력 자동 생성 self.create_transfer_history(id, old_equipment.warehouses_id, new_warehouse).await?; } } if let Some(new_company) = req.companies_id { if old_equipment.companies_id != Some(new_company) { // 소유권 이전 이력 자동 생성 self.create_ownership_history(id, old_equipment.companies_id, new_company).await?; } } Ok(updated_equipment) } ``` #### **Equipment 삭제 시 자동 폐기 이력** ```rust // 💡 개선 요청: Soft Delete 시 폐기 이력 자동 생성 pub async fn delete(&self, id: i32) -> AppResult<()> { let equipment = self.find_by_id(id).await?; // 🆕 폐기 이력 자동 생성 let disposal_history = CreateEquipmentHistoryRequest { equipments_id: Some(id), warehouses_id: equipment.current_warehouse_id, transaction_type: "D".to_string(), // 폐기 quantity: -equipment.current_quantity, transacted_at: Utc::now().into(), remark: Some("장비 삭제 시 자동 폐기 처리".to_string()), company_ids: equipment.companies_id.map(|id| vec![id]), }; self.create_history_internal(disposal_history, &self.db).await?; // Soft Delete 실행 let mut equipment: ActiveModel = equipment.into(); equipment.is_deleted = Set(true); equipment.deleted_at = Set(Some(Utc::now().into())); equipment.update(&self.db).await?; Ok(()) } ``` ### **2. 비즈니스 로직 백엔드 이관** #### **재고 수량 자동 계산** ```yaml 현재_방식: "프론트엔드에서 GET /equipment-history/stock-status 호출 후 계산" 개선_요청: 새로운_API: "GET /equipments/{id}/current-stock" 응답_구조: | { "equipment_id": 1, "current_quantity": 15, "current_warehouse": { "id": 3, "name": "중앙창고" }, "last_transaction": { "type": "I", "quantity": 5, "date": "2025-08-31T10:00:00Z" }, "total_in": 100, "total_out": 85, "reserved_quantity": 3 } 백엔드_로직: - "Equipment History에서 실시간 재고 계산" - "예약 수량 고려" - "캐싱으로 성능 최적화" ``` #### **자동 알림 시스템** ```yaml 개선_요청: 보증_만료_알림: - "warranty_ended_at 30일 전 자동 알림" - "Equipment에 보증 만료 플래그 추가" 재고_부족_알림: - "minimum_quantity 설정 기능" - "재고 부족 시 자동 알림" 정비_스케줄_알림: - "마지막 정비일 기준 자동 스케줄링" - "정비 예정 장비 목록 API" ``` ### **3. 고급 기능 요청** #### **Bulk Operations** ```yaml 대량_작업_API: - "POST /equipments/bulk-create" (Excel 업로드) - "POST /equipments/bulk-update" (일괄 수정) - "POST /equipment-history/bulk-transfer" (일괄 이동) 성능_최적화: - "트랜잭션 배치 처리" - "백그라운드 작업 큐" - "진행률 조회 API" ``` #### **고급 검색 및 필터링** ```yaml 개선_요청: 전체_텍스트_검색: - "장비명, 시리얼, 바코드, 회사명 통합 검색" - "검색 결과 관련도 순 정렬" 고급_필터: - "날짜 범위 필터 (구매일, 보증 기간 등)" - "금액 범위 필터" - "다중 상태 선택" - "사용자 정의 태그 시스템" 검색_성능: - "Full-text search 인덱스" - "검색 결과 캐싱" - "자동완성 API" ``` ### **4. 데이터 일관성 및 무결성** #### **제약 조건 강화** ```sql -- 💡 개선 요청: 데이터베이스 레벨 제약 조건 ALTER TABLE equipment_history ADD CONSTRAINT chk_transaction_type CHECK (transaction_type IN ('I', 'O', 'R', 'D')); ALTER TABLE equipment_history ADD CONSTRAINT chk_positive_quantity CHECK ( (transaction_type = 'I' AND quantity > 0) OR (transaction_type = 'O' AND quantity > 0) OR (transaction_type = 'R' AND quantity > 0) OR (transaction_type = 'D' AND quantity >= 0) ); -- 재고 마이너스 방지 CREATE OR REPLACE FUNCTION check_stock_availability() RETURNS TRIGGER AS $$ BEGIN -- 출고/임대 시 재고 확인 IF NEW.transaction_type IN ('O', 'R') THEN -- 현재 재고 < 요청 수량이면 에러 END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; ``` #### **감사 로그 (Audit Log)** ```yaml 개선_요청: 모든_변경_추적: - "누가, 언제, 무엇을, 왜 변경했는지 기록" - "변경 전후 값 저장" - "IP 주소, User Agent 기록" 새로운_테이블: audit_logs: - "table_name, record_id, action_type" - "old_values, new_values (JSON)" - "user_id, ip_address, user_agent" - "created_at" 자동_생성: - "모든 CUD 작업 시 자동 감사 로그 생성" - "민감한 필드 변경 시 특별 표시" ``` ### **5. 마이그레이션 계획** #### **단계별 이관 전략** ```yaml Phase_1_준비: - "현재 프론트엔드 방식 유지하며 백엔드 기능 병렬 개발" - "Feature Flag로 새 기능 점진적 활성화" Phase_2_히스토리_자동화: - "Equipment History 자동 생성 기능 적용" - "기존 수동 생성 방식과 병행" Phase_3_비즈니스_로직_이관: - "재고 계산, 알림 시스템 백엔드 이관" - "프론트엔드 코드 단순화" Phase_4_고급_기능: - "Bulk Operations, 고급 검색 기능 추가" - "성능 최적화 및 모니터링" 호환성_보장: - "기존 API 버전 유지" - "점진적 마이그레이션" - "롤백 계획 수립" ``` --- ## 📋 정식 버전 체크리스트 ### **개발 우선순위** - [ ] **Phase 1**: 현재 프론트엔드 100% API 활용 완성 - [ ] **Phase 2**: Equipment History 자동 생성 기능 - [ ] **Phase 3**: 비즈니스 로직 백엔드 이관 - [ ] **Phase 4**: 고급 기능 및 성능 최적화 ### **품질 보장** - [ ] **데이터 일관성**: 제약 조건 및 트랜잭션 강화 - [ ] **감사 추적**: 모든 변경사항 로깅 - [ ] **성능 최적화**: 인덱스 및 캐싱 전략 - [ ] **보안 강화**: 권한 체계 및 입력 검증 --- **📝 작성일**: 2025-08-31 **📊 현재 상태**: 백엔드 API 53개 중 프론트엔드 활용 목표 100% **🎯 최종 목표**: 백엔드 중심의 자동화된 ERP 시스템