Compare commits
2 Commits
35ababe236
...
f88d8e53cb
| Author | SHA1 | Date | |
|---|---|---|---|
| f88d8e53cb | |||
| 358585da53 |
1699
PTC/index.html
1699
PTC/index.html
File diff suppressed because it is too large
Load Diff
50
PTC_ISSUES_LIST.md
Normal file
50
PTC_ISSUES_LIST.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# PTC 프로젝트 이슈 정리 (레이블: ptc 실행분석)
|
||||||
|
|
||||||
|
## 1. [마스터] [PTC::실행분석] 전용 실행 분석 및 계정 관리 시스템 구축
|
||||||
|
**설명**: PTC 전용 실행 분석 시스템 구축을 위한 전체 진행 상황을 관리하는 마스터 이슈입니다.
|
||||||
|
|
||||||
|
### 체크리스트
|
||||||
|
- [ ] UI 렌더링 완성 (PTC 데이터 선택 시 테이블 공백 문제 해결)
|
||||||
|
- [ ] PTC 고유 계정 체계(7xx, 8xx, 513) 분류 로직 고도화
|
||||||
|
- [ ] PTC 전용 실행 예산 보고서 양식 개발 및 출력 기능
|
||||||
|
- [ ] PTC 대시보드 고도화 (순유입/유출 잔액 합계 및 시각화 개선)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. [PTC::UI] 테이블 렌더링 오류 수정
|
||||||
|
**설명**: `index.html` 등에서 PTC 데이터를 불러올 때 실행 예산 테이블이 빈 공백으로 표시되는 문제를 해결합니다.
|
||||||
|
|
||||||
|
### 주요 작업
|
||||||
|
- PTC 전용 데이터 매핑 정의 추가 (7xx, 8xx 계정 대응)
|
||||||
|
- 데이터 로드 후 UI 렌더링 분기 로직 점검 및 수정
|
||||||
|
- 테이블 데이터가 없을 경우의 예외 처리 강화
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. [PTC::계정] 전용 계정 코드(7xx, 8xx, 513) 분류 로직 강화
|
||||||
|
**설명**: 분석 로직에서 PTC 고유의 계정 체계를 정확히 인식하도록 개선합니다.
|
||||||
|
|
||||||
|
### 주요 작업
|
||||||
|
- 7xx(시공), 8xx(관리) 계정 코드에 대한 분류 로직 최적화
|
||||||
|
- 513(시공 퇴직금) 항목의 프로젝트별 분리 및 예산 대비 실적 비교 기능 검증
|
||||||
|
- PTC 프로젝트 성격 기반 계정 추천 로직 최적화
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. [PTC::보고서] 전용 실행 예산 보고서 양식 개발
|
||||||
|
**설명**: PTC의 공사원가 및 관리비 기준에 최적화된 보고서 출력 양식을 구현합니다.
|
||||||
|
|
||||||
|
### 주요 작업
|
||||||
|
- PTC 전용 엑셀/PDF 출력 템플릿 설계
|
||||||
|
- 실행 예산 보고서 내 계정별 집계 데이터 매핑
|
||||||
|
- PTC 특화 항목(현장운영비, 보증료 등) 반영
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. [PTC::대시보드] 순유입/유출 잔액 합계 및 시각화 개선
|
||||||
|
**설명**: 대시보드에서 PTC 데이터를 보여줄 때, 잔액 계산 방식과 시각적 표현을 개선합니다.
|
||||||
|
|
||||||
|
### 주요 작업
|
||||||
|
- PTC 순유입 및 유출 잔액 합계 산출 로직 개선
|
||||||
|
- 대시보드 상의 차트 및 요약 테이블에 실시간 데이터 반영
|
||||||
|
- 데이터 동기화 및 탭 전환 최적화
|
||||||
@@ -433,6 +433,8 @@ def init_db() -> None:
|
|||||||
project_type text,
|
project_type text,
|
||||||
construction_family text,
|
construction_family text,
|
||||||
construction_method text,
|
construction_method text,
|
||||||
|
start_date text,
|
||||||
|
end_date text,
|
||||||
note text,
|
note text,
|
||||||
updated_at text not null
|
updated_at text not null
|
||||||
)
|
)
|
||||||
@@ -456,6 +458,8 @@ def init_db() -> None:
|
|||||||
create table if not exists project_progress (
|
create table if not exists project_progress (
|
||||||
project_code text primary key,
|
project_code text primary key,
|
||||||
progress_rate real not null default 0,
|
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
|
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()]
|
existing_cols = [row["name"] for row in cur.execute("pragma table_info(project_master)").fetchall()]
|
||||||
if "construction_family" not in existing_cols:
|
if "construction_family" not in existing_cols:
|
||||||
cur.execute("alter table project_master add column construction_family text")
|
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()]
|
txn_cols = [row["name"] for row in cur.execute("pragma table_info(ptc_transactions)").fetchall()]
|
||||||
if "account_code_final" not in txn_cols:
|
if "account_code_final" not in txn_cols:
|
||||||
cur.execute("alter table ptc_transactions add column account_code_final text")
|
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:
|
def fetch_project_master(conn: sqlite3.Connection, project_code: str) -> dict | None:
|
||||||
row = conn.execute(
|
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
|
from project_master
|
||||||
where project_code = ?
|
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:
|
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(
|
item_budget_rows = conn.execute(
|
||||||
"""
|
"""
|
||||||
select section, group_name, category, budget_amount
|
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
|
for row in budget_rows
|
||||||
}
|
}
|
||||||
progress_row = conn.execute(
|
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,),
|
(project_code,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
progress_rate = progress_row["progress_rate"] if progress_row else 0
|
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 = []
|
rows = []
|
||||||
expense_budget_total = 0.0
|
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
|
execution_rate_total = (expense_actual_total / expense_budget_total * 100) if expense_budget_total > 0 else 0
|
||||||
return {
|
return {
|
||||||
"progress_rate": progress_rate,
|
"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,
|
"execution_rate_total": execution_rate_total,
|
||||||
"expense_budget_total": expense_budget_total,
|
"expense_budget_total": expense_budget_total,
|
||||||
"expense_actual_total": expense_actual_total,
|
"expense_actual_total": expense_actual_total,
|
||||||
@@ -977,23 +1040,27 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self._send(400, {"ok": False, "message": "invalid construction_method"})
|
self._send(400, {"ok": False, "message": "invalid construction_method"})
|
||||||
return
|
return
|
||||||
construction_family = resolve_construction_family(construction_method, construction_family)
|
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()
|
note = str(payload.get("note", "")).strip()
|
||||||
updated_at = datetime.now().isoformat()
|
updated_at = datetime.now().isoformat()
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
insert into project_master (
|
insert into project_master (
|
||||||
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
|
||||||
) values (?, ?, ?, ?, ?, ?, ?)
|
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
on conflict(project_code) do update set
|
on conflict(project_code) do update set
|
||||||
project_name = excluded.project_name,
|
project_name = excluded.project_name,
|
||||||
project_type = excluded.project_type,
|
project_type = excluded.project_type,
|
||||||
construction_family = excluded.construction_family,
|
construction_family = excluded.construction_family,
|
||||||
construction_method = excluded.construction_method,
|
construction_method = excluded.construction_method,
|
||||||
|
start_date = excluded.start_date,
|
||||||
|
end_date = excluded.end_date,
|
||||||
note = excluded.note,
|
note = excluded.note,
|
||||||
updated_at = excluded.updated_at
|
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()
|
conn.commit()
|
||||||
self._send(200, {"ok": True, "item": fetch_project_master(conn, project_code)})
|
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 "")
|
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(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
insert into project_master (
|
insert into project_master (
|
||||||
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
|
||||||
) values (?, ?, ?, ?, ?, ?, ?)
|
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
on conflict(project_code) do update set
|
on conflict(project_code) do update set
|
||||||
project_name = excluded.project_name,
|
project_name = excluded.project_name,
|
||||||
project_type = excluded.project_type,
|
project_type = excluded.project_type,
|
||||||
construction_family = excluded.construction_family,
|
construction_family = excluded.construction_family,
|
||||||
construction_method = excluded.construction_method,
|
construction_method = excluded.construction_method,
|
||||||
|
start_date = excluded.start_date,
|
||||||
|
end_date = excluded.end_date,
|
||||||
note = excluded.note,
|
note = excluded.note,
|
||||||
updated_at = excluded.updated_at
|
updated_at = excluded.updated_at
|
||||||
""",
|
""",
|
||||||
@@ -1049,6 +1121,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
project_type,
|
project_type,
|
||||||
construction_family,
|
construction_family,
|
||||||
construction_method,
|
construction_method,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
merged_note,
|
merged_note,
|
||||||
updated_at,
|
updated_at,
|
||||||
),
|
),
|
||||||
@@ -1181,6 +1255,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
item_rows = payload.get("item_rows", [])
|
item_rows = payload.get("item_rows", [])
|
||||||
account_rows = payload.get("account_rows", [])
|
account_rows = payload.get("account_rows", [])
|
||||||
progress_rate = float(payload.get("progress_rate", 0) or 0)
|
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:
|
if not project_code:
|
||||||
self._send(400, {"ok": False, "message": "project_code is required"})
|
self._send(400, {"ok": False, "message": "project_code is required"})
|
||||||
return
|
return
|
||||||
@@ -1228,18 +1304,84 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
)
|
)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
insert into project_progress (project_code, progress_rate, updated_at)
|
insert into project_progress (
|
||||||
values (?, ?, ?)
|
project_code, progress_rate, contract_pile_count, constructed_pile_count, updated_at
|
||||||
|
)
|
||||||
|
values (?, ?, ?, ?, ?)
|
||||||
on conflict(project_code) do update set
|
on conflict(project_code) do update set
|
||||||
progress_rate = excluded.progress_rate,
|
progress_rate = excluded.progress_rate,
|
||||||
|
contract_pile_count = excluded.contract_pile_count,
|
||||||
|
constructed_pile_count = excluded.constructed_pile_count,
|
||||||
updated_at = excluded.updated_at
|
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()
|
conn.commit()
|
||||||
self._send(200, {"ok": True, "project_code": project_code, "updated_at": updated_at})
|
self._send(200, {"ok": True, "project_code": project_code, "updated_at": updated_at})
|
||||||
return
|
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"})
|
self._send(404, {"ok": False, "message": "Not found"})
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user