- 단가 책 = 자재만(자재단가대비표 · 4 자재대 · 관급자재대) · 노무 · 기계 · 운전사 계수(CC000336 세 줄) · 연료 · 환율 줄 걷음 - 바탕 셈(py · ts 짝) 운전사 시간 노임 · 중기사용료(손료 · 운전사 · 연료 · 잡품 재현) · 경비목록표 · 노임 · 기계 덮개 걷음 - 노무비목록표 · 중기목록표 = 대가 · 산근 줄 엔진 단가(프로젝트 사본 M02 값) 나열 · 다시 안 셈(price_lists) - 경비목록표 · 중기사용료 · 환율및기초자료 탭 = 「—」 한 줄 + 까닭 한 줄(탭은 둠) - [단가 갱신] = 프로젝트 사본 자원 단가 줄(단가.json LB · MT · EQ · MO · FX)을 지금 M01 로 + 자재 단가 책(새 파일 B09_Estimation_Copy_Prices.py · 사용자가 누를 때만) · 미리 보기 = 덮개로 같은 셈(파일 안 씀) - 산근 · 대가 단가 고친 칸 걷음(품만) · 단가표 불러오기(707-3) 노임 · 기계 덮개 걷음(자재만) - 낡은 시험은 C 걸음에서 정리 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
4.1 KiB
Python
93 lines
4.1 KiB
Python
"""B09 [단가 갱신] = 프로젝트 사본 자원 단가 줄 갱신(PLAN 841-1 · 요구 854 · 866) — 서버 단독.
|
|
|
|
B09 금액 = 프로젝트 사본(구조물 사본 · 서고 사본)을 M02 엔진으로 셈한 값(B09 단가 책으로 다시 안 셈).
|
|
그래서 사본 값을 지금 M01 로 올리는 일 = 사본 `단가.json` 자원 줄(LB · MT · EQ · MO · FX)만 지금 M01 줄로
|
|
(로직 줄 · 일위대가 · 입력값 · 고른 자재 · 덮개 그대로 · M01 에서 지워진 키 = 마지막 값 그대로) —
|
|
M02 [단가 갱신](`M02_Sangeun_Copy.refresh`)과 같은 뜻 · 사용자가 누를 때만(프로젝트 안은 자동 아님).
|
|
미리 보기 = `OVER` 덮개(파일 안 씀 · 사본 읽는 곳이 `bundle` 로 읽음)로 같은 셈.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from contextvars import ContextVar
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from B09_Estimation import B09_Estimation_Bill_Library as library
|
|
from common_util.common_util_json import atomic_write_json
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_Item_Prices as item_prices
|
|
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
|
from M02_MasterTemplete.M02_Item_Snapshot import linked_snapshots, structures_dir
|
|
from M02_MasterTemplete.M02_Item_Source import PRICE_FILE, read_json
|
|
|
|
#: 미리 보기 덮개 {사본 폴더 글: 새 `단가.json`} — 없으면 파일
|
|
OVER: ContextVar[dict[str, dict] | None] = ContextVar("b09_copy_prices", default=None)
|
|
|
|
|
|
def bundle(folder: Path) -> dict[str, Any]:
|
|
"""사본 `단가.json` — 미리 보기 덮개가 있으면 그것."""
|
|
over = OVER.get()
|
|
if over and str(folder) in over:
|
|
return over[str(folder)]
|
|
return read_json(folder / PRICE_FILE) or {}
|
|
|
|
|
|
def folders(root: str | Path) -> list[Path]:
|
|
"""연결된 구조물 사본 + 서고 사본 폴더."""
|
|
base = structures_dir(root)
|
|
out = [base / one["폴더"] for one in linked_snapshots(root).values()]
|
|
out += [folder for folder, _manifest in library.copies(root)]
|
|
return list(dict.fromkeys(out))
|
|
|
|
|
|
def _shown(row: dict[str, Any]) -> Any:
|
|
"""미리 보기 값 — 노임 · 유가 = 값 · 기계 = 가격(천원) · 자재 = 출처 값 묶음."""
|
|
value = row.get("값")
|
|
if isinstance(value, dict):
|
|
return value.get("가격")
|
|
if value is not None:
|
|
return value
|
|
return {k: row.get(k) for k in ("조달청", "물가자료", "유통물가", "물가정보", "거래가격")}
|
|
|
|
|
|
def refreshed(root: str | Path) -> tuple[dict[str, dict], list[dict[str, Any]]]:
|
|
"""({바뀐 사본 폴더 글: 새 `단가.json`}, 바뀐 자원 [{키, 이름, 전, 후}]) — 파일 안 씀."""
|
|
whole = skc.whole_()
|
|
news: dict[str, dict] = {}
|
|
changed: dict[str, dict[str, Any]] = {}
|
|
for folder in folders(root):
|
|
data = read_json(folder / PRICE_FILE)
|
|
if not isinstance(data, dict):
|
|
continue
|
|
out, hit = {}, False
|
|
for name, part in data.items():
|
|
slot = m01.items_key(part) if isinstance(part, dict) else ""
|
|
rows = part.get(slot) if slot else None
|
|
if not isinstance(rows, list):
|
|
out[name] = part
|
|
continue
|
|
fresh = []
|
|
for row in rows:
|
|
key = str((row or {}).get("키") or "")
|
|
live = whole.index.get(key[:2], {}).get(key) if key[:2] in skc.RES else None
|
|
new = item_prices.to_json(live) if live is not None else row
|
|
if new != row:
|
|
hit = True
|
|
changed.setdefault(
|
|
key,
|
|
{"키": key, "이름": new.get("이름"), "전": _shown(row), "후": _shown(new)},
|
|
)
|
|
fresh.append(new)
|
|
out[name] = {**part, slot: fresh}
|
|
if hit:
|
|
news[str(folder)] = out
|
|
return news, list(changed.values())
|
|
|
|
|
|
def write(news: dict[str, dict]) -> int:
|
|
"""새 `단가.json` 을 사본 폴더에 씀 — 쓴 사본 수."""
|
|
for folder, data in news.items():
|
|
atomic_write_json(Path(folder) / PRICE_FILE, data)
|
|
return len(news)
|