feat: 프로젝트 시공 시작일 및 종료일 관리 기능 추가

This commit is contained in:
2026-03-23 16:59:45 +09:00
parent 358585da53
commit f88d8e53cb
3 changed files with 1153 additions and 888 deletions

View File

@@ -433,6 +433,8 @@ def init_db() -> None:
project_type text,
construction_family text,
construction_method text,
start_date text,
end_date text,
note text,
updated_at text not null
)
@@ -456,6 +458,8 @@ def init_db() -> None:
create table if not exists project_progress (
project_code text primary key,
progress_rate real not null default 0,
contract_pile_count real not null default 0,
constructed_pile_count real not null default 0,
updated_at text not null
)
"""
@@ -475,9 +479,43 @@ def init_db() -> None:
)
"""
)
cur.execute(
"""
create table if not exists project_pile_progress_entries (
id integer primary key autoincrement,
project_code text not null,
work_date text not null,
start_date text not null,
end_date text,
pile_count real not null default 0,
note text,
sort_order integer not null default 0,
updated_at text not null
)
"""
)
existing_cols = [row["name"] for row in cur.execute("pragma table_info(project_master)").fetchall()]
if "construction_family" not in existing_cols:
cur.execute("alter table project_master add column construction_family text")
if "start_date" not in existing_cols:
cur.execute("alter table project_master add column start_date text")
if "end_date" not in existing_cols:
cur.execute("alter table project_master add column end_date text")
progress_cols = [row["name"] for row in cur.execute("pragma table_info(project_progress)").fetchall()]
if "contract_pile_count" not in progress_cols:
cur.execute("alter table project_progress add column contract_pile_count real not null default 0")
if "constructed_pile_count" not in progress_cols:
cur.execute("alter table project_progress add column constructed_pile_count real not null default 0")
pile_progress_cols = [row["name"] for row in cur.execute("pragma table_info(project_pile_progress_entries)").fetchall()]
if "work_date" not in pile_progress_cols:
cur.execute("alter table project_pile_progress_entries add column work_date text")
if "start_date" not in pile_progress_cols and "work_date" in pile_progress_cols:
cur.execute("alter table project_pile_progress_entries add column start_date text")
cur.execute("update project_pile_progress_entries set start_date = coalesce(nullif(start_date, ''), work_date)")
if "end_date" not in pile_progress_cols:
cur.execute("alter table project_pile_progress_entries add column end_date text")
cur.execute("update project_pile_progress_entries set work_date = coalesce(nullif(work_date, ''), start_date)")
cur.execute("update project_pile_progress_entries set end_date = coalesce(nullif(end_date, ''), start_date)")
txn_cols = [row["name"] for row in cur.execute("pragma table_info(ptc_transactions)").fetchall()]
if "account_code_final" not in txn_cols:
cur.execute("alter table ptc_transactions add column account_code_final text")
@@ -577,7 +615,7 @@ def init_db() -> None:
def fetch_project_master(conn: sqlite3.Connection, project_code: str) -> dict | None:
row = conn.execute(
"""
select project_code, project_name, project_type, construction_family, construction_method, note, updated_at
select project_code, project_name, project_type, construction_family, construction_method, start_date, end_date, note, updated_at
from project_master
where project_code = ?
""",
@@ -728,6 +766,21 @@ def build_account_structure_rows(account_rows: list[sqlite3.Row]) -> list[dict]:
def build_budget_analysis(conn: sqlite3.Connection, project_code: str, account_structure_rows: list[dict]) -> dict:
pile_progress_rows = conn.execute(
"""
select
id,
coalesce(nullif(start_date, ''), work_date) as start_date,
coalesce(nullif(end_date, ''), nullif(start_date, ''), work_date) as end_date,
pile_count,
note,
sort_order
from project_pile_progress_entries
where project_code = ?
order by coalesce(nullif(start_date, ''), work_date) asc, sort_order asc, id asc
""",
(project_code,),
).fetchall()
item_budget_rows = conn.execute(
"""
select section, group_name, category, budget_amount
@@ -753,10 +806,17 @@ def build_budget_analysis(conn: sqlite3.Connection, project_code: str, account_s
for row in budget_rows
}
progress_row = conn.execute(
"select progress_rate from project_progress where project_code = ?",
"select progress_rate, contract_pile_count, constructed_pile_count from project_progress where project_code = ?",
(project_code,),
).fetchone()
progress_rate = progress_row["progress_rate"] if progress_row else 0
contract_pile_count = float(progress_row["contract_pile_count"] or 0) if progress_row else 0
constructed_pile_count = float(progress_row["constructed_pile_count"] or 0) if progress_row else 0
entry_pile_total = sum(float(row["pile_count"] or 0) for row in pile_progress_rows)
if pile_progress_rows:
constructed_pile_count = entry_pile_total
if contract_pile_count > 0:
progress_rate = (constructed_pile_count / contract_pile_count) * 100
rows = []
expense_budget_total = 0.0
@@ -801,6 +861,9 @@ def build_budget_analysis(conn: sqlite3.Connection, project_code: str, account_s
execution_rate_total = (expense_actual_total / expense_budget_total * 100) if expense_budget_total > 0 else 0
return {
"progress_rate": progress_rate,
"contract_pile_count": contract_pile_count,
"constructed_pile_count": constructed_pile_count,
"pile_progress_entries": [dict(row) for row in pile_progress_rows],
"execution_rate_total": execution_rate_total,
"expense_budget_total": expense_budget_total,
"expense_actual_total": expense_actual_total,
@@ -977,23 +1040,27 @@ class Handler(BaseHTTPRequestHandler):
self._send(400, {"ok": False, "message": "invalid construction_method"})
return
construction_family = resolve_construction_family(construction_method, construction_family)
start_date = str(payload.get("start_date", "")).strip()
end_date = str(payload.get("end_date", "")).strip()
note = str(payload.get("note", "")).strip()
updated_at = datetime.now().isoformat()
conn.execute(
"""
insert into project_master (
project_code, project_name, project_type, construction_family, construction_method, note, updated_at
) values (?, ?, ?, ?, ?, ?, ?)
project_code, project_name, project_type, construction_family, construction_method, start_date, end_date, note, updated_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
on conflict(project_code) do update set
project_name = excluded.project_name,
project_type = excluded.project_type,
construction_family = excluded.construction_family,
construction_method = excluded.construction_method,
start_date = excluded.start_date,
end_date = excluded.end_date,
note = excluded.note,
updated_at = excluded.updated_at
""",
(project_code, project_name, project_type, construction_family, construction_method, note, updated_at),
(project_code, project_name, project_type, construction_family, construction_method, start_date, end_date, note, updated_at),
)
conn.commit()
self._send(200, {"ok": True, "item": fetch_project_master(conn, project_code)})
@@ -1030,16 +1097,21 @@ class Handler(BaseHTTPRequestHandler):
)
merged_note = note if note else (existing.get("note") or "")
start_date = existing.get("start_date") or ""
end_date = existing.get("end_date") or ""
conn.execute(
"""
insert into project_master (
project_code, project_name, project_type, construction_family, construction_method, note, updated_at
) values (?, ?, ?, ?, ?, ?, ?)
project_code, project_name, project_type, construction_family, construction_method, start_date, end_date, note, updated_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
on conflict(project_code) do update set
project_name = excluded.project_name,
project_type = excluded.project_type,
construction_family = excluded.construction_family,
construction_method = excluded.construction_method,
start_date = excluded.start_date,
end_date = excluded.end_date,
note = excluded.note,
updated_at = excluded.updated_at
""",
@@ -1049,6 +1121,8 @@ class Handler(BaseHTTPRequestHandler):
project_type,
construction_family,
construction_method,
start_date,
end_date,
merged_note,
updated_at,
),
@@ -1181,6 +1255,8 @@ class Handler(BaseHTTPRequestHandler):
item_rows = payload.get("item_rows", [])
account_rows = payload.get("account_rows", [])
progress_rate = float(payload.get("progress_rate", 0) or 0)
contract_pile_count = float(payload.get("contract_pile_count", 0) or 0)
constructed_pile_count = float(payload.get("constructed_pile_count", 0) or 0)
if not project_code:
self._send(400, {"ok": False, "message": "project_code is required"})
return
@@ -1228,18 +1304,84 @@ class Handler(BaseHTTPRequestHandler):
)
conn.execute(
"""
insert into project_progress (project_code, progress_rate, updated_at)
values (?, ?, ?)
insert into project_progress (
project_code, progress_rate, contract_pile_count, constructed_pile_count, updated_at
)
values (?, ?, ?, ?, ?)
on conflict(project_code) do update set
progress_rate = excluded.progress_rate,
contract_pile_count = excluded.contract_pile_count,
constructed_pile_count = excluded.constructed_pile_count,
updated_at = excluded.updated_at
""",
(project_code, progress_rate, updated_at),
(project_code, progress_rate, contract_pile_count, constructed_pile_count, updated_at),
)
conn.commit()
self._send(200, {"ok": True, "project_code": project_code, "updated_at": updated_at})
return
if parsed.path == "/api/project-pile-progress/upsert":
payload = self._read_json()
project_code = str(payload.get("project_code", "")).strip()
contract_pile_count = float(payload.get("contract_pile_count", 0) or 0)
entries = payload.get("entries", [])
if not project_code:
self._send(400, {"ok": False, "message": "project_code is required"})
return
if not isinstance(entries, list):
self._send(400, {"ok": False, "message": "entries must be a list"})
return
updated_at = datetime.now().isoformat()
conn.execute("delete from project_pile_progress_entries where project_code = ?", (project_code,))
constructed_pile_count = 0.0
for idx, item in enumerate(entries):
start_date = str(item.get("start_date", "")).strip()
end_date = str(item.get("end_date", "")).strip()
pile_count = float(item.get("pile_count", 0) or 0)
note = str(item.get("note", "")).strip()
if not start_date:
continue
if not end_date:
end_date = start_date
constructed_pile_count += pile_count
conn.execute(
"""
insert into project_pile_progress_entries (
project_code, work_date, start_date, end_date, pile_count, note, sort_order, updated_at
) values (?, ?, ?, ?, ?, ?, ?, ?)
""",
(project_code, start_date, start_date, end_date, pile_count, note, idx, updated_at),
)
progress_rate = (constructed_pile_count / contract_pile_count * 100) if contract_pile_count > 0 else 0
conn.execute(
"""
insert into project_progress (
project_code, progress_rate, contract_pile_count, constructed_pile_count, updated_at
)
values (?, ?, ?, ?, ?)
on conflict(project_code) do update set
progress_rate = excluded.progress_rate,
contract_pile_count = excluded.contract_pile_count,
constructed_pile_count = excluded.constructed_pile_count,
updated_at = excluded.updated_at
""",
(project_code, progress_rate, contract_pile_count, constructed_pile_count, updated_at),
)
conn.commit()
self._send(
200,
{
"ok": True,
"project_code": project_code,
"contract_pile_count": contract_pile_count,
"constructed_pile_count": constructed_pile_count,
"progress_rate": progress_rate,
"updated_at": updated_at,
},
)
return
self._send(404, {"ok": False, "message": "Not found"})
finally:
conn.close()