feat(tkqc): 관리함 이슈 프로젝트 변경 + cause_person 필드명 버그 수정
- 모바일/데스크톱 관리함에서 이슈 소속 프로젝트 변경 가능 - 프로젝트 변경 시 sequence_no 자동 재계산 (DB 함수 사용) - in_progress 상태에서만 변경 허용 (프론트+백엔드 이중 제한) - cause_person → responsible_person_detail 필드명 불일치 수정 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
<button class="m-sheet-close" onclick="closeSheet('editMgmt')"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<div class="m-sheet-body">
|
||||
<div class="m-form-group">
|
||||
<label class="m-label"><i class="fas fa-folder" style="color:#8b5cf6;margin-right:4px"></i>프로젝트</label>
|
||||
<select id="editProject" class="m-select"><option value="">선택하세요</option></select>
|
||||
</div>
|
||||
<div class="m-form-group">
|
||||
<label class="m-label"><i class="fas fa-lightbulb" style="color:#eab308;margin-right:4px"></i>해결방안 (확정)</label>
|
||||
<textarea id="editManagementComment" class="m-textarea" rows="3" placeholder="확정된 해결 방안을 입력하세요..."></textarea>
|
||||
|
||||
@@ -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 = '<option value="">선택하세요</option>';
|
||||
projects.forEach(function (p) {
|
||||
projSel.innerHTML += '<option value="' + p.id + '"' + (p.id == issue.project_id ? ' selected' : '') + '>' + escapeHtml(p.project_name || p.job_no) + '</option>';
|
||||
});
|
||||
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
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">프로젝트</label>
|
||||
<input type="text" value="${project ? project.project_name : '-'}" class="w-full px-3 py-2 bg-gray-100 border border-gray-300 rounded-lg text-sm" readonly>
|
||||
<select id="edit-issue-project-${issue.id}" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500"${issue.review_status === 'completed' ? ' disabled' : ''}>
|
||||
<option value="">선택하세요</option>
|
||||
${(window._cachedProjects || []).map(p => `<option value="${p.id}"${p.id == issue.project_id ? ' selected' : ''}>${p.project_name || p.job_no}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">부적합명</label>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user