feat: v2.2.0 - 중복 카드 문제 해결 및 삭제 기능 개선

### 주요 변경사항

1. 작업 현황 모달 중복 카드 문제 근본 해결
   - monthlyStatusModel.getDailyWorkerStatus() 리팩토링
   - 집계 테이블 대신 daily_work_reports에서 직접 조회
   - GROUP BY로 작업자별 1개 카드 보장

2. 삭제 권한 강화
   - 작업보고서 삭제는 그룹장/시스템/관리자만 가능
   - 권한 없는 사용자는 403 에러 반환

3. 작업 입력 UI 개선
   - 작업 항목 삭제 버튼 스타일 개선 (이모지 + 빨간색)
   - 삭제 버튼 호버 효과 추가

4. 작업 현황 모달에 삭제 기능 추가
   - 관리자/그룹장만 삭제 버튼 표시
   - 작업자의 해당 날짜 전체 작업 삭제 가능

5. 시놀로지 배포 스크립트 추가
   - update.sh: DB 보존하면서 코드만 업데이트
   - 안전한 배포 절차 자동화
This commit is contained in:
Hyungi Ahn
2025-12-02 13:33:24 +09:00
parent a9bce9d20b
commit a2669e08c4
16 changed files with 420 additions and 64 deletions

View File

@@ -41,36 +41,47 @@ class MonthlyStatusModel {
}
// 특정 날짜의 작업자별 상태 조회 (모달용)
// ✅ 리팩토링: 집계 테이블 대신 daily_work_reports에서 직접 조회 (중복 문제 완전 해결)
static async getDailyWorkerStatus(date) {
const db = await getDb();
try {
// 중복 방지: worker_id와 date로 그룹화하고 최신 데이터만 조회
const [rows] = await db.execute(`
// daily_work_reports에서 직접 집계하여 조회 (중복 없음 보장)
const [rows] = await db.query(`
SELECT
mws.worker_id,
dwr.worker_id,
w.worker_name,
w.job_type,
MAX(mws.year) as year,
MAX(mws.month) as month,
mws.date,
SUM(mws.total_work_hours) as total_work_hours,
SUM(mws.actual_work_hours) as actual_work_hours,
SUM(mws.vacation_hours) as vacation_hours,
SUM(mws.total_work_count) as total_work_count,
SUM(mws.regular_work_count) as regular_work_count,
SUM(mws.error_work_count) as error_work_count,
MAX(mws.work_status) as work_status,
MAX(mws.has_vacation) as has_vacation,
MAX(mws.has_error) as has_error,
MAX(mws.has_issues) as has_issues,
MAX(mws.last_updated) as last_updated
FROM monthly_worker_status mws
JOIN workers w ON mws.worker_id = w.worker_id
WHERE mws.date = ?
GROUP BY mws.worker_id, mws.date, w.worker_name, w.job_type
YEAR(?) as year,
MONTH(?) as month,
? as date,
COALESCE(SUM(dwr.work_hours), 0) as total_work_hours,
COALESCE(SUM(CASE WHEN dwr.project_id != 13 THEN dwr.work_hours ELSE 0 END), 0) as actual_work_hours,
COALESCE(SUM(CASE WHEN dwr.project_id = 13 THEN dwr.work_hours ELSE 0 END), 0) as vacation_hours,
COUNT(*) as total_work_count,
COUNT(CASE WHEN dwr.project_id != 13 AND dwr.work_status_id != 2 THEN 1 END) as regular_work_count,
COUNT(CASE WHEN dwr.work_status_id = 2 THEN 1 END) as error_work_count,
CASE
WHEN MAX(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) = 1 THEN 'error'
WHEN SUM(dwr.work_hours) > 12 THEN 'overtime-warning'
WHEN SUM(dwr.work_hours) > 8 THEN 'overtime'
WHEN SUM(dwr.work_hours) = 8 THEN 'complete'
WHEN SUM(dwr.work_hours) > 0 THEN 'partial'
ELSE 'incomplete'
END as work_status,
MAX(CASE WHEN dwr.project_id = 13 THEN 1 ELSE 0 END) as has_vacation,
MAX(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as has_error,
CASE
WHEN SUM(dwr.work_hours) < 8 AND SUM(dwr.work_hours) > 0 THEN 1
ELSE 0
END as has_issues,
MAX(dwr.created_at) as last_updated
FROM daily_work_reports dwr
JOIN workers w ON dwr.worker_id = w.worker_id
WHERE dwr.report_date = ?
GROUP BY dwr.worker_id, w.worker_name, w.job_type
ORDER BY w.worker_name ASC
`, [date]);
`, [date, date, date, date]);
return rows;
} catch (error) {