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,