- 일위대가 격자(ui_template_hopyo_grid onPaste) 누르고 Ctrl+V → 서버 /hopyo-paste · 열 짝 = 처음 한 번 묻고 이 브라우저에 기억 · 엑셀 머리 글이면 묻지 않음 - 호표 머리 없음 = 열린 문서에 고정 줄(알림 누름 = 다시 잇기) · 머리 있음 = 호표마다 사전조합 + 결과 창 [다음 — 이 공사 단가로 다시 잇기] - 다시 잇기 창(EST 신 · 구 단가 비교 꼴) — 붙인 단가 · 최신 판 단가 · 후보(705-3 이음 짝) · 금액 차이 · 줄마다 · 모두 고름 · [바꿈] · [되돌리기] - 서버 M02_Hopyo_Relink — 후보 표 · 이음 칸만 고쳐 씀(숫자 안 받음) · 전후 1 단위 값 · 되돌림 - 실무자료 [모두 고정으로 들여오기] 결과에도 다음 걸음 단추 - M02 상세가 고정 줄을 읽을 때 떨구던 흠 — normalizeLabor 가 고정 칸 지킴 · 로직 표 · 격자에 고정 줄 이름 · 규격 · 단위 · 「고정」 꼬리표 · 이음 / 손 단가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
6.8 KiB
Python
149 lines
6.8 KiB
Python
"""「이 공사 단가로 다시 잇기」(PLAN 705-2 · 요구서 4장 2 · EST 「신 · 구 단가 비교」 꼴) — 고정 줄마다 붙인 단가 ↔
|
|
단가 책(M02 = M01 최신 판 노임 「값」 · 자재 기준단가) · 이음 후보 · 고른 줄만 이음 바꿈 · 되돌리기.
|
|
|
|
후보 = 705-3 이음과 같은 짝(`M02_Hopyo_Fixed.Linker`) — 노임 = 이름이 우리 직종(단위 인) ·
|
|
자재 = 이름 · 규격 같은 품목 + 단위 배 1 · 단가가 달라도 후보(바꿀지는 사람이 고름) · 기계 = 없음(B09 중기 몫).
|
|
줄 = 비율 줄 뺀 고정 줄 · 줄 가름 = `fixed_id` · 붙인 이름 끝 「 (2)」(줄 가름)은 떼고 찾음.
|
|
바꿈 = `{키, 줄, 노임?, 자재?}` — 키 = 이 층 사전조합(SC) · 값 null = 이음 풂(붙인 단가로) ·
|
|
서버가 문서를 읽어 이음 칸만 고쳐 씀(숫자 안 받음) · 전후 1 단위 값(`Labor_Ref.Expand`) · `되돌림` = 옛 이음.
|
|
열린 문서(저장 전) = `doc` 로 줄 · 후보만 — 바꿈은 화면이 문서에 넣고 [저장].
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
|
|
from M02_MasterTemplete import M02_Hopyo_Shares as shares
|
|
from M02_MasterTemplete import M02_Labor_Fixed as fixed
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
|
|
KINDS = ("노임", "자재")
|
|
CHANGES_MAX = 2000
|
|
_SUFFIX = re.compile(r"\s*\(\d+\)$")
|
|
|
|
|
|
def _num(raw: Any) -> float | None:
|
|
got = fixed._num(raw)
|
|
return float(got) if got is not None else None
|
|
|
|
|
|
class Finder:
|
|
"""후보 찾기 — 단가 책(M01 최신 판) 한 번 읽음."""
|
|
|
|
def __init__(self) -> None:
|
|
self.linker = hfix.Linker()
|
|
self.whole = self.linker.whole
|
|
|
|
def _name(self, code: str) -> dict[str, str]:
|
|
try:
|
|
row = self.whole.get(code) or {}
|
|
except fixed.mf.FormulaError:
|
|
row = {}
|
|
return {k: str(row.get(k) or "") for k in ("이름", "규격", "단위")}
|
|
|
|
def price(self, kind: str, code: str | None, unit: str | None) -> float | None:
|
|
if not code:
|
|
return None
|
|
try:
|
|
if kind == "노임":
|
|
return _num((self.whole.get(code) or {}).get("값"))
|
|
return _num(fixed.mm.element(self.whole, code, {}, unit or None)[0])
|
|
except fixed.mf.FormulaError:
|
|
return None
|
|
|
|
def candidates(self, fx: dict[str, Any]) -> list[dict[str, Any]]:
|
|
name = _SUFFIX.sub("", str(fx.get("이름") or ""))
|
|
line = {"이름": name, "규격": fx.get("규격") or "", "단위": fx.get("단위") or ""}
|
|
out = []
|
|
trade = shares.trade(name) if line["단위"].strip() == "인" else None
|
|
for code, value in self.linker.labor.get(trade or "", []):
|
|
out.append({"비목": "노임", "키": code, **self._name(code), "단가": _num(value)})
|
|
price = fx.get("단가") or {}
|
|
if not trade and (_num(price.get("재료비")) or not any(_num(v) for v in price.values())):
|
|
for code, value in self.linker._mats({**line, "갈래": "자재"}):
|
|
out.append({"비목": "자재", "키": code, **self._name(code), "단가": _num(value)})
|
|
return out
|
|
|
|
def row(self, key: str | None, one: dict[str, Any]) -> dict[str, Any]:
|
|
fx = one[fixed.FIXED]
|
|
link = fx.get("이음") or {}
|
|
qty = _num((one.get("수량") or {}).get("손"))
|
|
now = {
|
|
k: self.price(k, link.get(k), fx.get("단위"))
|
|
for k in KINDS
|
|
if link.get(k) # 이은 비목 지금 단가
|
|
}
|
|
return {
|
|
"키": key,
|
|
"줄": fixed.fixed_id(fx),
|
|
**{k: fx.get(k) or "" for k in ("이름", "규격", "단위")},
|
|
"수량": qty,
|
|
"붙인": {k: _num((fx.get("단가") or {}).get(k)) or 0 for k in fixed.COSTS},
|
|
"이음": {k: link.get(k) for k in KINDS},
|
|
"지금": now,
|
|
"후보": self.candidates(fx),
|
|
}
|
|
|
|
|
|
def _lines(doc: dict[str, Any]) -> list[dict[str, Any]]:
|
|
return [
|
|
one
|
|
for one in doc.get("담은로직") or []
|
|
if fixed.is_fixed(one) and not one[fixed.FIXED].get("비율")
|
|
]
|
|
|
|
|
|
def rows(keys: list[str] | None = None, doc: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
"""고정 줄 표 — `keys` = 이 층 사전조합들 · `doc` = 열린 문서 → `{줄 [...], 문서 [{키, 이름}]}`."""
|
|
finder, out, docs = Finder(), [], []
|
|
if doc is not None:
|
|
out = [finder.row(None, one) for one in _lines(doc)]
|
|
for key in keys or []:
|
|
refs.check_key("precombo", key)
|
|
got = store.read("precombo", key)["문서"]
|
|
docs.append({"키": key, "이름": got.get("이름") or key})
|
|
out += [finder.row(key, one) for one in _lines(got)]
|
|
return {"줄": out, "문서": docs}
|
|
|
|
|
|
def _total(key: str) -> float | None:
|
|
at = refs.here()
|
|
got = refs.Expand(m01.loaded()[1])({"층": at[0], "주인": at[1], "키": key})
|
|
return _num((got.get("기준") or {}).get("계"))
|
|
|
|
|
|
def apply(changes: list[dict[str, Any]]) -> dict[str, Any]:
|
|
"""바꿈 → 이 층 사전조합 이음 칸만 고쳐 씀 · M01 에 없는 키 400(`Labor_Fixed.clean`) →
|
|
`{바뀜, 문서 [{키, 이름, 전, 후}], 되돌림 [{키, 줄, 노임, 자재}]}`."""
|
|
if len(changes) > CHANGES_MAX:
|
|
raise store.StoreError(400, f"바꿈은 {CHANGES_MAX} 줄 안")
|
|
by_key: dict[str, list[dict[str, Any]]] = {}
|
|
for one in changes:
|
|
if not isinstance(one, dict) or not isinstance(one.get("줄"), str):
|
|
raise store.StoreError(400, "바꿈 줄은 {키, 줄, 노임?, 자재?}")
|
|
refs.check_key("precombo", str(one.get("키") or ""))
|
|
by_key.setdefault(str(one["키"]), []).append(one)
|
|
whole, done, back, docs = m01.loaded()[1], 0, [], []
|
|
for key, wants in by_key.items():
|
|
got = store.read("precombo", key)
|
|
doc, before = got["문서"], _total(key)
|
|
lines = {fixed.fixed_id(x[fixed.FIXED]): x for x in _lines(doc)}
|
|
for want in wants:
|
|
one = lines.get(want["줄"])
|
|
if one is None:
|
|
raise store.StoreError(404, f"없는 고정 줄 「{want['줄']}」 ({key})")
|
|
link = dict(one[fixed.FIXED].get("이음") or {})
|
|
back.append({"키": key, "줄": want["줄"], **{k: link.get(k) for k in KINDS}})
|
|
for k in KINDS:
|
|
if k in want:
|
|
link[k] = want[k] or None
|
|
one[fixed.FIXED] = fixed.clean({**one[fixed.FIXED], "이음": link}, want["줄"], whole)
|
|
done += 1
|
|
store.write("precombo", key, got["판"], doc)
|
|
docs.append({"키": key, "이름": doc.get("이름") or key, "전": before, "후": _total(key)})
|
|
return {"바뀜": done, "문서": docs, "되돌림": back}
|