diff --git a/system3-nonconformance/api/routers/issues.py b/system3-nonconformance/api/routers/issues.py index a8d1b75..4536aff 100644 --- a/system3-nonconformance/api/routers/issues.py +++ b/system3-nonconformance/api/routers/issues.py @@ -1,5 +1,6 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session +from sqlalchemy import text, func from typing import List, Optional from datetime import datetime @@ -140,6 +141,20 @@ async def update_issue( # 업데이트 update_data = issue_update.dict(exclude_unset=True) + # 프로젝트 변경 시 sequence_no 재계산 + if "project_id" in update_data and update_data["project_id"] != issue.project_id: + if issue.review_status and issue.review_status != ReviewStatus.in_progress: + raise HTTPException(status_code=400, detail="진행 중 상태에서만 프로젝트를 변경할 수 있습니다") + try: + new_seq = db.execute(text("SELECT generate_project_sequence_no(:pid)"), + {"pid": update_data["project_id"]}).scalar() + update_data["project_sequence_no"] = new_seq + except Exception: + max_seq = db.query(func.coalesce(func.max(Issue.project_sequence_no), 0)).filter( + Issue.project_id == update_data["project_id"] + ).scalar() + update_data["project_sequence_no"] = max_seq + 1 + # 사진 업데이트 처리 (최대 5장) - 새 사진 저장 후 기존 사진 삭제 (안전) old_photos_to_delete = [] for i in range(1, 6): diff --git a/system3-nonconformance/web/m/management.html b/system3-nonconformance/web/m/management.html index 8ce03a9..c7257f3 100644 --- a/system3-nonconformance/web/m/management.html +++ b/system3-nonconformance/web/m/management.html @@ -65,6 +65,10 @@
+
+ + +
diff --git a/system3-nonconformance/web/static/js/m/m-management.js b/system3-nonconformance/web/static/js/m/m-management.js index fe397bd..1b441ac 100644 --- a/system3-nonconformance/web/static/js/m/m-management.js +++ b/system3-nonconformance/web/static/js/m/m-management.js @@ -253,6 +253,14 @@ function openEditMgmtSheet(issueId) { var issue = issues.find(function (i) { return i.id === issueId; }); if (!issue) return; + // 프로젝트 셀렉트 채우기 + var projSel = document.getElementById('editProject'); + projSel.innerHTML = ''; + projects.forEach(function (p) { + projSel.innerHTML += ''; + }); + projSel.disabled = (issue.review_status === 'completed'); + document.getElementById('editManagementComment').value = cleanManagementComment(issue.management_comment) || ''; document.getElementById('editResponsibleDept').value = issue.responsible_department || ''; document.getElementById('editResponsiblePerson').value = issue.responsible_person || ''; @@ -270,6 +278,22 @@ async function saveManagementEdit() { expected_completion_date: document.getElementById('editExpectedDate').value ? document.getElementById('editExpectedDate').value + 'T00:00:00' : null }; + // 프로젝트 변경 확인 + var newProjectId = parseInt(document.getElementById('editProject').value); + var issue = issues.find(function (i) { return i.id === currentIssueId; }); + if (newProjectId && issue && newProjectId !== issue.project_id) { + // 프로젝트 변경은 /issues/{id} PUT으로 별도 호출 + var projResp = await fetch(API_BASE_URL + '/issues/' + currentIssueId, { + method: 'PUT', + headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, + body: JSON.stringify({ project_id: newProjectId }) + }); + if (!projResp.ok) { + var projErr = await projResp.json(); + throw new Error(projErr.detail || '프로젝트 변경 실패'); + } + } + var resp = await fetch(API_BASE_URL + '/issues/' + currentIssueId + '/management', { method: 'PUT', headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, @@ -358,7 +382,7 @@ function loadAdditionalInfo() { var issue = issues.find(function (i) { return i.id === id; }); if (!issue) return; document.getElementById('additionalCauseDept').value = issue.cause_department || ''; - document.getElementById('additionalCausePerson').value = issue.cause_person || ''; + document.getElementById('additionalCausePerson').value = issue.responsible_person_detail || ''; document.getElementById('additionalCauseDetail').value = issue.cause_detail || ''; } @@ -372,7 +396,7 @@ async function saveAdditionalInfo() { headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, body: JSON.stringify({ cause_department: document.getElementById('additionalCauseDept').value || null, - cause_person: document.getElementById('additionalCausePerson').value.trim() || null, + responsible_person_detail: document.getElementById('additionalCausePerson').value.trim() || null, cause_detail: document.getElementById('additionalCauseDetail').value.trim() || null }) }); diff --git a/system3-nonconformance/web/static/js/pages/issues-management.js b/system3-nonconformance/web/static/js/pages/issues-management.js index 46e7664..4ac0d3a 100644 --- a/system3-nonconformance/web/static/js/pages/issues-management.js +++ b/system3-nonconformance/web/static/js/pages/issues-management.js @@ -66,6 +66,7 @@ async function loadProjects() { if (response.ok) { projects = await response.json(); + window._cachedProjects = projects; updateProjectFilter(); } } catch (error) { @@ -1573,7 +1574,10 @@ function openIssueEditModal(issueId, isCompletionMode = false) {
- +
@@ -1845,6 +1849,27 @@ async function saveIssueFromModal(issueId) { const combinedDescription = title + (detail ? '\n' + detail : ''); + // 프로젝트 변경 처리 + const projectSelect = document.getElementById(`edit-issue-project-${issueId}`); + if (projectSelect) { + const newProjectId = parseInt(projectSelect.value); + const issue = issues.find(i => i.id === issueId); + if (newProjectId && issue && newProjectId !== issue.project_id) { + try { + const projResp = await fetch(`/api/issues/${issueId}`, { + method: 'PUT', + headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ project_id: newProjectId }) + }); + if (!projResp.ok) { + const err = await projResp.json(); + showToast(err.detail || '프로젝트 변경 실패', 'error'); + return; + } + } catch (e) { showToast('프로젝트 변경 실패: ' + e.message, 'error'); return; } + } + } + const requestBody = { final_description: combinedDescription, final_category: category,