feat(740-1): 단가산출 · 일위대가 서로 독립 — 넣기 펼침(M01 키 줄 + 덮개 사본 · 본뜬) · 초기화 길 · 옮기기 함수 · B09 덮개 줄 굳힘
- 새 M02_Library_Flat: SK 에 SK = 더한줄 로직 한 줄에 원본 몸 통째(덮개 · 뺀줄 · 고름 · 더한줄 · % 덧줄까지 같은 금액) · SC 에 SK = 로직 줄 + 패치 · SK 에 SC = 줄마다 더한줄(수량 = 저장 수량.손 › 실무수량 › 1) · 바탕 SK · 패치 SK 참조 = 덮개 겹침 · 줄 본뜬 {층, 주인, 키, 이름, 때}
- 저장 · 읽기 · 층 복사 때 서버가 펼침(못 펼친 줄 = 옛 참조 그대로) · SC 안 SC(하위 호표)는 저장 · 읽기에서 참조 그대로(엑셀 하위 호표 끝수 · B09 하위 대가) · 찾아 넣기 길만 펴 넣음
- 길: POST {kind}-flat(넣을 줄 · 못 펼침 400) · POST {kind}-reset(미리 보기 걷힐 수 · 씀)
- 초기화 = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸까지) · 항목 · 수량 · 입력값 · 고름 남김
- 옮기기 = migrate(apply) · 전후 1 단위 금액 같을 때만 씀 · 공유 storage 는 브레인
- 둘째 기초 세부 셈 = 넣은 SK 몸이면 이름 없는 SK 로(Sangeun_Calc.sub_run)
- B09: SC 사본 덮개 줄 = 굳힌 줄(굳힌 {키, 줄 …}) → 사본 셈 · 대가 블록이 덮개를 읽음(옛 산근 길은 덮개 무시 · 실측 6,451 대 M02 210,866)
- 733 지문 = 옛 참조 줄 펼친 꼴로 대조
- 낡은 시험 고침(까닭 = 단가산출이 낀 참조가 이제 펼쳐 담김): 715 store(바탕 · 사전조합 SK · 층 복사) · 717-2a 목록 바탕 · 717-4 SC 안 SK · 719-1 원본판(원본 고쳐도 그대로) · 719-2 속 일위대가(빠진 줄 없음 · 쓰지 않는 _calc_given 지움) · 725-1 SK 안 SC · 729 SK 줄 본뜬 · 733-3 쓰는 곳 = 구조물 조합(SK 안 SK 참조 없음) · 572 지문 대조 대상
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,7 +50,7 @@ def freeze(copies: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
|
||||
sks = sorted(rid for rid, doc in copies.items() if is_sangeun(doc))
|
||||
if not sks:
|
||||
if not sks and not any(_patched(doc) for doc in copies.values()):
|
||||
return copies
|
||||
whole = m01.loaded()[1]
|
||||
|
||||
@@ -86,19 +86,76 @@ def freeze(copies: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||||
if got["켜쌓기율"] is not None:
|
||||
frozen["켜쌓기율"] = prices.to_json(got["켜쌓기율"])
|
||||
out[rid] = frozen
|
||||
for rid in sorted(rid for rid, doc in copies.items() if _patched(doc)):
|
||||
out[rid] = _freeze_lines(rid, copies[rid], whole, read, taken)
|
||||
return out
|
||||
|
||||
|
||||
def _patched(doc: Any) -> list[int]:
|
||||
"""일위대가(SC) 사본의 덮개 줄(로직 줄 + `패치` · 740-1 SK 넣기 꼴) 차례."""
|
||||
if not isinstance(doc, dict) or doc.get("종류") != refs.TITLE["precombo"]:
|
||||
return []
|
||||
lines = doc.get("담은로직") or []
|
||||
return [
|
||||
n for n, x in enumerate(lines) if isinstance(x, dict) and x.get("로직") and x.get("패치")
|
||||
]
|
||||
|
||||
|
||||
def _freeze_lines(rid: str, doc: dict, whole: Any, read: Any, taken: set[str]) -> dict:
|
||||
"""SC 사본 덮개 줄마다 고친 줄을 풀어 굳힘(740-1 · B09 대가 · 사본 셈이 덮개를 읽게) —
|
||||
줄은 그대로 두고 덧칸 `굳힌 {키, 줄, 입력값, 켜쌓기율, 고름, 자재표시, 정체}`(고름 ·
|
||||
자재표시 = 굳힌 줄 차례) · 못 푼 줄 = 그대로(사본 셈이 옛 길)."""
|
||||
from M02_MasterTemplete import M02_Item_Prices as prices
|
||||
from M02_MasterTemplete import M02_Labor_Patch as patch
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from M02_MasterTemplete.M02_Labor_Expand import Expand
|
||||
|
||||
lines = list(doc.get("담은로직") or [])
|
||||
for n in _patched(doc):
|
||||
one = lines[n]
|
||||
try:
|
||||
got = calc.resolve(sd.clean(patch.as_doc(one, whole), None, whole), whole, read, (rid,))
|
||||
row = {**got["줄"], "호표": [dict(x) for x in got["줄"]["호표"]]}
|
||||
calc._bundles(row, whole, Expand(whole, read), (rid,)) # 고정 더한줄 = 값묶음
|
||||
except sd.DocError:
|
||||
continue
|
||||
key = frozen_key(f"{rid}#{n}", taken)
|
||||
fz = {"키": key, "줄": prices.to_json({**row, "키": key}), "정체": list(got["정체"])}
|
||||
fz |= {
|
||||
"입력값": prices.to_json(got["입력값"]),
|
||||
"고름": got["고름"],
|
||||
"자재표시": got["자재표시"],
|
||||
}
|
||||
if got["켜쌓기율"] is not None:
|
||||
fz["켜쌓기율"] = prices.to_json(got["켜쌓기율"])
|
||||
lines[n] = {**one, FROZEN: fz}
|
||||
return {**doc, "담은로직": lines}
|
||||
|
||||
|
||||
FROZEN = "굳힌"
|
||||
|
||||
|
||||
def rows(copies: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""굳힌 SK 줄 — 사본 `단가.json` `FILE` 에 넣음(이름표 차례)."""
|
||||
return [doc[ROW] for _, doc in sorted(copies.items()) if is_sangeun(doc) and ROW in doc]
|
||||
"""굳힌 SK 줄 + SC 덮개 줄 굳힌 줄(740-1) — 사본 `단가.json` `FILE` 에 넣음(이름표 차례)."""
|
||||
out = [doc[ROW] for _, doc in sorted(copies.items()) if is_sangeun(doc) and ROW in doc]
|
||||
for _, doc in sorted(copies.items()):
|
||||
out += [
|
||||
x[FROZEN]["줄"]
|
||||
for x in doc.get("담은로직") or []
|
||||
if isinstance(x, dict) and x.get(FROZEN)
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
def materials(copies: dict[str, Any]) -> list[str]:
|
||||
"""굳힌 SK 고른 자재 키 — 단가 묶음에 담음."""
|
||||
return [
|
||||
out = [
|
||||
str(mt)
|
||||
for doc in copies.values()
|
||||
if is_sangeun(doc)
|
||||
for mt in (doc.get("고름") or {}).values()
|
||||
]
|
||||
for doc in copies.values():
|
||||
lines = [x for x in doc.get("담은로직") or [] if isinstance(x, dict) and x.get(FROZEN)]
|
||||
out += [str(mt) for x in lines for mt in (x[FROZEN].get("고름") or {}).values()]
|
||||
return out
|
||||
|
||||
@@ -105,8 +105,13 @@ class Expand:
|
||||
"""사전조합 안 로직 줄 — 일위대가 로직 줄과 같은 `run_line` · 배는 물량 알약 것."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
pill = pill or {}
|
||||
if one.get(patch.FIELD): # 줄 덮개(715-3) = 이름 없는 단가산출 · 별도계상까지
|
||||
pill, fz = pill or {}, one.get("굳힌")
|
||||
if isinstance(fz, dict) and fz.get("키"): # 사본 굳힌 덮개 줄(740-1 · `Item_Sangeun`)
|
||||
got = labor.run_line(
|
||||
self.whole, fz["키"], fz.get("입력값"), fz.get("켜쌓기율"), fz.get("고름") or None
|
||||
)
|
||||
got = labor.drop_apart(got, fz.get("자재표시"))
|
||||
elif one.get(patch.FIELD): # 줄 덮개(715-3) = 이름 없는 단가산출 · 별도계상까지
|
||||
got = patch.run(self.whole, one, self.read)
|
||||
else:
|
||||
got = labor.run_line(
|
||||
|
||||
@@ -155,6 +155,10 @@ def clean_doc(kind: str, doc: Any) -> dict[str, Any]:
|
||||
raise _bad("서고 문서가 묶음 아님")
|
||||
key = str(doc.get("키") or "")
|
||||
check_key(kind, key)
|
||||
if here()[0] != "project": # 740-1 — M02 참조 줄 = M01 키 줄로 펼침(못 펼친 줄은 그대로)
|
||||
from M02_MasterTemplete import M02_Library_Flat as flat
|
||||
|
||||
doc = flat.flat_doc(kind, doc)
|
||||
name, unit = str(doc.get("이름") or "").strip(), str(doc.get("단위") or "").strip()
|
||||
if kind == "sangeun":
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
@@ -239,13 +243,14 @@ def _combo_lines(doc: dict, key: str) -> list[dict[str, Any]]:
|
||||
일위대가와 같은 길(`labor.clean` · 같은 로직 · 같은 참조 두 번 · 입력값) · 수량 기본 {손: 1}."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
lines = []
|
||||
for one in labor.clean({"담은로직": doc.get("담은로직") or []})["담은로직"]:
|
||||
lines, raw = [], doc.get("담은로직") or []
|
||||
for one, sent in zip(labor.clean({"담은로직": raw})["담은로직"], raw):
|
||||
head = {k: one[k] for k in ("참조", "고정", "패치") if k in one} # 패치 = 줄 덮개(715-3)
|
||||
if "로직" in one:
|
||||
head.update(로직=one["로직"], 입력값=one["입력값"])
|
||||
head.update({k: one[k] for k in ("켜쌓기율", "고름", "자재표시") if k in one})
|
||||
head.update({"별칭": one["별칭"]} if one["별칭"] else {}) # 실무 품명(324-3)
|
||||
head.update(_mark(sent)) # 넣은 줄 출처(740-1 · 셈에 안 씀)
|
||||
lines.append({**head, "수량": one["수량"], "메모": one["메모"]})
|
||||
at_ = here()
|
||||
me = {"층": at_[0], "주인": at_[1], "키": key}
|
||||
@@ -263,13 +268,20 @@ def _combo_mats(doc: dict, lines: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
로직 자재와 같은 품목 400(`labor.no_twice`)."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
mats = labor.clean({"자재": doc.get("자재") or []})["자재"]
|
||||
raw = doc.get("자재") or []
|
||||
mats = [{**m, **_mark(s)} for m, s in zip(labor.clean({"자재": raw})["자재"], raw)]
|
||||
if mats:
|
||||
got = {"담은로직": lines, "자재": mats}
|
||||
labor.no_twice(got, labor.calc(got, {}))
|
||||
return mats
|
||||
|
||||
|
||||
def _mark(sent: Any) -> dict[str, Any]:
|
||||
"""줄 `본뜬`(740-1 · 펼쳐 넣은 출처 {층, 주인, 키, 이름, 때}) — 묶음만 · 셈에 안 씀."""
|
||||
got = sent.get("본뜬") if isinstance(sent, dict) else None
|
||||
return {"본뜬": got} if isinstance(got, dict) else {}
|
||||
|
||||
|
||||
def _no_loop(ref: Ref, trail: list[Ref]) -> None:
|
||||
"""저장 때 순환 · 깊이 — 아래로 따라가 처음 문서를 다시 만나면 422 「A → B → A」."""
|
||||
if len(trail) > DEPTH:
|
||||
@@ -406,8 +418,12 @@ def _reused(kind: str, doc: dict[str, Any], ref: Ref) -> str | None:
|
||||
def carry(doc: dict[str, Any], source: Here) -> dict[str, Any]:
|
||||
"""복사(원본 층 `source` → 지금 층) — 지금 층이 못 읽는 참조는 그 문서를 지금 층에 새 키로
|
||||
같이 복사(`본뜬` = 원래 참조) · 참조를 새 키로 바꿈 · 같은 원본 같은 내용 사본이 이미 있으면
|
||||
그 키(733-2). 원본 층 읽기 권한은 부르는 쪽 몫."""
|
||||
그 키(733-2). 원본 층 읽기 권한은 부르는 쪽 몫 · 단가산출 · 일위대가 = 먼저 펼침(740-1)."""
|
||||
target, done = here(), {}
|
||||
if doc.get("종류") in (TITLE["sangeun"], TITLE["precombo"]):
|
||||
from M02_MasterTemplete import M02_Library_Flat as flat
|
||||
|
||||
doc = flat.flat_doc("sangeun" if doc["종류"] == TITLE["sangeun"] else "precombo", doc)
|
||||
|
||||
def move(ref: Ref, depth: int) -> Ref:
|
||||
if readable(target, ref):
|
||||
|
||||
@@ -0,0 +1,585 @@
|
||||
"""M02 단가산출(SK) · 일위대가(SC) 펼침 · 초기화 · 옮기기(PLAN 740-1 · 요구 581 · 분석 579-2).
|
||||
|
||||
서로 독립 — 연결 고리 = M01 키만. M02 문서를 넣으면(찾아 넣기 `-flat` · 저장 · 읽기 · 층 사이
|
||||
복사) 그 문서 몸을 M01 키 줄 + 그 문서 덮개 사본으로 펼쳐 담음 → 원본이 나중에 바뀌어도
|
||||
넣은 쪽 금액 그대로. 줄 `본뜬 {층, 주인, 키, 이름, 때}` = 출처 정보(셈에 안 씀).
|
||||
SK 에 SK 더한줄 로직 한 줄 = 원본 몸 통째(바탕 기초 · 입력값 · 덮개 · 뺀줄 · 고름 · 자재표시 ·
|
||||
켜쌓기율 · 더한줄 — `M02_Sangeun_Doc.BODY` · % 덧줄까지 같은 금액) ·
|
||||
수량 = 넣은 수 ÷ 배(728 환산) · 바탕 없는 SK = 그 더한줄 × 넣은 수
|
||||
SK 에 SC SC 줄마다 더한줄(로직 줄 = 로직 한 줄 + 패치 몸 · 고정 · 자재 = 재료) ·
|
||||
수량 = 넣은 수 × 줄 수량(저장 `수량.손` › 실무수량 › 1 · 742-1) ÷ 배(물량 알약 배)
|
||||
SC 에 SK 로직 줄 하나 + 패치(= SK 몸) · 별칭 = SK 이름 · 같은 자리(안 줄 알약 키 그대로) ·
|
||||
바탕 없는 SK · 단위 환산 SK = 못 넣음(값 단위가 달라짐)
|
||||
SC 에 SC 찾아 넣기 길만 — 그 자리에 안 줄 · 자재를 펴 넣음 · 줄 수량 · 본뜬 실무수량 =
|
||||
바깥 × 안 · 같은 기초 · 고정 · 자재가 겹치면 못 넣음(일위대가 줄 짝 = 로직 키) ·
|
||||
저장 · 읽기 · 옮기기는 하위 호표 참조 그대로(엑셀 하위 호표 끝수 · B09 하위 대가)
|
||||
바탕 SK 참조 · 패치 있는 SK 참조 줄 = 위 덮개를 아래 덮개에 겹쳐 한 벌(`compose`)
|
||||
못 펼친 줄 = 저장 · 읽기 · 옮기기에선 옛 참조 그대로(값 그대로) · 넣기 길(`strict`)에선 400 까닭.
|
||||
초기화(`reset`) = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸까지) · 항목 · 수량 · 입력값 · 고름 ·
|
||||
자재표시 · 켜쌓기율 · 고정 줄 · 기본정보 남김 → 값 = 연결된 M01 키 값.
|
||||
옮기기(`migrate`) = 층 문서 펼쳐 다시 씀 — 전후 1 단위 금액 같을 때만(안 줄 수량 모두 1 로 대조) ·
|
||||
쓰는 구조물 조합이 있는 SC 펼쳐 넣기(차례 바뀜)는 안 옮김 · 공유 storage 는 브레인이 돌림.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from M02_MasterTemplete import M02_Labor_Fixed as fixed
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||
|
||||
ADD = re.compile(r"^add:#(\d+)$")
|
||||
FAIL = (ArithmeticError, KeyError, TypeError, ValueError) # 못 펼침(DocError · FormulaError 포함)
|
||||
EMPTY = (None, {}, [], "")
|
||||
Read = Any
|
||||
|
||||
|
||||
def _whole() -> Any:
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
|
||||
return m01.loaded()[1]
|
||||
|
||||
|
||||
def _d(value: Any) -> Decimal:
|
||||
return Decimal(str(value))
|
||||
|
||||
|
||||
def mark(ref: refs.Ref, doc: dict) -> dict[str, Any]:
|
||||
"""줄 출처 — 펼친 원본 {층, 주인, 키, 이름, 때(UTC)}."""
|
||||
when = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
return {
|
||||
**{k: ref.get(k) for k in ("층", "주인", "키")},
|
||||
"이름": doc.get("이름") or "",
|
||||
"때": when,
|
||||
}
|
||||
|
||||
|
||||
def _read(ref: refs.Ref, read: Read, trail: tuple) -> tuple[dict, tuple]:
|
||||
rid = refs.ref_id(ref)
|
||||
if rid in trail:
|
||||
raise sd.DocError("순환 — " + " → ".join(t.rsplit("|", 1)[-1] for t in (*trail, rid)), 422)
|
||||
if len(trail) >= refs.DEPTH:
|
||||
raise sd.DocError(f"참조가 {refs.DEPTH}단을 넘음", 422)
|
||||
got = read(ref)
|
||||
if not isinstance(got, dict):
|
||||
raise sd.DocError(f"없는 참조 「{ref.get('키')}」")
|
||||
return got, (*trail, rid)
|
||||
|
||||
|
||||
# ── SK ────────────────────────────────────────────────────────────────
|
||||
def flat_sk(doc: dict, read: Read, whole: Any, trail: tuple = (), strict: bool = True) -> dict:
|
||||
"""SK 문서 → M01 키만(더한줄 참조 펼침 · 바탕 참조 겹침) · `strict` False = 못 펼친 줄
|
||||
그대로."""
|
||||
adds = []
|
||||
for one in doc.get("더한줄") or []:
|
||||
ref = one.get("참조") if isinstance(one, dict) else None
|
||||
if not isinstance(ref, dict):
|
||||
adds.append(one)
|
||||
continue
|
||||
try:
|
||||
adds += into_sk(ref, one.get("수량", 1), read, whole, trail)
|
||||
except FAIL as e:
|
||||
if strict:
|
||||
raise sd.DocError(f"「{ref.get('키')}」 · {e}") from e
|
||||
adds.append(one)
|
||||
out = {**doc, "더한줄": adds}
|
||||
base = doc.get("바탕") or {}
|
||||
if isinstance(base.get("참조"), dict):
|
||||
try:
|
||||
under, path = _read(base["참조"], read, trail)
|
||||
under = flat_sk(sd.clean(under, None, whole), read, whole, path)
|
||||
out = compose(under, out, whole)
|
||||
except FAIL:
|
||||
if strict:
|
||||
raise
|
||||
return out
|
||||
|
||||
|
||||
def into_sk(ref: refs.Ref, qty: Any, read: Read, whole: Any, trail: tuple = ()) -> list[dict]:
|
||||
"""SK 에 넣을 더한줄 — 넣는 문서(SK · SC)를 M01 키 줄로(`qty` = 그 문서 단위 수)."""
|
||||
src, path = _read(ref, read, trail)
|
||||
kind, q, tag = refs.kind_of(ref.get("키")), _d(qty), mark(ref, src)
|
||||
src = sd.clean(src, None, whole) if kind == "sangeun" else src
|
||||
if kind == "sangeun":
|
||||
return sk_adds(flat_sk(src, read, whole, path), q, whole, tag)
|
||||
if kind == "precombo":
|
||||
return sc_adds(flat_sc(src, read, whole, path), q, whole, tag)
|
||||
raise sd.DocError("옛 단가승격은 펼치지 않음")
|
||||
|
||||
|
||||
def filled_adds(doc: dict, whole: Any) -> list[dict]:
|
||||
"""더한줄 로직 빈 입력 = 그 SK 입력값(셈 `_fill` 의 공통 옵션 몫) — 옮겨도 같은 옵션."""
|
||||
common, out = doc.get("입력값") or {}, []
|
||||
for one in doc.get("더한줄") or []:
|
||||
if one.get("종류") == "로직":
|
||||
try:
|
||||
names = [str(s.get("이름")) for s in whole.logic(one["로직"]).get("입력") or []]
|
||||
except Exception: # noqa: BLE001 — 없는 로직 = 검사 · 셈이 까닭
|
||||
names = []
|
||||
mine = dict(one.get("입력값") or {})
|
||||
for n in names:
|
||||
if mine.get(n) in EMPTY and common.get(n) not in EMPTY:
|
||||
mine[n] = common[n]
|
||||
one = {**one, "입력값": mine}
|
||||
out.append(one)
|
||||
return out
|
||||
|
||||
|
||||
def _scaled(one: dict, q: Decimal, tag: dict) -> dict:
|
||||
return {**one, "수량": sd.plain(_d(one.get("수량") or 0) * q), "본뜬": tag}
|
||||
|
||||
|
||||
def sk_adds(y: dict, q: Decimal, whole: Any, tag: dict) -> list[dict]:
|
||||
"""펼친 SK → 더한줄 — 바탕 기초 있으면 한 줄(몸 통째) · 없으면 그 더한줄 × q."""
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
|
||||
adds = filled_adds(y, whole)
|
||||
code = (y.get("바탕") or {}).get("로직")
|
||||
if not code:
|
||||
return [_scaled(one, q, tag) for one in adds]
|
||||
row = whole.logic(code)
|
||||
names = {str(s.get("이름")) for s in row.get("입력") or []}
|
||||
unit = calc.base_unit(row)
|
||||
entry: dict[str, Any] = {
|
||||
"종류": "로직",
|
||||
"로직": code,
|
||||
"이름": y.get("이름") or code,
|
||||
"단위": unit[0] if unit else "",
|
||||
"수량": sd.plain(q / calc.ratio_of(y, row, whole)),
|
||||
"입력값": {k: v for k, v in (y.get("입력값") or {}).items() if k in names},
|
||||
}
|
||||
entry |= {
|
||||
k: y[k] for k in ("덮개", "뺀줄", "고름", "자재표시", "켜쌓기율") if y.get(k) not in EMPTY
|
||||
}
|
||||
return [{**entry, **({"더한줄": adds} if adds else {}), "본뜬": tag}]
|
||||
|
||||
|
||||
def _hand(one: dict, kept: Any) -> Decimal:
|
||||
"""SC 안 줄 수량 — 저장 `수량.손`(742-1 사용자 입력) › 실무수량 › 1."""
|
||||
raw = one.get("수량")
|
||||
got = raw.get("손") if isinstance(raw, dict) else None
|
||||
return _d(got if got not in EMPTY else kept if kept not in EMPTY else 1)
|
||||
|
||||
|
||||
def sc_adds(z: dict, q: Decimal, whole: Any, tag: dict) -> list[dict]:
|
||||
"""펼친 SC → SK 더한줄 — 줄 수량 = q × `_hand` · 로직 줄 ÷ 물량 알약 배."""
|
||||
from M02_MasterTemplete import M02_Labor_Patch as patch
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
|
||||
hands = (z.get("본뜬") or {}).get("실무수량") or {}
|
||||
lines, mats = hands.get("줄") or {}, hands.get("자재") or {}
|
||||
out = []
|
||||
for at, one in enumerate(z.get("담은로직") or []):
|
||||
n = q * _hand(one, lines.get(str(at)))
|
||||
if one.get("로직"):
|
||||
row = whole.logic(one["로직"])
|
||||
body = patch.as_doc(one, whole)
|
||||
entry = {
|
||||
"종류": "로직",
|
||||
"로직": one["로직"],
|
||||
"이름": one.get("별칭") or row.get("이름") or "",
|
||||
}
|
||||
entry |= {"수량": sd.plain(n / calc.times_of(row)), "입력값": body["입력값"]}
|
||||
keep = ("덮개", "뺀줄", "고름", "자재표시", "켜쌓기율", "더한줄")
|
||||
entry |= {k: body[k] for k in keep if body.get(k) not in EMPTY}
|
||||
elif fixed.is_fixed(one) and not refs.is_rate(one):
|
||||
entry = {
|
||||
"고정": one["고정"],
|
||||
"이름": one["고정"].get("이름") or "",
|
||||
"수량": sd.plain(n),
|
||||
}
|
||||
else:
|
||||
raise sd.DocError("비율 고정 줄 · 참조 줄은 단가산출에 못 담음")
|
||||
out.append({**entry, "본뜬": tag})
|
||||
for mat in z.get("자재") or []:
|
||||
code = str(mat.get("코드") or "")
|
||||
if not code.startswith("MT"):
|
||||
raise sd.DocError(f"개인 자재 「{code}」 는 단가산출에 못 담음")
|
||||
n = q * _hand(mat, mats.get(code))
|
||||
one = {
|
||||
"종류": "재료",
|
||||
"요소": code,
|
||||
"이름": mat.get("이름") or "",
|
||||
"단위": mat.get("단위") or "",
|
||||
}
|
||||
apart = {"별도계상": True} if mat.get("별도계상") == "별도계상" else {}
|
||||
out.append({**one, "수량": sd.plain(n), **apart, "본뜬": tag})
|
||||
return out
|
||||
|
||||
|
||||
# ── 겹침(바탕 SK · 패치 SK 참조) ─────────────────────────────────────────
|
||||
def _merge(a: dict | None, b: dict) -> dict:
|
||||
"""아래 덮개 a 위에 위 덮개 b — 품 qty 바꿈 · 배율 곱 · 단가 위 것."""
|
||||
if not a:
|
||||
return dict(b)
|
||||
out = {k: v for x in (a, b) for k, v in x.items() if k in sd.PARTS}
|
||||
if "qty" in b:
|
||||
out["qty"] = b["qty"]
|
||||
elif "배율" in b and "qty" in a:
|
||||
out["qty"] = sd.plain(_d(a["qty"]) * _d(b["배율"]))
|
||||
elif "배율" in b:
|
||||
out["배율"] = sd.plain(_d(a.get("배율", 1)) * _d(b["배율"]))
|
||||
else:
|
||||
out |= {k: a[k] for k in ("qty", "배율") if k in a}
|
||||
return out | ({"원값": a["원값"]} if "원값" in a else {})
|
||||
|
||||
|
||||
def _cover_add(add: dict, cover: dict) -> dict:
|
||||
"""위 덮개를 아래 더한줄 항목에 — 수량 · 그 비목 단가만(그 밖 = 못 겹침)."""
|
||||
out = dict(add)
|
||||
if "qty" in cover:
|
||||
out["수량"] = cover["qty"]
|
||||
elif "배율" in cover:
|
||||
out["수량"] = sd.plain(_d(add.get("수량") or 0) * _d(cover["배율"]))
|
||||
prices = {sd.PARTS[k]: cover[k] for k in sd.PARTS if k in cover}
|
||||
if prices and (add.get("종류") not in sd.ADD_PART or set(prices) != {add.get("비목")}):
|
||||
raise sd.DocError("더한줄 단가 덮개를 못 겹침")
|
||||
if prices:
|
||||
out["단가"] = prices[add["비목"]]
|
||||
return out
|
||||
|
||||
|
||||
def compose(under: dict, over: dict, whole: Any) -> dict:
|
||||
"""펼친 바탕 SK `under` 위에 `over`(덮개 · 뺀줄 · 고름 · 자재표시 · 더한줄 · 입력값) → 한 벌 —
|
||||
위 줄 정체를 아래 바탕 정체로 옮김(`patched` 의 `정체` 짝) · 짝 잃은 위 고침은 버림(값 없음)."""
|
||||
base = under.get("바탕")
|
||||
row = whole.logic(base["로직"]) if (base or {}).get("로직") else sd.empty_row(under)
|
||||
got = sd.patched(row, under)
|
||||
where = dict(zip(sd.line_ids(got["줄"]), got["정체"]))
|
||||
covers = {c: dict(v) for c, v in (under.get("덮개") or {}).items()}
|
||||
drop, picks = list(under.get("뺀줄") or []), dict(under.get("고름") or {})
|
||||
marks, adds, gone = (
|
||||
dict(under.get("자재표시") or {}),
|
||||
[dict(x) for x in under.get("더한줄") or []],
|
||||
set(),
|
||||
)
|
||||
|
||||
def to(cid: str) -> tuple[str, Any] | None:
|
||||
o = where.get(cid)
|
||||
hit = ADD.match(o) if o else None
|
||||
return None if o is None else ("add", int(hit[1])) if hit else ("base", o)
|
||||
|
||||
for cid, cover in (over.get("덮개") or {}).items():
|
||||
if t := to(cid):
|
||||
if t[0] == "base":
|
||||
covers[t[1]] = _merge(covers.get(t[1]), cover)
|
||||
else:
|
||||
adds[t[1]] = _cover_add(adds[t[1]], cover)
|
||||
for cid in over.get("뺀줄") or []:
|
||||
if (t := to(cid)) and t[0] == "add":
|
||||
gone.add(t[1])
|
||||
elif t and t[1] not in drop:
|
||||
drop.append(t[1])
|
||||
for name, mine, into in (
|
||||
("고름", over.get("고름"), picks),
|
||||
("자재표시", over.get("자재표시"), marks),
|
||||
):
|
||||
for cid, value in (mine or {}).items():
|
||||
if (t := to(cid)) and t[0] == "add":
|
||||
raise sd.DocError(f"더한줄 {name}를 못 겹침")
|
||||
if t:
|
||||
into[t[1]] = value
|
||||
rate = over.get("켜쌓기율", under.get("켜쌓기율"))
|
||||
out = {**over, "바탕": base, "덮개": covers, "뺀줄": drop, "고름": picks, "자재표시": marks}
|
||||
out["더한줄"] = [a for k, a in enumerate(adds) if k not in gone] + list(
|
||||
over.get("더한줄") or []
|
||||
)
|
||||
out["입력값"] = {**(under.get("입력값") or {}), **(over.get("입력값") or {})}
|
||||
return out | ({"켜쌓기율": rate} if rate is not None else {})
|
||||
|
||||
|
||||
# ── SC ────────────────────────────────────────────────────────────────
|
||||
def sk_line(y: dict, one: dict, whole: Any, tag: dict) -> dict:
|
||||
"""펼친 SK → 일위대가 로직 줄 하나(+ 패치) — 옛 참조 줄 `one` 의 별칭 · 메모 · 수량 · 패치
|
||||
이어받음."""
|
||||
from M02_MasterTemplete import M02_Labor_Patch as patch
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
|
||||
if one.get(patch.FIELD):
|
||||
y = compose(y, {"입력값": {}, **one[patch.FIELD]}, whole)
|
||||
code = (y.get("바탕") or {}).get("로직")
|
||||
if not code:
|
||||
raise sd.DocError("바탕 기초 없는 단가산출은 일위대가 줄로 못 담음 — 그 안 줄을 따로 넣음")
|
||||
row = whole.logic(code)
|
||||
if calc.ratio_of(y, row, whole) != calc.times_of(row):
|
||||
raise sd.DocError("단위 환산 단가산출은 일위대가 줄로 못 담음(값 단위가 바뀜)")
|
||||
ids = sd.line_ids(row)
|
||||
names = {str(s.get("이름")) for s in row.get("입력") or []}
|
||||
at = {c: str(n) for n, c in enumerate(ids)}
|
||||
body = {k: y[k] for k in ("덮개", "뺀줄") if y.get(k) not in EMPTY}
|
||||
body |= {"더한줄": adds} if (adds := filled_adds(y, whole)) else {}
|
||||
line: dict[str, Any] = {
|
||||
"로직": code,
|
||||
"입력값": {k: v for k, v in (y.get("입력값") or {}).items() if k in names},
|
||||
"별칭": one.get("별칭") or y.get("이름"),
|
||||
"메모": one.get("메모"),
|
||||
"수량": one.get("수량"),
|
||||
}
|
||||
for name in ("고름", "자재표시"):
|
||||
if got := {at[c]: v for c, v in (y.get(name) or {}).items() if c in at}:
|
||||
line[name] = got
|
||||
if y.get("켜쌓기율") is not None:
|
||||
line["켜쌓기율"] = y["켜쌓기율"]
|
||||
return {**line, **({patch.FIELD: body} if body else {}), "본뜬": tag}
|
||||
|
||||
|
||||
def _ids(doc: dict) -> list[Any]:
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
lines = [labor.line_id(x) for x in doc.get("담은로직") or [] if isinstance(x, dict)]
|
||||
return lines + [f"자재|{m.get('코드')}" for m in doc.get("자재") or [] if isinstance(m, dict)]
|
||||
|
||||
|
||||
def _twice(doc: dict) -> str | None:
|
||||
seen: set[str] = set()
|
||||
for one in _ids(doc):
|
||||
key = json.dumps(one, ensure_ascii=False, sort_keys=True, default=str)
|
||||
if key in seen:
|
||||
return str(one)
|
||||
seen.add(key)
|
||||
return None
|
||||
|
||||
|
||||
def flat_sc(doc: dict, read: Read, whole: Any, trail: tuple = (), strict: bool = True) -> dict:
|
||||
"""SC 문서 → M01 키만 — SK 참조 = 로직 줄 · SC 참조 = 안 줄 펴 넣음(`strict` 만 · 넣기 길) ·
|
||||
`strict` False(저장 · 읽기 · 옮기기) = SC 참조 그대로(하위 호표 · 엑셀 끝수 단계) · 못 펼친
|
||||
줄 그대로."""
|
||||
hands = (doc.get("본뜬") or {}).get("실무수량")
|
||||
old_q = dict((hands or {}).get("줄") or {})
|
||||
new_q, mat_q = {}, dict((hands or {}).get("자재") or {})
|
||||
lines, mats = [], list(doc.get("자재") or [])
|
||||
for at, one in enumerate(doc.get("담은로직") or []):
|
||||
ref = one.get("참조") if isinstance(one, dict) else None
|
||||
kind = refs.kind_of(ref.get("키")) if isinstance(ref, dict) else None
|
||||
here_q = old_q.get(str(at))
|
||||
if kind != "sangeun" and not (strict and kind == "precombo"): # 하위 호표 = 참조 그대로
|
||||
new_q |= {str(len(lines)): here_q} if here_q is not None else {}
|
||||
lines.append(one)
|
||||
continue
|
||||
try:
|
||||
src, path = _read(ref, read, trail)
|
||||
tag = mark(ref, src)
|
||||
if kind == "sangeun":
|
||||
y = flat_sk(sd.clean(src, None, whole), read, whole, path)
|
||||
put, put_m = [sk_line(y, one, whole, tag)], []
|
||||
put_q = {"0": here_q} if here_q is not None else {}
|
||||
else:
|
||||
z = flat_sc(src, read, whole, path)
|
||||
put, put_q, put_m = _spread(z, one, here_q, tag)
|
||||
rest = (doc.get("담은로직") or [])[at + 1 :]
|
||||
trial = {"담은로직": [*lines, *put, *rest], "자재": [*mats, *put_m]}
|
||||
if twice := _twice(trial):
|
||||
raise sd.DocError(f"같은 줄이 겹침 「{twice}」 — 펼쳐 넣지 못함")
|
||||
except FAIL as e:
|
||||
if strict:
|
||||
raise sd.DocError(f"「{ref.get('키')}」 · {e}") from e
|
||||
new_q |= {str(len(lines)): here_q} if here_q is not None else {}
|
||||
lines.append(one)
|
||||
continue
|
||||
new_q |= {str(len(lines) + int(n)): v for n, v in put_q.items()}
|
||||
mat_q |= {m["코드"]: m.pop("_q") for m in put_m if "_q" in m}
|
||||
lines, mats = [*lines, *put], [*mats, *put_m]
|
||||
out = {**doc, "담은로직": lines, "자재": mats}
|
||||
if hands is not None:
|
||||
out["본뜬"] = {**doc["본뜬"], "실무수량": {**hands, "줄": new_q, "자재": mat_q}}
|
||||
return out
|
||||
|
||||
|
||||
def _spread(z: dict, one: dict, here_q: Any, tag: dict) -> tuple[list, dict, list]:
|
||||
"""안 SC 줄 · 자재 펴기 — 실무수량 = 바깥 × 안(둘 다 있을 때만) · 비율 고정 줄 있으면 못 폄
|
||||
(밑 = 그 SC 합 → 바깥 합으로 바뀜)."""
|
||||
if any(refs.is_rate(x) for x in z.get("담은로직") or []):
|
||||
raise sd.DocError("비율 고정 줄이 있는 일위대가는 펴 넣지 못함(밑 합이 바뀜)")
|
||||
hands = (z.get("본뜬") or {}).get("실무수량") or {}
|
||||
inner, inner_m = hands.get("줄") or {}, hands.get("자재") or {}
|
||||
outer = (one.get("수량") or {}).get("손") if isinstance(one.get("수량"), dict) else None
|
||||
|
||||
def times(
|
||||
x: dict,
|
||||
) -> Any: # 줄 수량(742-1) = 바깥 × 안 · 둘 중 하나라도 없으면 쓰는 쪽 몫(null)
|
||||
inner = (x.get("수량") or {}).get("손") if isinstance(x.get("수량"), dict) else None
|
||||
both = outer not in EMPTY and inner not in EMPTY
|
||||
return {"손": sd.plain(_d(outer) * _d(inner))} if both else None
|
||||
|
||||
lines = [{**x, "수량": times(x), "본뜬": tag} for x in z.get("담은로직") or []]
|
||||
if len(lines) == 1 and one.get("별칭"):
|
||||
lines[0]["별칭"] = one["별칭"]
|
||||
both = here_q is not None
|
||||
qty = {
|
||||
str(n): sd.plain(_d(here_q) * _d(inner[str(n)]))
|
||||
for n in range(len(lines))
|
||||
if both and inner.get(str(n)) is not None
|
||||
}
|
||||
mats = []
|
||||
for m in z.get("자재") or []:
|
||||
extra = (
|
||||
{"_q": sd.plain(_d(here_q) * _d(inner_m[m["코드"]]))}
|
||||
if both and m.get("코드") in inner_m
|
||||
else {}
|
||||
)
|
||||
mats.append({**m, "본뜬": tag, **extra})
|
||||
return lines, qty, mats
|
||||
|
||||
|
||||
# ── 문 ────────────────────────────────────────────────────────────────
|
||||
def flat_doc(kind: str, doc: Any, read: Read = refs.read_ref, strict: bool = False) -> Any:
|
||||
"""층 문서 하나 펼침(저장 · 읽기 · 복사 · 옮기기) — 참조 없으면 받은 그대로."""
|
||||
if not isinstance(doc, dict) or kind not in refs.PREFIX or not refs.refs_in(doc):
|
||||
return doc
|
||||
whole = _whole()
|
||||
if kind == "sangeun":
|
||||
return flat_sk(doc, read, whole, (), strict)
|
||||
return flat_sc(doc, read, whole, (), strict)
|
||||
|
||||
|
||||
def flat_lines(kind: str, ref: Any, qty: Any = 1, doc: Any = None) -> dict[str, Any]:
|
||||
"""찾아 넣기(`-flat` 길) — `kind` 문서에 `ref` 를 넣을 줄 · 못 펼침 = StoreError 400 ·
|
||||
SC = 지금 화면 문서 `doc` 과 겹치면 400."""
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
at = refs.here()
|
||||
try:
|
||||
ref = refs.check_ref(ref, "넣을 문서", at)
|
||||
whole = _whole()
|
||||
if kind == "sangeun":
|
||||
return {"더한줄": into_sk(ref, qty if qty not in EMPTY else 1, refs.read_ref, whole)}
|
||||
got = flat_sc({"담은로직": [{"참조": ref, "수량": None}]}, refs.read_ref, whole)
|
||||
host = doc if isinstance(doc, dict) else {}
|
||||
trial = {k: [*(host.get(k) or []), *got[k]] for k in ("담은로직", "자재")}
|
||||
if twice := _twice(trial):
|
||||
raise sd.DocError(f"이미 담은 줄과 겹침 「{twice}」")
|
||||
return {"담은로직": got["담은로직"], "자재": got["자재"]}
|
||||
except FAIL as e:
|
||||
raise store.StoreError(getattr(e, "status", 400), str(e)) from e
|
||||
|
||||
|
||||
# ── 초기화 ────────────────────────────────────────────────────────────
|
||||
def _reset_adds(adds: list) -> tuple[list, int]:
|
||||
out, n = [], 0
|
||||
for one in adds or []:
|
||||
one = dict(one) if isinstance(one, dict) else one
|
||||
if not isinstance(one, dict):
|
||||
out.append(one)
|
||||
continue
|
||||
if "단가" in one:
|
||||
one.pop("단가")
|
||||
n += 1
|
||||
n += len(one.pop("덮개", None) or {}) + len(one.pop("뺀줄", None) or [])
|
||||
one.pop("바탕판", None)
|
||||
if one.get("더한줄"):
|
||||
one["더한줄"], k = _reset_adds(one["더한줄"])
|
||||
n += k
|
||||
out.append(one)
|
||||
return out, n
|
||||
|
||||
|
||||
def reset(kind: str, doc: dict) -> tuple[dict, int]:
|
||||
"""[초기화] — (걷은 문서, 걷힌 고침 수) · 먼저 펼침(옛 참조 줄도 M01 키로)."""
|
||||
from M02_MasterTemplete import M02_Labor_Patch as patch
|
||||
|
||||
doc = flat_doc(kind, doc)
|
||||
if kind == "sangeun":
|
||||
adds, n = _reset_adds(doc.get("더한줄") or [])
|
||||
n += len(doc.get("덮개") or {}) + len(doc.get("뺀줄") or [])
|
||||
return {**doc, "덮개": {}, "뺀줄": [], "더한줄": adds}, n
|
||||
lines, n = [], 0
|
||||
for one in doc.get("담은로직") or []:
|
||||
body = one.get(patch.FIELD) if isinstance(one, dict) else None
|
||||
if not body:
|
||||
lines.append(one)
|
||||
continue
|
||||
adds, k = _reset_adds(body.get("더한줄") or [])
|
||||
n += k + len(body.get("덮개") or {}) + len(body.get("뺀줄") or [])
|
||||
rest = {**({"더한줄": adds} if adds else {})}
|
||||
line = {x: v for x, v in one.items() if x != patch.FIELD}
|
||||
lines.append({**line, **({patch.FIELD: rest} if rest else {})})
|
||||
return {**doc, "담은로직": lines}, n
|
||||
|
||||
|
||||
def reset_doc(kind: str, key: str | None, doc: Any, look: bool, version: str | None) -> dict:
|
||||
"""`-reset` 길 — 미리 보기 = 걷힐 수 · 아니면 지금 층에 씀(판이 오면 그 판일 때만)."""
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
if doc is None:
|
||||
if not key:
|
||||
raise store.StoreError(400, "문서 또는 키")
|
||||
found = store.read(kind, key)
|
||||
doc, version = found["문서"], version or found["판"]
|
||||
if not isinstance(doc, dict):
|
||||
raise store.StoreError(400, "문서가 묶음 아님")
|
||||
got, n = reset(kind, doc)
|
||||
if look:
|
||||
return {"걷힘": n, "문서": got}
|
||||
name = str(got.get("키") or key or "")
|
||||
if not version:
|
||||
version = store.read(kind, name)["판"]
|
||||
store.write(kind, name, version, got)
|
||||
return {"걷힘": n, "문서": store.read(kind, name)["문서"]}
|
||||
|
||||
|
||||
# ── 옮기기 ────────────────────────────────────────────────────────────
|
||||
def _value(ref: refs.Ref, read: Read, whole: Any) -> Any:
|
||||
from M02_MasterTemplete.M02_Labor_Expand import Expand, inner_pills
|
||||
|
||||
given = {p["키"]: 1 for p in inner_pills(ref, read)}
|
||||
got = Expand(whole, read, given)(ref).get("기준")
|
||||
return None if got is None else {k: round(float(v), 6) for k, v in got.items()}
|
||||
|
||||
|
||||
def migrate(apply: bool = False) -> list[dict[str, Any]]:
|
||||
"""저장소 층(마스터 · 회사 · 개인) SK · SC 의 M02 참조 줄을 펼쳐 다시 씀 — 줄마다 결과
|
||||
{어디, 키, 펼침, 같음, 씀, 까닭} · `apply` False = 보기만."""
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
|
||||
whole, out = _whole(), []
|
||||
for kind in ("sangeun", "precombo"):
|
||||
for where_, path in layers.layer_files(kind):
|
||||
try:
|
||||
doc = json.loads(path.read_bytes())
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
if not refs.refs_in(doc):
|
||||
continue
|
||||
layer, owner = refs.where(path.parent.parent)
|
||||
ref = {"층": layer, "주인": owner, "키": path.stem}
|
||||
got = flat_doc(kind, doc)
|
||||
left = [
|
||||
r.get("키") for r in refs.refs_in(got) if refs.kind_of(r.get("키")) != "promote"
|
||||
]
|
||||
row = {
|
||||
"어디": where_,
|
||||
"키": path.stem,
|
||||
"펼침": left == [],
|
||||
"남은참조": left,
|
||||
"씀": False,
|
||||
}
|
||||
|
||||
def mine(r: refs.Ref, _got: dict = got, _rid: str = refs.ref_id(ref)) -> dict | None:
|
||||
return _got if refs.ref_id(r) == _rid else refs.read_ref(r)
|
||||
|
||||
row["전"], row["후"] = _value(ref, refs.read_ref, whole), _value(ref, mine, whole)
|
||||
row["같음"] = row["전"] == row["후"]
|
||||
used = kind == "precombo" and len(got["담은로직"]) != len(doc.get("담은로직") or [])
|
||||
if used and refs.users(ref):
|
||||
row["까닭"] = "쓰는 곳 있는 일위대가 — 안 줄 차례가 바뀌어 안 옮김"
|
||||
elif not row["같음"]:
|
||||
row["까닭"] = "전후 금액 다름 — 안 옮김"
|
||||
elif apply and got != doc:
|
||||
with store.at(path.parent.parent):
|
||||
store.write(kind, path.stem, store.read(kind, path.stem)["판"], got)
|
||||
row["씀"] = True
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__": # 브레인 — `python -m M02_MasterTemplete.M02_Library_Flat [--apply]`
|
||||
import sys
|
||||
|
||||
for one in migrate("--apply" in sys.argv):
|
||||
print(json.dumps(one, ensure_ascii=False, default=str))
|
||||
@@ -80,6 +80,10 @@ class Same:
|
||||
def finger(self, kind: str, doc: Any, trail: tuple = ()) -> str | None:
|
||||
if not isinstance(doc, dict):
|
||||
return None
|
||||
if not trail: # 740-1 — 옛 참조 줄 = 펼친 꼴로 대조(펼쳐 담은 문서와 같은 지문)
|
||||
from M02_MasterTemplete import M02_Library_Flat as flat
|
||||
|
||||
doc = flat.flat_doc(kind, doc)
|
||||
body = self._sk(doc, trail) if kind == "sangeun" else self._sc(doc, trail)
|
||||
if kind == "sangeun" and not body.get("바탕") and not body.get("더한줄"):
|
||||
return None
|
||||
|
||||
@@ -11,6 +11,10 @@ POST …/undo {키} — `_합친것/` 에서 되돌림 · 옮긴 참조도 되
|
||||
`M02_Library_Delete.undo`(문서 · 자리 · 고친 문서 앞판)
|
||||
POST /layers/{층}/{kind}-tree/delete-check {대상} — 나무 지우기 미리 보기(735-1) · 지울 문서 ·
|
||||
쓰는 줄
|
||||
POST /layers/{층}/{kind}-flat {참조, 수량?, 문서?} — 넣을 줄(740-1 · M02 문서 → M01 키 줄 + 덮개
|
||||
사본 · 본뜬 · `M02_Library_Flat.flat_lines`) · 층 읽기 권한 · 못 펼침 400
|
||||
POST /layers/{층}/{kind}-reset {키 | 문서, 미리보기, 판?} — [초기화](740-1) · 걷힐 수 · 문서 ·
|
||||
미리보기 false = 그 층에 씀(쓰기 권한)
|
||||
지움 = 이 층 문서만 · 남김 = 이 층 또는 위 층(위 층과 같은 아래 층 문서 = 남김 = 위 층 키) ·
|
||||
판 = 이 층 서고 나무 판(`{종류}_폴더.json`) · 권한 = 그 층 쓰기 · 프로젝트 사본 = 굳힌 값이라
|
||||
안 건드림 · 지문 = `M02_Library_Same`(733-1) 읽기만.
|
||||
@@ -31,6 +35,7 @@ from common_util.common_util_auth import verify_session
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Library_Delete as deleting
|
||||
from M02_MasterTemplete import M02_Library_Flat as flat
|
||||
from M02_MasterTemplete import M02_Library_Same as same
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Precombo_Folders as pre_folders
|
||||
@@ -380,3 +385,51 @@ def library_same_undo(
|
||||
) -> dict:
|
||||
"""되돌림 — `_합친것/<키>` 문서 · 나무 자리 · 옮긴 참조."""
|
||||
return _run(undo, session, _root(session, layer, owner, write=True), kind, body.키)
|
||||
|
||||
|
||||
class FlatBody(BaseModel):
|
||||
"""`참조` = 넣을 문서 {층, 주인, 키} · `수량` = 그 문서 단위 수(SK 만 · 기본 1) · `문서` = 지금
|
||||
화면 문서(SC 겹침 검사)."""
|
||||
|
||||
참조: dict[str, Any]
|
||||
수량: float | None = None
|
||||
문서: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class ResetBody(BaseModel):
|
||||
키: str | None = None
|
||||
문서: dict[str, Any] | None = None
|
||||
미리보기: bool = True
|
||||
판: str | None = None
|
||||
|
||||
|
||||
def _in(folder: Path, fn, *args):
|
||||
with store.at(folder):
|
||||
return fn(*args)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/{kind}-flat")
|
||||
def library_flat(
|
||||
layer: Layer,
|
||||
kind: Kind,
|
||||
body: FlatBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""찾아 넣기(740-1) — 넣을 줄(SK `더한줄` · SC `담은로직` + `자재`) · 저장 안 함."""
|
||||
folder = _root(session, layer, owner)
|
||||
return _run(_in, folder, flat.flat_lines, kind, body.참조, body.수량, body.문서)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/{kind}-reset")
|
||||
def library_reset(
|
||||
layer: Layer,
|
||||
kind: Kind,
|
||||
body: ResetBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""[초기화](740-1) — 덮개 · 뺀줄 · 더한줄 단가 걷음 → M01 키 값 · `{걷힘, 문서}`."""
|
||||
folder = _root(session, layer, owner, write=not body.미리보기)
|
||||
args = (kind, body.키, body.문서, body.미리보기, body.판)
|
||||
return _run(_in, folder, flat.reset_doc, *args)
|
||||
|
||||
@@ -27,6 +27,7 @@ from common_util.common_util_storage import resolve_stored_project_path
|
||||
from config.config_db import get_db_pool, run_with_connection
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Layer_Access as access
|
||||
from M02_MasterTemplete import M02_Library_Flat as flat
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Items as items_store
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Router_Items as items_router
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
@@ -313,6 +314,7 @@ async def read_layer_template(
|
||||
found["문서"] = refs.filled(kind, found["문서"]) # 옛 서고 문서 구분 · 상세구분(51-12)
|
||||
if layer != "project": # 옛 단가승격 참조 줄 = 새 꼴(134-2 · 프로젝트 사본은 그대로)
|
||||
found["문서"] = await asyncio.to_thread(refs.unpromote, kind, found["문서"])
|
||||
found["문서"] = await asyncio.to_thread(flat.flat_doc, kind, found["문서"]) # 740-1
|
||||
return found
|
||||
|
||||
|
||||
|
||||
@@ -216,6 +216,14 @@ def sub_run(one: dict, whole: Any, patched: bool = True) -> dict[str, Any]:
|
||||
row = whole.logic(code)
|
||||
except Exception as e: # noqa: BLE001 — 엔진 FormulaError
|
||||
return {"기준": None, "까닭": str(e), "정체": [], "짝잃음": [], "바탕판": ""}
|
||||
if any(one.get(k) not in (None, {}, []) for k in sd.BODY): # 넣은 SK 몸(740-1) = 이름 없는 SK
|
||||
keep = ("입력값", *sd.BODY, *(("덮개", "뺀줄") if patched else ()))
|
||||
doc = {"키": "SK-000000", "이름": "줄", "단위": "식", "바탕": {"로직": code}}
|
||||
doc |= {"덮개": {}, "뺀줄": [], "더한줄": [], **{k: one[k] for k in keep if k in one}}
|
||||
try:
|
||||
return {**run(doc, whole), "바탕판": fingerprint(row)}
|
||||
except sd.DocError as e:
|
||||
return {"기준": None, "까닭": str(e), "정체": [], "짝잃음": [], "바탕판": ""}
|
||||
got = sd.patched(row, one if patched else {})
|
||||
over = _Over(whole, code, {**got["줄"], "키": code})
|
||||
out = labor.run_line(over, code, one.get("입력값"), None, got["고름"] or None)
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
덮개 {cid: {qty | 배율, labor, material, expense, 원값}},
|
||||
더한줄 [{종류: 인력|재료|기계, 요소, 이름, 단위, 수량, 비목?, 단가?,
|
||||
별도계상?(재료 · 없음 = 계 안)}
|
||||
| {종류: 로직, 로직, 입력값, 수량, 덮개?, 뺀줄?, 바탕판?}
|
||||
| {종류: 로직, 로직, 입력값, 수량, 덮개?, 뺀줄?, 바탕판?,
|
||||
고름? · 자재표시? · 켜쌓기율? · 더한줄?(넣은 SK 몸 · 740-1) · 본뜬?(출처)}
|
||||
| {참조 | 고정, 이름?, 단위?, 수량}],
|
||||
뺀줄 [cid], 바탕판, 환산?}
|
||||
SK 단위 환산(728-2) = `환산` {계수: 양수}(성격 다름 · 바탕 앞 수 뗀 1 단위 = SK 몇 단위) |
|
||||
@@ -178,7 +179,7 @@ def _added(raw: Any, where: str, whole: Any) -> dict:
|
||||
if row is not None:
|
||||
head["입력값"] = {k: v for k, v in given.items() if k in names}
|
||||
name = name or str(row.get("이름") or code)
|
||||
head |= _sub_patch(raw, where)
|
||||
head |= _sub_patch(raw, where) | _sub_body(raw, where, whole)
|
||||
elif raw.get("종류") in ADD_PART:
|
||||
key = str(raw.get("요소") or "")
|
||||
got = _element(whole, key, where)
|
||||
@@ -194,7 +195,8 @@ def _added(raw: Any, where: str, whole: Any) -> dict:
|
||||
unit = unit or str((got or {}).get("단위") or "")
|
||||
else:
|
||||
raise DocError(f"{where} · 종류는 인력 · 재료 · 기계 · 로직 · 참조 · 고정")
|
||||
return {**head, "이름": name, "단위": unit, "수량": qty}
|
||||
mark = {"본뜬": raw["본뜬"]} if isinstance(raw.get("본뜬"), dict) else {} # 출처(740-1)
|
||||
return {**head, "이름": name, "단위": unit, "수량": qty, **mark}
|
||||
|
||||
|
||||
def _sub_patch(raw: dict, where: str) -> dict:
|
||||
@@ -210,9 +212,29 @@ def _sub_patch(raw: dict, where: str) -> dict:
|
||||
return {**({"덮개": covers} if covers else {}), **({"뺀줄": drop} if drop else {})}
|
||||
|
||||
|
||||
BODY = ("고름", "자재표시", "켜쌓기율", "더한줄") # 넣은 SK 몸(740-1 · `M02_Library_Flat`)
|
||||
|
||||
|
||||
def _sub_body(raw: dict, where: str, whole: Any) -> dict:
|
||||
"""넣은 SK 한 줄(740-1) — 그 SK 의 고름 · 자재표시 · 켜쌓기율 · 더한줄(M01 키만) · `본뜬`(출처
|
||||
정보 · 셈에 안 씀) · 빈 칸은 안 둠."""
|
||||
out: dict[str, Any] = {}
|
||||
if isinstance(raw.get("본뜬"), dict):
|
||||
out["본뜬"] = raw["본뜬"]
|
||||
if not any(raw.get(k) not in (None, {}, []) for k in BODY):
|
||||
return out
|
||||
body = {k: raw.get(k) for k in BODY if raw.get(k) not in (None, {}, [])}
|
||||
doc = {"키": "SK-000000", "이름": "줄", "단위": "식", "바탕": {"로직": raw.get("로직")}, **body}
|
||||
try:
|
||||
got = clean(doc, None, whole)
|
||||
except DocError as e:
|
||||
raise DocError(f"{where} · {e}") from e
|
||||
return out | {k: got[k] for k in BODY if got.get(k) not in (None, {}, [])}
|
||||
|
||||
|
||||
def sub_patched(one: dict) -> bool:
|
||||
"""고친 둘째 기초 — 더한줄 로직에 세부 덮개 · 뺀줄."""
|
||||
return one.get("종류") == "로직" and bool(one.get("덮개") or one.get("뺀줄"))
|
||||
"""고친 둘째 기초 — 더한줄 로직에 세부 덮개 · 뺀줄 · 넣은 SK 몸(740-1)."""
|
||||
return one.get("종류") == "로직" and any(one.get(k) for k in ("덮개", "뺀줄", *BODY))
|
||||
|
||||
|
||||
def _added_names(whole: Any, adds: Any) -> set[str]:
|
||||
|
||||
Reference in New Issue
Block a user