feat(manage): refine lifecycle flow UI and direct/shared cost breakdown
This commit is contained in:
@@ -879,6 +879,13 @@ def build_related_projects(conn: sqlite3.Connection, project_code: str, project_
|
||||
{excluded_clause}
|
||||
group by tx.project_code
|
||||
),
|
||||
pile_progress_summary as (
|
||||
select
|
||||
project_code,
|
||||
sum(coalesce(pile_count, 0)) as entry_pile_total
|
||||
from project_pile_progress_entries
|
||||
group by project_code
|
||||
),
|
||||
master_rows as (
|
||||
select
|
||||
pm.project_code as project_code,
|
||||
@@ -906,10 +913,17 @@ def build_related_projects(conn: sqlite3.Connection, project_code: str, project_
|
||||
coalesce(ps.expense_supply, 0) as expense_supply,
|
||||
coalesce(ps.txn_count, 0) as txn_count,
|
||||
coalesce(ps.min_date, '') as min_date,
|
||||
coalesce(ps.max_date, '') as max_date
|
||||
coalesce(ps.max_date, '') as max_date,
|
||||
case
|
||||
when coalesce(pp.contract_pile_count, 0) > 0 then
|
||||
(coalesce(psum.entry_pile_total, pp.constructed_pile_count, 0) / pp.contract_pile_count) * 100
|
||||
else coalesce(pp.progress_rate, 0)
|
||||
end as progress_rate
|
||||
from code_set cs
|
||||
left join project_summary ps on ps.project_code = cs.project_code
|
||||
left join master_rows mr on mr.project_code = cs.project_code
|
||||
left join project_progress pp on pp.project_code = cs.project_code
|
||||
left join pile_progress_summary psum on psum.project_code = cs.project_code
|
||||
order by cs.project_code
|
||||
""",
|
||||
[project_code, *excluded_values],
|
||||
@@ -949,6 +963,7 @@ def build_related_projects(conn: sqlite3.Connection, project_code: str, project_
|
||||
"txn_count": int(row_dict.get("txn_count") or 0),
|
||||
"min_date": row_dict.get("min_date") or "",
|
||||
"max_date": row_dict.get("max_date") or "",
|
||||
"progress_rate": float(row_dict.get("progress_rate") or 0),
|
||||
"is_current": code == project_code,
|
||||
}
|
||||
items.append(item)
|
||||
@@ -1135,7 +1150,11 @@ def build_project_lifecycle_cost(
|
||||
total_income = sum(float(item.get("income_supply") or 0) for item in rows_with_allocation)
|
||||
total_expense = sum(float(item.get("adjusted_expense_supply") or 0) for item in rows_with_allocation)
|
||||
project_codes = [item.get("project_code") for item in rows_with_allocation if item.get("project_code")]
|
||||
breakdown_totals = {"시공비": 0.0, "인건비": 0.0, "관리비": 0.0}
|
||||
breakdown_components = {
|
||||
"시공비": {"direct": 0.0, "shared": 0.0, "total": 0.0},
|
||||
"인건비": {"direct": 0.0, "shared": 0.0, "total": 0.0},
|
||||
"관리비": {"direct": 0.0, "shared": 0.0, "total": 0.0},
|
||||
}
|
||||
breakdown_project_maps: dict[str, dict[str, dict]] = {
|
||||
"시공비": {},
|
||||
"인건비": {},
|
||||
@@ -1185,7 +1204,11 @@ def build_project_lifecycle_cost(
|
||||
allocation_ratio = float(project_info.get("allocation_ratio") or 1.0)
|
||||
expense_supply = float(row["expense_supply"] or 0) * allocation_ratio
|
||||
|
||||
breakdown_totals[bucket] += expense_supply
|
||||
# Until common-cost allocation is introduced, lifecycle-linked costs
|
||||
# are treated as direct costs for the target project.
|
||||
source_component = "direct"
|
||||
breakdown_components[bucket][source_component] += expense_supply
|
||||
breakdown_components[bucket]["total"] += expense_supply
|
||||
|
||||
project_entry = breakdown_project_maps[bucket].setdefault(
|
||||
project_code,
|
||||
@@ -1198,9 +1221,12 @@ def build_project_lifecycle_cost(
|
||||
"allocation_numerator": numerator,
|
||||
"allocation_denominator": denominator,
|
||||
"allocation_ratio": allocation_ratio,
|
||||
"direct_expense_supply": 0.0,
|
||||
"shared_expense_supply": 0.0,
|
||||
"expense_supply": 0.0,
|
||||
},
|
||||
)
|
||||
project_entry[f"{source_component}_expense_supply"] += expense_supply
|
||||
project_entry["expense_supply"] += expense_supply
|
||||
|
||||
account_entry = breakdown_account_maps[bucket].setdefault(
|
||||
@@ -1208,15 +1234,20 @@ def build_project_lifecycle_cost(
|
||||
{
|
||||
"account_code": account_code or "",
|
||||
"account_name": (meta or {}).get("name") or (row["account_code"] or ""),
|
||||
"direct_expense_supply": 0.0,
|
||||
"shared_expense_supply": 0.0,
|
||||
"expense_supply": 0.0,
|
||||
},
|
||||
)
|
||||
account_entry[f"{source_component}_expense_supply"] += expense_supply
|
||||
account_entry["expense_supply"] += expense_supply
|
||||
|
||||
breakdown = [
|
||||
{
|
||||
"label": "시공비",
|
||||
"expense_supply": breakdown_totals["시공비"],
|
||||
"expense_supply": breakdown_components["시공비"]["total"],
|
||||
"direct_expense_supply": breakdown_components["시공비"]["direct"],
|
||||
"shared_expense_supply": breakdown_components["시공비"]["shared"],
|
||||
"projects": sorted(
|
||||
breakdown_project_maps["시공비"].values(),
|
||||
key=lambda item: (-float(item.get("expense_supply") or 0), item.get("project_code") or ""),
|
||||
@@ -1228,13 +1259,23 @@ def build_project_lifecycle_cost(
|
||||
},
|
||||
{
|
||||
"label": "인건비",
|
||||
"expense_supply": 0.0,
|
||||
"projects": [],
|
||||
"accounts": [],
|
||||
"expense_supply": breakdown_components["인건비"]["total"],
|
||||
"direct_expense_supply": breakdown_components["인건비"]["direct"],
|
||||
"shared_expense_supply": breakdown_components["인건비"]["shared"],
|
||||
"projects": sorted(
|
||||
breakdown_project_maps["인건비"].values(),
|
||||
key=lambda item: (-float(item.get("expense_supply") or 0), item.get("project_code") or ""),
|
||||
),
|
||||
"accounts": sorted(
|
||||
breakdown_account_maps["인건비"].values(),
|
||||
key=lambda item: (-float(item.get("expense_supply") or 0), item.get("account_code") or ""),
|
||||
),
|
||||
},
|
||||
{
|
||||
"label": "관리비",
|
||||
"expense_supply": breakdown_totals["관리비"],
|
||||
"expense_supply": breakdown_components["관리비"]["total"],
|
||||
"direct_expense_supply": breakdown_components["관리비"]["direct"],
|
||||
"shared_expense_supply": breakdown_components["관리비"]["shared"],
|
||||
"projects": sorted(
|
||||
breakdown_project_maps["관리비"].values(),
|
||||
key=lambda item: (-float(item.get("expense_supply") or 0), item.get("project_code") or ""),
|
||||
@@ -1263,10 +1304,10 @@ def classify_lifecycle_bucket(account_code: str, project_code: str, project_type
|
||||
meta = meta or ACCOUNT_MASTER.get(account_code)
|
||||
if meta:
|
||||
if meta.get("category") == "인건비":
|
||||
return "시공비"
|
||||
if meta.get("project_type") == "시공":
|
||||
return "시공비"
|
||||
return "관리비"
|
||||
return "인건비"
|
||||
if meta.get("project_type") == "관리":
|
||||
return "관리비"
|
||||
return "시공비"
|
||||
if "-시공-" in project_code or project_type == "시공":
|
||||
return "시공비"
|
||||
return "관리비"
|
||||
|
||||
Reference in New Issue
Block a user