From 133bfc55b873be0324a0328e913fde79f9b4120e Mon Sep 17 00:00:00 2001 From: king Date: Thu, 25 Jun 2026 13:34:52 +0900 Subject: [PATCH] =?UTF-8?q?feat(project):=20=EC=97=85=EB=AC=B4=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=A7=80=EC=A0=95(=EC=A2=85=EC=9D=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8)=20+=20=ED=83=80=EC=9E=84=EB=9D=BC=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tasks 에 start_time/due_time TIME 컬럼 추가(마이그레이션 003). NULL=종일 - 업무 모달에 '시간 지정' 체크박스 → 체크 시 시작/마감 시간 입력란 표시. 미체크면 종일로 저장(시간 null) - store.validate_time(HH:MM), db create/update + TIME 직렬화('HH:MM') - 타임라인: 시간 있으면 date+time 으로 정확히 배치, 없으면 종일 DB: scripts/sql/project_db_003_task_times.sql 서버서 적용 필요. Co-Authored-By: Claude Opus 4.8 --- app/modules/project/db.py | 21 +++++++++++++--- app/modules/project/router.py | 2 ++ app/modules/project/store.py | 16 ++++++++++++ .../project/templates/project/inbox.html | 4 +-- .../project/templates/project/index.html | 4 +-- .../project/templates/project/project.html | 11 ++++++-- app/static/project.css | 3 +++ app/static/project.js | 25 ++++++++++++++++--- scripts/sql/project_db_003_task_times.sql | 17 +++++++++++++ 9 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 scripts/sql/project_db_003_task_times.sql diff --git a/app/modules/project/db.py b/app/modules/project/db.py index 00a4626..571e6c8 100644 --- a/app/modules/project/db.py +++ b/app/modules/project/db.py @@ -13,7 +13,7 @@ from __future__ import annotations import logging -from datetime import date, datetime +from datetime import date, datetime, time from typing import Any from psycopg.rows import dict_row @@ -331,12 +331,16 @@ class ProjectStore: priority: str = "normal", start_date: str | None = None, due_date: str | None = None, + start_time: str | None = None, + due_time: str | None = None, created_by: str = "", ) -> dict[str, Any]: ttl = store.validate_task_title(title) pr = store.validate_priority(priority) sd = store.validate_date(start_date) dd = store.validate_date(due_date) + sti = store.validate_time(start_time) + dti = store.validate_time(due_time) assignee = store.norm_str(assignee_email, lower=True) with self._pool.connection() as conn: with conn.transaction(): @@ -357,14 +361,15 @@ class ProjectStore: """ INSERT INTO tasks (project_id, stage_id, title, description, assignee_email, - assignee_name, priority, start_date, due_date, sort_order, created_by) - VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + assignee_name, priority, start_date, due_date, + start_time, due_time, sort_order, created_by) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) RETURNING * """, ( project_id, stage_id, ttl, store.norm_str(description), assignee, store.norm_str(assignee_name), pr, sd, dd, - int(nxt["n"]), store.norm_str(created_by, lower=True), + sti, dti, int(nxt["n"]), store.norm_str(created_by, lower=True), ), ).fetchone() if assignee: @@ -406,6 +411,12 @@ class ProjectStore: if "due_date" in fields: sets.append("due_date = %s") params.append(store.validate_date(fields["due_date"])) + if "start_time" in fields: + sets.append("start_time = %s") + params.append(store.validate_time(fields["start_time"])) + if "due_time" in fields: + sets.append("due_time = %s") + params.append(store.validate_time(fields["due_time"])) if "assignee_email" in fields: new_assignee = store.norm_str(fields["assignee_email"], lower=True) sets.append("assignee_email = %s") @@ -714,6 +725,8 @@ class ProjectStore: for k, v in list(out.items()): if isinstance(v, datetime): out[k] = v.astimezone(KST).isoformat(timespec="seconds") + elif isinstance(v, time): + out[k] = v.strftime("%H:%M") elif isinstance(v, date): out[k] = v.isoformat() for k in ("id", "parent_id", "project_id", "stage_id", "task_id", "sort_order"): diff --git a/app/modules/project/router.py b/app/modules/project/router.py index 7712123..bff4c55 100644 --- a/app/modules/project/router.py +++ b/app/modules/project/router.py @@ -681,6 +681,8 @@ async def api_create_task( priority=payload.get("priority", "normal"), start_date=payload.get("start_date"), due_date=payload.get("due_date"), + start_time=payload.get("start_time"), + due_time=payload.get("due_time"), created_by=user["email"], ) except ValueError as exc: diff --git a/app/modules/project/store.py b/app/modules/project/store.py index 6495f39..ff398f4 100644 --- a/app/modules/project/store.py +++ b/app/modules/project/store.py @@ -108,6 +108,22 @@ def validate_date(d: Any) -> str | None: return v[:10] +def validate_time(t: Any) -> str | None: + """'HH:MM' 형식만 통과(초는 버림). 빈값/None 은 None(=종일).""" + v = norm_str(t) + if not v: + return None + parts = v.split(":") + try: + hh = int(parts[0]) + mm = int(parts[1]) if len(parts) > 1 else 0 + except (ValueError, IndexError): + raise ValueError(f"시간 형식이 올바르지 않습니다: {t}") + if not (0 <= hh <= 23 and 0 <= mm <= 59): + raise ValueError(f"시간 범위가 올바르지 않습니다: {t}") + return f"{hh:02d}:{mm:02d}" + + def build_subject_assigned(*, project_name: str, task_title: str, assignee: str) -> str: return f"[DBX 프로젝트] 업무 배정: {project_name} · {task_title} → {assignee}" diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html index ffb35ed..e076dce 100644 --- a/app/modules/project/templates/project/inbox.html +++ b/app/modules/project/templates/project/inbox.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} @@ -45,5 +45,5 @@ {% endif %} - + {% endblock %} diff --git a/app/modules/project/templates/project/index.html b/app/modules/project/templates/project/index.html index 4217a8f..e5010e6 100644 --- a/app/modules/project/templates/project/index.html +++ b/app/modules/project/templates/project/index.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} @@ -100,5 +100,5 @@ - + {% endblock %} diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html index cdc913a..b48b593 100644 --- a/app/modules/project/templates/project/project.html +++ b/app/modules/project/templates/project/project.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + @@ -222,6 +222,13 @@ + +