feat(742-1): 일위대가 안 줄 수량 = 사용자 입력 저장 · 기준단위 단가 / 수량 반영 금액 열
- 723-1 「안 줄 수량 null」 걷음 — SC 줄 · 자재 수량 {손} 저장(새 줄 1 · 빈 칸 = 「수량 입력」 · 금액 빠짐)
- 셈 차례: 층 = 알약 값 → 저장 수량 · 공사 굳힌 사본 = 굳힌 손 → 알약 → 저장(with_pills 가 {저장} 칸으로 옮김)
- 불러오기로 만든 일위대가 = 엑셀 수량을 저장 수량으로(본뜬.실무수량 같은 값 남김)
- 단가산출 안 일위대가 = 저장 수량으로 금액(수량 없음 없어짐) · B08 자재 · B09 내 자료집 저장도 같은 차례
- 일위대가표 수량 칸 = 문서에 저장(휘발 임시 칸 걷음) · 합계 열 「기준단위 단가」 · 「수량 반영 금액」
- 낡은 시험(723 · 175 · 204-2 · 702-3 · 703 · 717-4 · 719-1 · 719-2 · hopyo build · flow · labor ref)의 null 단언을
저장 수량 단언으로 고침 — 723-1 결정이 742-1(요구 584-1)로 바뀜 · 새 지킴 시험 test_742_1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,7 @@ def _ref_mats(
|
||||
) -> list[tuple]:
|
||||
"""참조 문서 1 단위 재료 `[(재료 줄, 수량)]` — 층 셈 펼침(`M02_Labor_Ref.Expand`)과 같은 길:
|
||||
승격 = 원 로직 재료 ÷ 배 · 사전조합 = 안 줄 재료 × 안 줄 수량(굳힌 손 · 아니면 면적 알약
|
||||
「참조|…#차례」 값 `given` · 723-1 · 없으면 안 셈) · 로직 줄은 알약 배로 나눔 ·
|
||||
「참조|…#차례」 값 `given` · 그다음 굳힌 저장 수량(742-1) · 없으면 안 셈) · 로직 줄은 알약 배로 나눔 ·
|
||||
사전조합 자재(175-2) = 손 · 알약 「…#자재|코드」(없음 = 안 셈 · 품목 키 = 코드 · `자재` 표시 =
|
||||
연료 거름 안 함) · 순환 · 깊이 넘음 · 없는 문서 = 빈 것."""
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
@@ -95,9 +95,12 @@ def _ref_mats(
|
||||
out: list[tuple] = []
|
||||
given = given or {}
|
||||
|
||||
def qty_of(one: dict, key: str) -> float | None: # 723-1 — 굳힌 손 · 아니면 이은 칸 값
|
||||
hand = _qty((one.get("수량") or {}).get("손"))
|
||||
return _qty(given.get(f"{rid}#{key}")) if hand is None else hand
|
||||
def qty_of(one: dict, key: str) -> float | None: # 굳힌 손 → 이은 칸 값 → 저장 수량(742-1)
|
||||
qty = one.get("수량") if isinstance(one.get("수량"), dict) else {}
|
||||
hand = _qty(qty.get("손"))
|
||||
if hand is None:
|
||||
hand = _qty(given.get(f"{rid}#{key}"))
|
||||
return _qty(qty.get(refs.SAVED)) if hand is None else hand
|
||||
|
||||
for at, one in enumerate(lines):
|
||||
if isinstance(one.get("참조"), dict):
|
||||
|
||||
@@ -141,7 +141,7 @@ def _copies(docs: dict[int, dict[str, Any]], tag: str, no: int) -> dict[str, dic
|
||||
rid = refs.ref_id(_ref(tag, at))
|
||||
if rid in out or at not in docs:
|
||||
continue
|
||||
# 이 공사 엑셀 수량 = 굳힌 손(723-4 · `with_pills` 의 723-1 수량 걷기는 M02 저장용)
|
||||
# 이 공사 엑셀 수량 = 굳힌 손(723-4 · `with_pills` 의 저장 수량 옮김(742-1)은 M02 저장용)
|
||||
doc = docs[at]
|
||||
out[rid] = {**refs.with_pills(doc), **{k: doc.get(k) or [] for k in ("담은로직", "자재")}}
|
||||
todo += [
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
담는 것 = 담은로직(넣을 값 · 고른 자재 얹음) · 자재 · 줄 수량(= 수량산출 수기 `손`) —
|
||||
맨 위 줄 수량 덮개 = 손(원래 손 × 덮개 ÷ 블록 줄 수량 · 대가 줄 ÷ 배를 되돌림) ·
|
||||
SC 쓸 때 손 걷힘(723-1 `refs.no_qty` · 쓰는 곳 알약 · 이 공사 입력으로 이음)
|
||||
SC 저장 수량(742-1 · 쓰는 곳 알약 · 이 공사 입력이 이어지면 그 값이 앞섬)
|
||||
안 담는 것 = 품 덮개(부른 블록 안 줄 · 품셈 블록 줄 — 로직 통째 배수로 바꾸면 다른 줄까지 바뀜) ·
|
||||
단가 덮개(사전조합 꼴에 단가 칸 없음) → 수만 알림(품은 PLAN 705 고정 줄이 생기면 담김)
|
||||
원천 = 블록 cover — 조합 `S:`/`L:` = 사본 폴더(일위대가 · 넣을 값 · 고른 자재 · 셈 수량) ·
|
||||
@@ -79,13 +79,21 @@ def _frozen(root: Path, rid: str) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
|
||||
def _hand(one: dict[str, Any], none: Any) -> Any:
|
||||
"""굳힌 줄 수량 → 층 저장 꼴 `{손}` — 굳힌 손 · 저장 수량(742-1 `refs.SAVED`) · 없으면 `none`."""
|
||||
qty = one.get("수량") if isinstance(one.get("수량"), dict) else {}
|
||||
hand = qty.get("손", qty.get(refs.SAVED))
|
||||
return none if hand is None else {"손": hand}
|
||||
|
||||
|
||||
def _precombo(doc: dict[str, Any]) -> tuple[list[dict], list[dict]]:
|
||||
lines = [
|
||||
{**one, "수량": {"손": (one.get("수량") or {}).get("손", 1)}}
|
||||
{**one, "수량": _hand(one, {"손": 1})}
|
||||
for one in doc.get("담은로직") or []
|
||||
if isinstance(one, dict)
|
||||
]
|
||||
return lines, [m for m in doc.get("자재") or [] if isinstance(m, dict)]
|
||||
mats = [{**m, "수량": _hand(m, None)} for m in doc.get("자재") or [] if isinstance(m, dict)]
|
||||
return lines, mats
|
||||
|
||||
|
||||
def source(root: Path, block: dict[str, Any], base: str) -> tuple[list[dict], list[dict]]:
|
||||
|
||||
@@ -336,17 +336,20 @@ class Builder:
|
||||
depth: int = 0,
|
||||
use: tuple[dict, str] = ({}, ""),
|
||||
) -> str:
|
||||
"""사전조합(SC) = 겹 대가 — 로직 줄 = 품셈 호표 · 자재 줄(175-2) · 줄 수량(723-4) =
|
||||
굳힌 손 → use 알약 값 → 없음(`ask`) · use = (쓰는 구조물 사본 면적 알약 값, 덮개 자리)."""
|
||||
"""사전조합(SC) = 겹 대가 — 로직 줄 = 품셈 호표 · 자재 줄(175-2) · 줄 수량(723-4 · 742-1) =
|
||||
굳힌 손 → use 알약 값 → 저장 수량(`refs.SAVED`) → 없음(`ask`) · use = (쓰는 구조물 사본 면적
|
||||
알약 값, 덮개 자리)."""
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
|
||||
rid, (given, where) = refs.ref_id(ref), use
|
||||
pilled: list[bool] = [] # 줄마다 알약 값을 썼나
|
||||
|
||||
def qty_of(key: str, hand: Any) -> float | None: # 짝 = `M02_Labor_Expand._qty` 차례
|
||||
got = _f(hand)
|
||||
def qty_of(key: str, qty: Any) -> float | None: # 짝 = `M02_Labor_Expand._qty` 차례
|
||||
qty = qty if isinstance(qty, dict) else {}
|
||||
got = _f(qty.get("손"))
|
||||
pill = None if got is not None else _f(given.get(key))
|
||||
pilled.append(got is None and key in given)
|
||||
return _f(given.get(key)) if got is None else got
|
||||
return got if got is not None else pill if pill is not None else _f(qty.get(refs.SAVED))
|
||||
|
||||
inner = [one for one in doc.get("담은로직") or [] if isinstance(one, dict)]
|
||||
kept = doc.get("알약") # 굳힌 알약(142-2 · 지금 M01 안 읽음 · 719-2) · 없으면 셈
|
||||
@@ -358,7 +361,7 @@ class Builder:
|
||||
}
|
||||
lines = []
|
||||
for n, one in enumerate(inner):
|
||||
qty = qty_of(f"{rid}#{n}", (one.get("수량") or {}).get("손"))
|
||||
qty = qty_of(f"{rid}#{n}", one.get("수량"))
|
||||
pill = pills.get(one.get("로직")) or {}
|
||||
times = pill.get("배") if "로직" in one else 1
|
||||
name = pill.get("이름") or str(one.get("로직") or "")
|
||||
@@ -368,7 +371,7 @@ class Builder:
|
||||
lines.append(self.line_of(whole, one, qty, times, name, copies, depth, use))
|
||||
for mat in doc.get("자재") or []:
|
||||
key = f"{rid}#{labor_doc.material_key(mat.get('코드'))}"
|
||||
lines.append(_material(whole, mat, qty_of(key, (mat.get("수량") or {}).get("손"))))
|
||||
lines.append(_material(whole, mat, qty_of(key, mat.get("수량"))))
|
||||
lines = [ln if ln["kind"] == "blank" or ln.get("qty") is not None else {**ln, "ask": ASK}
|
||||
for ln in lines] # fmt: skip
|
||||
return self.put(
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
참조(차이 보존) · 노임 = 이름 · 기계 = EQ(`M02_Hopyo_Fixed.Linker`) · 사용자 이음 대상 `{노임|기계: 키}` ·
|
||||
`{비율: 기준}` = 고정 줄 + 그 이음 · 남은 줄(`Fixed.linked` 아님 · 「우리에 없음」 고른 줄 뺌) 있으면 409
|
||||
`{까닭, 남은줄}`(저장 안 함 · `save` 거짓 = 막지 않고 `남은줄` 만) · 미리 보기 = `unlinked`(쓰기 없음).
|
||||
수량(723-1 · 내역 17-1) = 저장 때 걷음(`refs.no_qty`) · 엑셀 수량은 `본뜬.실무수량 {줄: {차례: 수}, 자재: {코드: 수}}`
|
||||
(출처 기록 · 셈 안 씀 · 엑셀 대조 `compare` 만 — 하위 호표 것까지 `excel_given`).
|
||||
수량(742-1 · 요구 584-1) = 엑셀 수량을 줄 · 자재 저장 수량(`{손}`)으로 · 같은 값 `본뜬.실무수량 {줄: {차례: 수},
|
||||
자재: {코드: 수}}`(출처 기록 · 엑셀 대조 `compare` — 하위 호표 것까지 `excel_given`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -98,12 +98,12 @@ def confirmed(folder: str | Path, source: refs.Here | None = None) -> dict[str,
|
||||
|
||||
|
||||
def _save(doc: dict[str, Any]) -> tuple[dict[str, Any], bool]:
|
||||
"""(참조, 다시씀) — 같은 본뜬(키 · 원본)이 있으면 그 키에 덮음 · 없으면 새 키 · 수량 걷음(723-1)."""
|
||||
"""(참조, 다시씀) — 같은 본뜬(키 · 원본)이 있으면 그 키에 덮음 · 없으면 새 키 · 엑셀 수량 = 저장(742-1)."""
|
||||
hands = {
|
||||
"줄": {str(i): x["수량"]["손"] for i, x in enumerate(doc["담은로직"]) if x.get("수량")},
|
||||
"자재": {m["코드"]: m["수량"]["손"] for m in doc.get("자재") or [] if m.get("수량")},
|
||||
}
|
||||
doc = {**refs.no_qty(doc), "본뜬": {**doc["본뜬"], "실무수량": hands}}
|
||||
doc = {**doc, "본뜬": {**doc["본뜬"], "실무수량": hands}}
|
||||
at = refs.here()
|
||||
want = doc["본뜬"]
|
||||
have = next(
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
참조 → `{이름, 단위, 기준(1 단위), 까닭, 안?}` — 단가승격(옛 SP) · 사전조합(SC) · 단가산출(SK ·
|
||||
`M02_Sangeun_Calc.unit`). 이름은 `M02_Labor_Ref.Expand` 로 그대로 부름.
|
||||
사전조합 안 줄 수량(723-1 · 내역 17-1) = 쓰는 쪽 `given` 의 알약 「참조|층|주인|키#차례」(자재 =
|
||||
「…#자재|코드」 · `inner_pills`) · 문서 손은 굳힌 사본(`hands` · 프로젝트 · 불러오기 대조)과 비율 줄(율)만 ·
|
||||
없으면 「수량 없음」(1 아님 · 그 사전조합 기준 없음).
|
||||
사전조합 안 줄 수량(742-1) 차례 — 층 문서 = 알약 값(쓰는 쪽 `given` 「참조|층|주인|키#차례」 · 자재 =
|
||||
「…#자재|코드」 · `inner_pills`) → 저장 손 · 굳힌 사본(`hands` · 프로젝트) = 굳힌 손(옛 사본 · 707-7) →
|
||||
알약 값 → 저장(`refs.SAVED`) · 비율 줄 = 율 · 모두 없으면 「수량 없음」(1 아님 · 그 사전조합 기준 없음).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -41,13 +41,17 @@ class Expand:
|
||||
) # 층 원본 손 = 옛 값(안 씀)
|
||||
|
||||
def _qty(self, one: dict, key: str) -> Decimal | None:
|
||||
"""안 줄 수량 — 손(굳힌 사본 · 비율 줄) · 알약 값(given) · 없음."""
|
||||
"""안 줄 수량 — 굳힌 손(사본) · 비율 줄 율 → 알약 값(given) → 저장 수량(층 손 · 사본 `저장`)."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
hand = labor._number((one.get("수량") or {}).get("손"))
|
||||
qty = one.get("수량") if isinstance(one.get("수량"), dict) else {}
|
||||
hand = labor._number(qty.get("손"))
|
||||
if hand is not None and (self.hands or refs.is_rate(one)):
|
||||
return hand
|
||||
return labor._number(self.given.get(key))
|
||||
got = labor._number(self.given.get(key))
|
||||
if got is not None:
|
||||
return got
|
||||
return labor._number(qty.get(refs.SAVED)) if self.hands else hand
|
||||
|
||||
def __call__(self, ref: Ref, trail: tuple[str, ...] = ()) -> dict[str, Any]:
|
||||
rid = refs.ref_id(ref)
|
||||
@@ -152,7 +156,7 @@ class Expand:
|
||||
if sub.get("기준") is None:
|
||||
why = why or f"「{sub.get('이름')}」 {sub.get('까닭')}"
|
||||
continue
|
||||
if qty is None: # 723-1 — 이은 값 없음 = 금액 빠짐(1 로 안 셈)
|
||||
if qty is None: # 742-1 — 저장 · 이은 값 없음 = 금액 빠짐(1 로 안 셈)
|
||||
inner[-1]["까닭"] = NO_QTY
|
||||
why = why or f"「{sub.get('이름')}」 {NO_QTY}"
|
||||
continue
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
precombo `{층}/precombo/SC-######.json` 사전조합
|
||||
`{종류:"사전조합", 키, 이름, 단위, 본뜬, 담은로직:[{참조, 수량:{손}, 메모}
|
||||
| {로직, 입력값, 메모, 수량:{손}, 고름?, 자재표시?}], 자재:[일위대가 자재 꼴]}` —
|
||||
승격 · 사전조합 · 단가산출 로직(51-11) · 안 줄 · 자재 수량 = 저장 안 함(늘 null ·
|
||||
723-1 · 내역 17-1 · `no_qty` · 비율 줄 율만 남김) · 쓰는 쪽 알약으로 이음(`Expand` given) ·
|
||||
값 = Σ 안 줄 1 단위 값 × 이은 수량(로직 줄 = 기준 ÷ 배) + Σ 자재 기준단가 × 이은 수량
|
||||
승격 · 사전조합 · 단가산출 로직(51-11) · 안 줄 · 자재 수량 = 저장(742-1 · 사용자 입력 ·
|
||||
null = 빈 칸 · 금액 빠짐) · 쓰는 쪽 알약이 이어지면 그 값이 앞섬(`Expand` given) ·
|
||||
값 = Σ 안 줄 1 단위 값 × 수량(로직 줄 = 기준 ÷ 배) + Σ 자재 기준단가 × 수량
|
||||
sangeun `{층}/sangeun/SK-######.json` 단가산출(715-2) — 꼴 `M02_Sangeun_Doc` · 셈
|
||||
`M02_Sangeun_Calc` · 참조 자리 = `바탕.참조` · `더한줄[].참조`(`refs_in`)
|
||||
참조 = `{층, 주인, 키}`(마스터 주인 null · 회사 `C` · 개인 `C/U`) — 종류는 키 머리(SP · SC · SK) ·
|
||||
@@ -170,7 +170,7 @@ def clean_doc(kind: str, doc: Any) -> dict[str, Any]:
|
||||
out["자재"] = _combo_mats(doc, out["담은로직"])
|
||||
out.update({k: str(doc.get(k) or "").strip() for k in ("구분", "상세구분")})
|
||||
out["본뜬"] = doc["본뜬"] if isinstance(doc.get("본뜬"), dict) else None
|
||||
return no_qty(out)
|
||||
return out
|
||||
|
||||
|
||||
def is_rate(one: Any) -> bool:
|
||||
@@ -178,11 +178,17 @@ def is_rate(one: Any) -> bool:
|
||||
return isinstance(one, dict) and bool((one.get("고정") or {}).get("비율"))
|
||||
|
||||
|
||||
def no_qty(doc: dict[str, Any]) -> dict[str, Any]:
|
||||
"""사전조합 안 줄 · 자재 수량 걷음(723-1 · 내역 17-1 = M02 에 저장 안 함) · 비율 줄 율만 남김."""
|
||||
lines = [one if is_rate(one) else {**one, "수량": None} for one in doc.get("담은로직") or []]
|
||||
mats = [{**one, "수량": None} for one in doc.get("자재") or []]
|
||||
return {**doc, "담은로직": lines, "자재": mats}
|
||||
SAVED = "저장" # 굳힌 사본의 저장 수량 칸(742-1) — 굳힌 손(옛 사본 · 707-7)과 가름
|
||||
|
||||
|
||||
def saved_qty(one: dict[str, Any]) -> dict[str, Any]:
|
||||
"""굳힐 때 줄 · 자재 저장 수량 `{손}` → `{저장}`(742-1 · 공사 차례 = 굳힌 손 → 알약 → 저장) ·
|
||||
비율 줄 율 · 빈 수량 그대로."""
|
||||
qty = one.get("수량")
|
||||
hand = qty.get("손") if isinstance(qty, dict) else None
|
||||
if is_rate(one) or hand is None:
|
||||
return one
|
||||
return {**one, "수량": {SAVED: hand}}
|
||||
|
||||
|
||||
_OLD_CLASS = re.compile(r"품셈") # 134-5 — 옛 품셈 분류(건설품셈 공통 · 산림품셈 · 07장 …)
|
||||
@@ -497,7 +503,8 @@ def with_pills(doc: dict[str, Any]) -> dict[str, Any]:
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
|
||||
lines = [one for one in doc.get("담은로직") or [] if isinstance(one, dict)]
|
||||
doc = no_qty({**doc, "담은로직": lines}) # 723-1 — 옛 손도 굳히지 않음(쓰는 쪽 알약으로)
|
||||
mats = [saved_qty(one) for one in doc.get("자재") or [] if isinstance(one, dict)]
|
||||
doc = {**doc, "담은로직": [saved_qty(one) for one in lines], "자재": mats}
|
||||
return {**doc, "알약": combo_store.combo_pills({"담은로직": lines})["물량"]}
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
날짜(수정일) · 고정 줄 출처
|
||||
수 = Decimal 정규화(0.20 = 0.2 = 「0.2」) · 글 = 겹 빈칸 정리 · 빈 값 칸 뺌 · 단위 = 소문자 + m3 → ㎥
|
||||
차례 무시 = 더한줄 · 담은로직 · 자재 · 뺀줄 · 열린입력 정렬 · 더한줄 요소 줄 이름 뺌(요소 키가 정체)
|
||||
SC 줄 · 자재 수량 = null(723-1 `refs.no_qty` · 비율 줄 율만) · SK 더한줄 수량은 넣음
|
||||
SC 줄 · 자재 수량(742-1 저장 수량) · SK 더한줄 수량 넣음
|
||||
참조 = 가리킨 문서의 지문(깊이 `refs.DEPTH` · 층 넘어 사본도 같음)
|
||||
빈 문서(SK 바탕 · 더한줄 없음 · SC 줄 · 자재 없음) = 지문 None(검사 안 함).
|
||||
저장 칸 없음 — 그때그때 셈(`Same` 한 벌 = 한 요청 안 메모).
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
* M02 일위대가(서고 · 사전조합) 상세 = 일위대가표만(PLAN 723-2 · 내역 16 · 17 · 요구서_내역16 2장 3~5).
|
||||
* · 차례 = 정보(이름 · 구분 · 단위 · 본뜬) → 일위대가표(엑셀 꼴 격자 · `Labor_Grid.laborBlock`) · 오른쪽 원문 패널
|
||||
* · 옵션 값 = 규격 칸 · 「빈 값 n」 = 비고 칸 붉게(입력 빈 로직 줄)
|
||||
* · 수량 칸 = 확인용 임시 입력(723-1 · 휘발 · 문서 · 저장 · dirty 안 건드림) → 그 줄 금액 · 합계 미리 보기
|
||||
* (짝 셈 `Labor_Calc.settle` · 서버 안 부름) · 문서 안 줄 수량은 늘 null(쓰는 쪽 알약으로 이음)
|
||||
* · 수량 칸 = 일위대가 1 단위에 드는 양(742-1 · 요구 584-1 · 문서에 저장 · 새 줄 1 · 빈 칸 = 「수량 입력」 ·
|
||||
* 금액 빠짐) → 그 줄 금액 · 합계 바로(짝 셈 `Labor_Calc.settle`) · 구조물 조합 · 공사 알약이 이어지면 그 값이 앞섬
|
||||
* · 합계 열 = 「기준단위 단가」(줄 1 단위) · 「수량 반영 금액」(× 수량)
|
||||
* · 머리 단추 = [항목 추가](736-1 · 자재는 찾기 창에서 고름 → 자재 목록) · 줄 우클릭 · 줄 끝 ⋯ = 위로 · 아래로 · 별칭 · 삭제
|
||||
* · 줄 누름 = 줄 모달(`Labor_RowModal` — 옵션 · 메모 · 켜쌓기율 · 로직 자재 바꾸기 · 세 칸 · 단가산출) ·
|
||||
* 사전조합 참조 줄 = 서고 원본 모달(`Labor_RefEdit`) · 사용자 자재 줄 = 자재 모달(세 칸)
|
||||
* · 금액 = 서버 calc(최신 응답만 · 저장 안 함) · [저장]은 UI_Main(서버가 수량 걷고 다시 셈)
|
||||
* · 금액 = 서버 calc(최신 응답만 · 저장 안 함) · [저장]은 UI_Main(서버가 다시 셈)
|
||||
* · 안 이은 고정 줄(729-3) = 격자 위 띠 · [연결](`Labor_Unlinked` — 이음만 · 저장은 [저장])
|
||||
* 구조물 조합 일위대가(카드 · 열 상세)는 그대로 `Labor_Detail`(면적 잇기 · 수량산출).
|
||||
* ========================================================================== */
|
||||
@@ -49,10 +50,13 @@ import "@ui/ui_template_general_layout.css";
|
||||
import "./M02_MasterTemplete_Labor.css";
|
||||
|
||||
const TEXT = {
|
||||
TempHint: [
|
||||
"수량 칸 = 확인용 임시 입력(저장 안 함) — 실제 수량은 구조물 조합 · 프로젝트에서 이음",
|
||||
"Quantity = temporary check only (not saved) — real quantities link in structures · projects",
|
||||
QtyHint: [
|
||||
"수량 = 이 일위대가 1 단위에 드는 양(저장) — 구조물 조합 · 공사에서 알약 · 계산 값이 이어지면 그 값이 앞섬",
|
||||
"Quantity = amount per 1 unit of this item (saved) — linked pills · project values take precedence",
|
||||
],
|
||||
NeedQty: ["수량 입력", "Enter quantity"],
|
||||
BasePrice: ["기준단위 단가", "Unit price"],
|
||||
QtyAmount: ["수량 반영 금액", "Amount × qty"],
|
||||
Alias: ["별칭", "Alias"],
|
||||
AliasAsk: ["별칭(비우면 지움)", "Alias (empty = clear)"],
|
||||
RemoveAsk: ["「{value}」 줄을 지울까?", "Delete line “{value}”?"],
|
||||
@@ -61,9 +65,10 @@ const TEXT = {
|
||||
} as const satisfies Record<string, readonly [string, string]>;
|
||||
const t = (k: keyof typeof TEXT): string => TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0];
|
||||
|
||||
/** 비율 고정 줄(705-3) — 수량 = 율(문서에 남음 · 임시 입력 아님) */
|
||||
/** 비율 고정 줄(705-3) — 수량 = 율 */
|
||||
const isRate = (row: LaborRow): boolean => !!row.고정?.비율;
|
||||
const matKey = (code: string): string => `자재|${code}`;
|
||||
/** 새 줄 수량(742-1) — 1 · 사용자가 고침 */
|
||||
const ONE = { 손: 1 } as const;
|
||||
|
||||
export function mountLaborTable(
|
||||
host: HTMLElement,
|
||||
@@ -71,14 +76,9 @@ export function mountLaborTable(
|
||||
opt: LaborDetailOptions,
|
||||
): LaborDetailHandle {
|
||||
const cur = normalizeLabor(doc, opt.column);
|
||||
// 723-1 — 옛 문서 손 수량은 안 씀(서버도 저장 때 걷음) · 비율 줄 율만
|
||||
for (const r of cur.담은로직) if (!isRate(r)) r.수량 = null;
|
||||
for (const m of cur.자재) m.수량 = null;
|
||||
const saved = JSON.stringify(cur);
|
||||
const edit = !opt.readOnly;
|
||||
const logics = new Map<string, ItemLogic>();
|
||||
/** 임시 수량(휘발) — 줄 id · 「자재|코드」 → 수 */
|
||||
const temp = new Map<string, number>();
|
||||
let calc: LaborCalc | null = null;
|
||||
let last: LaborCalc | null = null;
|
||||
let fail: string | null = null;
|
||||
@@ -95,19 +95,6 @@ export function mountLaborTable(
|
||||
return () => watchers.delete(fn);
|
||||
};
|
||||
|
||||
/** 셈 몸 = 문서 + 임시 수량(저장 안 함) */
|
||||
const withTemp = (): LaborDoc => ({
|
||||
...cur,
|
||||
담은로직: cur.담은로직.map((r) => {
|
||||
const v = temp.get(lineId(r) ?? "");
|
||||
return v === undefined ? r : { ...r, 수량: { 손: v } };
|
||||
}),
|
||||
자재: cur.자재.map((m) => {
|
||||
const v = temp.get(matKey(m.코드));
|
||||
return v === undefined ? m : { ...m, 수량: { 손: v } };
|
||||
}),
|
||||
});
|
||||
|
||||
const gridHost = el("div", { attrs: { "data-labor-grid": "", "data-labor-table": "" } });
|
||||
const source = createSourceView({
|
||||
path: "/m02/logic-lib/source",
|
||||
@@ -128,28 +115,30 @@ export function mountLaborTable(
|
||||
if (sourceHost.firstChild !== source.root) sourceHost.replaceChildren(source.root);
|
||||
}
|
||||
|
||||
/** 일위대가표 — 줄 누름 · 우클릭 · 수량 칸(임시) · 빈 값 붉게 */
|
||||
/** 일위대가표 — 줄 누름 · 우클릭 · 수량 칸(저장) · 빈 값 · 빈 수량 붉게 */
|
||||
const draw = (): void => {
|
||||
const body = withTemp();
|
||||
const block = laborBlock(body, calc ?? last, logics);
|
||||
const block = laborBlock(cur, calc ?? last, logics);
|
||||
const empties = new Set<number>();
|
||||
block.lines.forEach((ln, n) => {
|
||||
const row = body.담은로직[n];
|
||||
if (!row) return void (ln.note = ln.note === tc("Grid_NoQty") ? "" : ln.note); // 자재 줄
|
||||
const empty = row.로직 ? valueLine(logics.get(row.로직)?.입력 ?? [], row.입력값).empty : 0;
|
||||
if (empty) empties.add(n);
|
||||
const row = cur.담은로직[n];
|
||||
if (ln.note === tc("Grid_NoQty")) ln.note = t("NeedQty"); // 742-1 빈 수량 = 사용자가 넣음
|
||||
const empty = row?.로직 ? valueLine(logics.get(row.로직)?.입력 ?? [], row.입력값).empty : 0;
|
||||
if (empty) ln.note = t("Empty").replace("{n}", String(empty));
|
||||
else if (ln.note === tc("Grid_NoQty")) ln.note = ""; // 수량 없음 = 서고에선 보통(723-1)
|
||||
if (empty || ln.note === t("NeedQty")) empties.add(n);
|
||||
});
|
||||
const have = calc ?? last;
|
||||
// 빈 줄 = 단가 모름(까닭) · 빈 값만 — 임시 수량 없는 줄은 안 셈
|
||||
block.blank = have ? block.lines.filter((l) => l.prices.sum == null).length + empties.size : 0;
|
||||
// 빈 줄 = 금액 모름(단가 까닭 · 빈 수량) · 빈 값
|
||||
block.blank = have
|
||||
? block.lines.filter((l, n) => l.amounts.sum == null || empties.has(n)).length
|
||||
: 0;
|
||||
if (fail && block.lines[0]) block.lines[0].note = fail;
|
||||
const g = hopyoGrid([block]);
|
||||
const at = (i: number): number | null => g.at.get(i)?.[1] ?? null;
|
||||
const label: Record<string, string> = { sum_price: t("BasePrice"), sum: t("QtyAmount") };
|
||||
const grid = createHopyoGrid(
|
||||
[block],
|
||||
{
|
||||
columns: g.columns.map((c) => (label[c.key] ? { ...c, label: label[c.key] } : c)),
|
||||
onRowClick: (i) => {
|
||||
const n = at(i);
|
||||
if (n !== null) openAt(n);
|
||||
@@ -163,13 +152,13 @@ export function mountLaborTable(
|
||||
: undefined,
|
||||
editable: (i, key) => {
|
||||
const n = at(i);
|
||||
if (key !== "qty" || n === null) return false;
|
||||
if (!edit || key !== "qty" || n === null) return false;
|
||||
const row = cur.담은로직[n];
|
||||
return !row || !isRate(row); // 임시 입력이라 읽기만 층도 됨
|
||||
return !row || !isRate(row);
|
||||
},
|
||||
onEdit: (i, _key, text) => {
|
||||
const n = at(i);
|
||||
if (n !== null) setTemp(n, text);
|
||||
if (n !== null) setQty(n, text);
|
||||
},
|
||||
cellClass: (i, key) => {
|
||||
const n = at(i);
|
||||
@@ -202,10 +191,9 @@ export function mountLaborTable(
|
||||
fail = null;
|
||||
draw();
|
||||
try {
|
||||
const body = withTemp();
|
||||
const got = opt.calc
|
||||
? await opt.calc(body)
|
||||
: await calcLabor(opt.layer, opt.column, body, undefined, opt.owner);
|
||||
? await opt.calc(cur)
|
||||
: await calcLabor(opt.layer, opt.column, cur, undefined, opt.owner);
|
||||
if (!alive || mine !== seq) return;
|
||||
calc = last = got;
|
||||
} catch (error) {
|
||||
@@ -215,34 +203,27 @@ export function mountLaborTable(
|
||||
draw();
|
||||
}, 150);
|
||||
};
|
||||
/** `qtyOnly` = 수량만(임시) — 짝 셈으로 바로 */
|
||||
/** `qtyOnly` = 수량만 — 짝 셈으로 바로 */
|
||||
const changed = (qtyOnly = false): void => {
|
||||
opt.onChanged(dirty());
|
||||
if (qtyOnly && calc) {
|
||||
calc = settle(calc, withTemp(), {});
|
||||
calc = settle(calc, cur, {});
|
||||
draw();
|
||||
} else recalc();
|
||||
};
|
||||
|
||||
/** 임시 수량(휘발) — 빈 글 = 지움 · 수 아님 = 알림 */
|
||||
function setTemp(n: number, text: string): void {
|
||||
const row = cur.담은로직[n];
|
||||
const key = row ? (lineId(row) ?? "") : matKey(cur.자재[n - cur.담은로직.length]?.코드 ?? "");
|
||||
/** 수량 칸(742-1 · 저장) — 빈 글 = 빈 수량(「수량 입력」) · 수 아님 · 음수 = 알림 */
|
||||
function setQty(n: number, text: string): void {
|
||||
const row = cur.담은로직[n] ?? cur.자재[n - cur.담은로직.length];
|
||||
const raw = text.replace(/,/g, "").trim();
|
||||
if (!raw) temp.delete(key);
|
||||
else if (Number.isFinite(Number(raw))) temp.set(key, Number(raw));
|
||||
else return showToast(t("NotNumber"), "error");
|
||||
calc = calc && settle(calc, withTemp(), {});
|
||||
draw();
|
||||
const v = Number(raw);
|
||||
if (!row) return;
|
||||
if (raw && !(Number.isFinite(v) && v >= 0)) return showToast(t("NotNumber"), "error");
|
||||
row.수량 = raw ? { 손: v } : null;
|
||||
changed(true);
|
||||
}
|
||||
/** 붙여넣기(705-2) 뒤 — 엑셀 수량은 임시 칸으로(문서엔 안 남김) */
|
||||
/** 붙여넣기(705-2) 뒤 — 엑셀 수량 = 저장 수량 */
|
||||
function afterPaste(): void {
|
||||
for (const r of cur.담은로직) {
|
||||
const v = r.수량?.손;
|
||||
if (isRate(r) || v == null) continue;
|
||||
temp.set(lineId(r) ?? "", v);
|
||||
r.수량 = null;
|
||||
}
|
||||
void loadLogics().then(() => changed());
|
||||
}
|
||||
|
||||
@@ -340,13 +321,8 @@ export function mountLaborTable(
|
||||
}
|
||||
function remove(n: number): void {
|
||||
const rows = cur.담은로직.length;
|
||||
if (n < rows) {
|
||||
temp.delete(lineId(cur.담은로직[n]) ?? "");
|
||||
cur.담은로직.splice(n, 1);
|
||||
} else {
|
||||
temp.delete(matKey(cur.자재[n - rows]?.코드 ?? ""));
|
||||
cur.자재.splice(n - rows, 1);
|
||||
}
|
||||
if (n < rows) cur.담은로직.splice(n, 1);
|
||||
else cur.자재.splice(n - rows, 1);
|
||||
changed();
|
||||
}
|
||||
|
||||
@@ -363,14 +339,21 @@ export function mountLaborTable(
|
||||
const id = lineId({ 참조: ref });
|
||||
if (cur.담은로직.some((r) => lineId(r) === id))
|
||||
return showToast(tc("Labor_AlreadyRef").replace("{value}", name), "error");
|
||||
cur.담은로직.push({ 로직: "", 참조: ref, 별칭, 메모: null, 입력값: {}, 수량: null });
|
||||
cur.담은로직.push({ 로직: "", 참조: ref, 별칭, 메모: null, 입력값: {}, 수량: { ...ONE } });
|
||||
changed();
|
||||
},
|
||||
fixed: (fx: LaborFixed, name: string) => {
|
||||
const id = lineId({ 고정: fx });
|
||||
if (cur.담은로직.some((r) => lineId(r) === id))
|
||||
return showToast(tc("Labor_AlreadyRef").replace("{value}", name), "error");
|
||||
cur.담은로직.push({ 로직: "", 고정: fx, 별칭: null, 메모: null, 입력값: {}, 수량: null });
|
||||
cur.담은로직.push({
|
||||
로직: "",
|
||||
고정: fx,
|
||||
별칭: null,
|
||||
메모: null,
|
||||
입력값: {},
|
||||
수량: { ...ONE },
|
||||
});
|
||||
changed();
|
||||
},
|
||||
/** 736-1 찾아 넣기 창에서 재료를 고름 — 「자재 목록」 줄 · 로직 안 자재 · 이미 있는 자재는 막음 */
|
||||
@@ -384,7 +367,7 @@ export function mountLaborTable(
|
||||
? "Labor_InLogicMat"
|
||||
: cur.자재.some((x) => x.코드 === m.코드) && "Labor_AlreadyMat";
|
||||
if (why) return showToast(tc(why).replace("{value}", `${m.코드} ${m.이름 ?? ""}`), "error");
|
||||
cur.자재.push({ 코드: m.코드, 이름: m.이름, 규격: m.규격, 단위: m.단위, 수량: null });
|
||||
cur.자재.push({ 코드: m.코드, 이름: m.이름, 규격: m.규격, 단위: m.단위, 수량: { ...ONE } });
|
||||
changed();
|
||||
},
|
||||
};
|
||||
@@ -395,7 +378,7 @@ export function mountLaborTable(
|
||||
openLogicPicker((x) => {
|
||||
if (cur.담은로직.some((r) => r.로직 === x.키))
|
||||
return showToast(tc("Labor_Already").replace("{value}", `${x.키} ${x.이름}`), "error");
|
||||
cur.담은로직.push({ 로직: x.키, 별칭: null, 메모: null, 입력값: {}, 수량: null });
|
||||
cur.담은로직.push({ 로직: x.키, 별칭: null, 메모: null, 입력값: {}, 수량: { ...ONE } });
|
||||
void loadLogics().then(() => changed());
|
||||
}, withHopyo(library)),
|
||||
});
|
||||
@@ -466,7 +449,7 @@ export function mountLaborTable(
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
createText(t("TempHint"), "hint"),
|
||||
createText(t("QtyHint"), "hint"),
|
||||
gridHost,
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -57,7 +57,10 @@ def test_저장_왕복_고름_자재표시_자재_남음(world): # noqa: F811
|
||||
_, got = _save(world["system"], {"담은로직": [line], "자재": mats})
|
||||
one = got["담은로직"][0]
|
||||
assert one["고름"] == {"4": NET_ITEM} and one["자재표시"] == {"3": {"자재구분": "관급"}}
|
||||
assert [(m["코드"], m["수량"]) for m in got["자재"]] == [(MAT, None), (WIRE, None)] # 723-1
|
||||
assert [(m["코드"], m["수량"]) for m in got["자재"]] == [
|
||||
(MAT, {"손": 2}),
|
||||
(WIRE, None),
|
||||
] # 742-1
|
||||
assert got["자재"][0]["운반비"] == "미포함" and got["자재"][0]["이름"] != "틀린 이름"
|
||||
# 자재 없음 = 빈 목록(옛 문서 꼴 그대로 읽힘)
|
||||
_, empty = _save(world["system"], {"담은로직": [{"로직": NET}]})
|
||||
@@ -118,16 +121,16 @@ def test_사전조합_값에_자재_금액_빈칸은_안_셈_안_줄에_자재(w
|
||||
)
|
||||
bare = labor.calc(doc, {})["줄"][0]
|
||||
one = labor.calc(doc, _given(key))["줄"][0]
|
||||
assert bare["기준"] is None and "수량 없음" in bare["까닭"] # 723-1 — 안 이으면 값 없음
|
||||
assert bare["기준"]["계"] == pytest.approx(float(want)) # 742-1 — 안 이으면 저장 수량
|
||||
assert one["기준"]["계"] == pytest.approx(float(want))
|
||||
assert one["반영"]["계"] == pytest.approx(float(want * 3))
|
||||
mats = [x for x in one["안"] if x.get("자재")]
|
||||
assert [(x["자재"], x["수량"]) for x in mats] == [(MAT, 2)] # WIRE = 기본 별도계상(안 셈)
|
||||
assert mats[0]["기준"]["재료비"] == pytest.approx(float(_price(MAT)))
|
||||
# 목록 기준금액(51-4) — 이은 값 없음 = 빈 값(723-1)
|
||||
# 목록 기준금액(51-4) — 저장 수량으로(742-1)
|
||||
rows = [{"종류": "precombo", "이름": key}]
|
||||
refs.summaries(world["system"], rows)
|
||||
assert rows[0]["기준금액"] is None
|
||||
assert rows[0]["기준금액"] == pytest.approx(float(want))
|
||||
|
||||
|
||||
def test_B08_참조_줄_재료에_사전조합_자재_손_곱(world): # noqa: F811
|
||||
@@ -143,10 +146,10 @@ def test_B08_참조_줄_재료에_사전조합_자재_손_곱(world): # noqa: F
|
||||
got = _ref_mats(m01.loaded()[1], lambda r: docs.get(r["키"]), {**SYS, "키": "SC-겹"})
|
||||
mats = [(m["품목"]["키"], q) for m, q in got if m.get("자재")]
|
||||
assert mats == [(MAT, pytest.approx(6.0))] # 2 × 겹 손 3 · WIRE 빈칸 없음
|
||||
# 723-1 — 수량 없는 새 문서 = 면적 알약 값(given)으로 · 안 이으면 안 셈
|
||||
# 742-1 — 층 문서 = 저장 수량 · 면적 알약 값(given)도 같음
|
||||
live = {"SC-겹": outer, key: refs.read_ref({**SYS, "키": key})}
|
||||
bare = _ref_mats(m01.loaded()[1], lambda r: live.get(r["키"]), {**SYS, "키": "SC-겹"})
|
||||
assert [m for m, _ in bare if m.get("자재")] == []
|
||||
assert [(m["품목"]["키"], q) for m, q in bare if m.get("자재")] == [(MAT, pytest.approx(6.0))]
|
||||
got = _ref_mats(
|
||||
m01.loaded()[1], lambda r: live.get(r["키"]), {**SYS, "키": "SC-겹"}, given=_given(key)
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ if str(ROOT) not in sys.path:
|
||||
from B09_Estimation import B09_Estimation_Bill_Library as library # noqa: E402
|
||||
from B09_Estimation import B09_Estimation_Router as router # noqa: E402
|
||||
from B09_Estimation import B09_Estimation_Store as store_b09 # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import ASK, daega_inputs # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Linked import linked_resource_keys # noqa: E402
|
||||
from config import config_system # noqa: E402
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs # noqa: E402
|
||||
@@ -86,9 +86,9 @@ def test_사전조합_고름_사본_들여옴_새_호표_다시_고르면_재사
|
||||
got, block = _block(root, folder)
|
||||
assert (block["kind"], block["name"], block["unit"]) == ("사전조합", "서고 조합", "㎡")
|
||||
kinds = [(ln["kind"], ln.get("qty")) for ln in block["lines"]]
|
||||
# 723-1 · 723-4 = M02 SC 안 줄 수량 저장 안 함 → 내역에서 바로 쓴 SC = 수량 없음(이 공사에서 입력)
|
||||
assert kinds == [("sangeun", None), ("sangeun", None), ("material", None)]
|
||||
assert all(ln["ask"] == ASK for ln in block["lines"])
|
||||
# 742-1 = M02 SC 안 줄 저장 수량 → 내역에서 바로 쓴 SC = 굳힌 저장 수량(이 공사 입력이 앞섬)
|
||||
assert kinds == [("sangeun", 2), ("sangeun", 1), ("material", 3)]
|
||||
assert not any(ln.get("ask") for ln in block["lines"])
|
||||
assert block["cover"] == f"C:{block['origin']['참조']}" # 공사 안 한 덮개 그대로
|
||||
assert got["owners"] == {} # 구조물 사본 없어도 호표
|
||||
# 단가 묶음 = 담은 로직 · 사전조합 자재 · 고른 자재(사본 셈이 마스터를 안 읽음)
|
||||
|
||||
@@ -44,11 +44,12 @@ def test_서고_사본_산근_목록에_듦_대가_단가_채움(world):
|
||||
by_key = {r["key"]: r for r in sg["sg_list"]}
|
||||
labor = compute_labor(daega, {**base, "gaps": []}, by_key)
|
||||
row = next(r for r in labor["daega"] if r["key"] == block["id"])
|
||||
assert not row["sum"] and row["blank"] == 3 # 723-4 서고 SC 안 줄 수량 없음 = 이 공사에서 입력
|
||||
saved = row["sum"] # 742-1 서고 SC 안 줄 저장 수량(2 · 1 · 3) = 굳힌 사본 그대로
|
||||
assert saved and not row.get("blank")
|
||||
cover = {block["cover"]: {ln["cid"]: {"qty": q} for ln, q in zip(block["lines"], (2, 1, 3))}}
|
||||
labor = compute_labor(daega, {**base, "gaps": []}, by_key, cover)
|
||||
row = next(r for r in labor["daega"] if r["key"] == block["id"])
|
||||
assert row["sum"] and not row.get("blank")
|
||||
assert row["sum"] == saved and not row.get("blank") # 이 공사 입력(같은 수) = 같은 값
|
||||
|
||||
|
||||
def test_화면_연결_뒤_셈_저장_단추_옮김_물음_TS():
|
||||
|
||||
@@ -186,8 +186,8 @@ def test_자료집_같은_이름_물음(world):
|
||||
saved = json.loads(
|
||||
(world["folder"] / "precombo" / f"{first['이름']}.json").read_text(encoding="utf-8")
|
||||
)
|
||||
# 723-1 = M02 SC 안 줄 수량 저장 안 함(덮어쓴 손 9 도 걷힘 · 쓰는 곳 알약 · 이 공사 입력으로 이음)
|
||||
assert saved["이름"] == "내 돌붙임" and saved["담은로직"][1]["수량"] is None
|
||||
# 742-1 = M02 SC 안 줄 수량 저장(덮어쓴 손 9 · 쓰는 곳 알약 · 이 공사 입력이 이어지면 그 값이 앞섬)
|
||||
assert saved["이름"] == "내 돌붙임" and saved["담은로직"][1]["수량"] == {"손": 9}
|
||||
assert saved["담은로직"][0]["입력값"] == b145.STONE
|
||||
assert len(list((world["folder"] / "precombo").glob("SC-*.json"))) == 2
|
||||
|
||||
@@ -221,5 +221,7 @@ def test_자료집_길_같은_이름_409_덮어쓰기(world, monkeypatch):
|
||||
over = client.post(url, json={**body, "overwrite": first.json()["키"]})
|
||||
assert over.status_code == 200 and over.json()["키"] == first.json()["키"]
|
||||
saved = world["folder"] / "precombo" / f"{first.json()['키']}.json"
|
||||
assert json.loads(saved.read_text(encoding="utf-8"))["담은로직"][0]["수량"] is None # 723-1
|
||||
assert json.loads(saved.read_text(encoding="utf-8"))["담은로직"][0]["수량"] == {
|
||||
"손": 4
|
||||
} # 742-1
|
||||
assert client.post(url, json={**body, "cover": "S:없음"}).status_code == 404
|
||||
|
||||
@@ -78,9 +78,9 @@ def test_list_amount_and_labor_line_holds_upper_sk(world): # noqa: F811
|
||||
fed = labor.calc(outer, {f"{refs.ref_id({**MINE, '키': sc})}#0": 3})
|
||||
direct = labor.calc({"담은로직": [{"참조": {**COMPANY, "키": sk}, "수량": {"손": 3}}]}, {})
|
||||
assert saved == {**COMPANY, "키": sk} # 참조 = 회사 SK 그대로
|
||||
# 723-1 · 내역 17-1 — 일위대가 안 줄 수량은 저장 안 함(null) · 쓰는 자리(바깥 문서)에서 들어옴
|
||||
assert inner == [None]
|
||||
assert got["반영합"]["계"] == 0 # 안 줄 수량 없음 = 금액 빠짐
|
||||
# 742-1 — 일위대가 안 줄 수량 = 저장(사용자 입력) · 쓰는 자리 알약이 이어지면 그 값
|
||||
assert inner == [{"손": 3}]
|
||||
assert got["반영합"]["계"] == pytest.approx(one * 3, abs=1) # 저장 수량 3
|
||||
assert direct["반영합"]["계"] == pytest.approx(
|
||||
one * 3, abs=1
|
||||
) # 바깥이 SK 를 손 3 으로 바로 참조
|
||||
|
||||
@@ -93,7 +93,7 @@ def test_B09_서고_사본_값_같음_원본판(world, tmp_path):
|
||||
"precombo",
|
||||
{"이름": "조합", "단위": "㎡", "담은로직": [{"참조": ref["ref"], "수량": {"손": 2}}]},
|
||||
)["이름"]
|
||||
# 723-1 = SC 안 줄 수량(손 2) 저장 안 함 → 쓰는 쪽 알약 값으로 이음(층 · 사본 같은 값)
|
||||
# 742-1 = SC 안 줄 수량(손 2) 저장 → 굳힌 사본 = 저장 2 · 쓰는 쪽 알약 값(2)이 앞섬(층 · 사본 같은 값)
|
||||
pill = f"{refs.ref_id({**MINE, '키': sc})}#0"
|
||||
with store.at(world["personal"]):
|
||||
top = labor.clean(
|
||||
@@ -108,7 +108,9 @@ def test_B09_서고_사본_값_같음_원본판(world, tmp_path):
|
||||
folder = library.library_dir(root) / made["폴더"]
|
||||
read = {p.name: json.loads(p.read_text(encoding="utf-8")) for p in folder.glob("*.json")}
|
||||
bare = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, None)["줄"][0]
|
||||
assert bare["기준"] is None and "수량 없음" in bare["까닭"] # 이은 값 없음 = 1 로 안 셈
|
||||
assert bare["기준"]["계"] == pytest.approx(
|
||||
live["기준"]["계"], rel=1e-9
|
||||
) # 이은 값 없음 = 저장 2
|
||||
got = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, area)["줄"][0]
|
||||
assert not got["까닭"] and not live["까닭"]
|
||||
assert got["기준"]["계"] == pytest.approx(live["기준"]["계"], rel=1e-9)
|
||||
|
||||
@@ -21,7 +21,7 @@ from B09_Estimation import B09_Estimation_Refresh as refresh
|
||||
from B09_Estimation import B09_Estimation_Sangeun as sangeun
|
||||
from B09_Estimation.B09_Estimation_Calc_Base import compute_base
|
||||
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import ASK, daega_inputs
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs
|
||||
from B09_Estimation.B09_Estimation_Linked import linked_resource_keys
|
||||
from M02_MasterTemplete import M02_Item_Copy as item_copy
|
||||
from M02_MasterTemplete import M02_Item_Snapshot as snapshots
|
||||
@@ -122,9 +122,8 @@ def test_SK_단가_덮개_속_일위대가_펼침(world, tmp_path):
|
||||
if not _engine_ready(world):
|
||||
pytest.skip("715-1b 엔진 입력 덧칸(단가 · 값묶음) 뒤 — 지금은 M02 SK 셈도 못 함")
|
||||
key = _mine(world, full=True)
|
||||
# 723-1 속 일위대가 안 줄 수량 없음 → M02 SK = 그 줄만 빠짐(725-2) · B09 = 그 줄만 빈 줄
|
||||
gone = _calc(world["personal"], key)["빠진줄"]
|
||||
assert len(gone) == 1 and "수량 없음" in gone[0]["까닭"]
|
||||
# 742-1 속 일위대가 안 줄 저장 수량(손 1) → M02 SK · B09 모두 빈 줄 없음
|
||||
assert _calc(world["personal"], key)["빠진줄"] == []
|
||||
root, folder = _lib(world, tmp_path, key)
|
||||
daega, done, _ = _b09(root)
|
||||
block = done[daega["libs"][folder]]
|
||||
@@ -133,8 +132,8 @@ def test_SK_단가_덮개_속_일위대가_펼침(world, tmp_path):
|
||||
sc_line = next(ln for ln in lines if ln["kind"] == "daega")
|
||||
sc = done[sc_line["key"]]
|
||||
assert sc["kind"] == "사전조합" and sc_line["qty"] == 2
|
||||
assert sc["blank"] == 1 and sc["lines"][0]["why"] == ASK and block["blank"] >= 1
|
||||
# 이 공사에서 입력(702 덮개 qty 1) = 옛 손 1 과 같은 값 → M02 셈(알약 값 1) 과 같은 금액
|
||||
assert sc["blank"] == 0 and sc["lines"][0]["qty"] == 1 and block["blank"] == 0
|
||||
# 이 공사에서 입력(702 덮개 qty 1) = 저장 1 과 같은 값 → M02 셈(알약 값 1) 과 같은 금액
|
||||
cover = {sc["cover"]: {sc["lines"][0]["cid"]: {"qty": 1}}}
|
||||
block = _b09(root, cover)[1][daega["libs"][folder]]
|
||||
assert block["blank"] == 0
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
"""PLAN 723 — M02 일위대가(사전조합) 수량 = 저장 안 함 · 일위대가표만 · 줄 모달(내역 16 · 17).
|
||||
"""PLAN 723 — M02 일위대가(사전조합) 일위대가표만 · 줄 모달(내역 16 · 17).
|
||||
|
||||
723-1 서버 — 저장 때 안 줄 · 자재 수량 null(비율 줄 율만) · 펼침 = 쓰는 쪽 알약 값 · 안 이으면 「수량 없음」 ·
|
||||
구조물 조합 알약에 안 줄 알약 · 불러오기 엑셀 수량 = `본뜬.실무수량`(셈 안 씀).
|
||||
723-2 · 723-3 화면 — 서고 상세 = `Labor_Table`(수량 칸 휘발 임시 입력 · 우클릭) · 줄 모달 = 717-2b 상세 줄 모드.
|
||||
723-1 서버 — 구조물 조합 알약에 안 줄 알약
|
||||
(안 줄 수량 저장은 742-1 이 되살림 · `test_742_1_sc_qty_saved`).
|
||||
723-2 · 723-3 화면 — 서고 상세 = `Labor_Table`(우클릭) · 줄 모달 = 717-2b 상세 줄 모드.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
@@ -25,41 +22,6 @@ def _src(name: str) -> str:
|
||||
return open(os.path.join(SRC, name), encoding="utf-8").read()
|
||||
|
||||
|
||||
def test_저장은_안_줄_수량을_걷고_비율_줄_율만_남김(world): # noqa: F811
|
||||
rate = {"고정": {**RATE, "비율": {"기준": "노무비", "비목": "경비"}}, "수량": {"손": 3}}
|
||||
lines = [{"로직": STONE, "수량": {"손": 2.5}}, rate]
|
||||
with store.at(world["system"]):
|
||||
key = refs.create("precombo", {"이름": "수량 없음", "단위": "m", "담은로직": lines})["이름"]
|
||||
doc = store.read("precombo", key)["문서"]
|
||||
assert doc["담은로직"][0]["수량"] is None
|
||||
assert doc["담은로직"][1]["수량"] == {"손": 3} # 비율 = 율(기준단위 당 수량 아님)
|
||||
frozen = refs.with_pills(doc) # 프로젝트 굳힘도 수량 없이
|
||||
assert frozen["담은로직"][0]["수량"] is None and frozen["알약"]
|
||||
|
||||
|
||||
def test_펼침은_이은_알약만_쓰고_층_원본_옛_손은_안_씀(world): # noqa: F811
|
||||
with store.at(world["system"]):
|
||||
key = refs.create("precombo", {"이름": "옛", "단위": "m", "담은로직": [{"로직": STONE}]})[
|
||||
"이름"
|
||||
]
|
||||
ref = {**SYS, "키": key}
|
||||
path = world["system"] / "precombo" / f"{key}.json"
|
||||
import json
|
||||
|
||||
old = json.loads(path.read_bytes())
|
||||
old["담은로직"][0]["수량"] = {"손": 9} # 옛 문서(723-1 앞) — 파일은 그대로 둠
|
||||
path.write_text(json.dumps(old, ensure_ascii=False), encoding="utf-8")
|
||||
whole = m01.loaded()[1]
|
||||
with store.at(world["system"]):
|
||||
bare = refs.Expand(whole)(ref)
|
||||
got = refs.Expand(whole, given={f"{refs.ref_id(ref)}#0": 2})(ref)
|
||||
frozen = refs.Expand(whole, read=lambda r: old)(ref) # 굳힌 사본 = 손 그대로
|
||||
stone = labor.run_line(whole, STONE, {})["기준"]["계"]
|
||||
assert bare["기준"] is None and "수량 없음" in bare["까닭"]
|
||||
assert got["기준"]["계"] == pytest.approx(stone * 2)
|
||||
assert frozen["기준"]["계"] == pytest.approx(stone * 9)
|
||||
|
||||
|
||||
def test_구조물_조합_알약에_안_줄_알약(world): # noqa: F811
|
||||
with store.at(world["system"]):
|
||||
key = refs.create(
|
||||
@@ -72,17 +34,14 @@ def test_구조물_조합_알약에_안_줄_알약(world): # noqa: F811
|
||||
assert pills[1]["이름"] == "옹벽 · 돌" and pills[1]["단위"] == "㎡"
|
||||
|
||||
|
||||
def test_화면_서고_상세는_일위대가표만_임시_수량_휘발():
|
||||
def test_화면_서고_상세는_일위대가표만():
|
||||
table, detail = (
|
||||
_src("M02_MasterTemplete_Labor_Table.ts"),
|
||||
_src("M02_MasterTemplete_Labor_Detail.ts"),
|
||||
)
|
||||
assert 'if (opt.mode === "precombo") return mountLaborTable(host, doc, opt);' in detail
|
||||
assert "const temp = new Map<string, number>();" in table # 휘발 — 문서 밖
|
||||
assert "calc = calc && settle(calc, withTemp(), {});" in table # 짝 셈 미리 보기
|
||||
assert (
|
||||
'const out: Record<string, unknown> = { ...cur, 종류: "사전조합" };' in table
|
||||
) # temp 안 실음
|
||||
assert "calc = settle(calc, cur, {});" in table # 짝 셈 바로(742-1 수량 = 문서)
|
||||
assert 'const out: Record<string, unknown> = { ...cur, 종류: "사전조합" };' in table
|
||||
for label in ('tc("Up")', 'tc("Down")', 't("Alias")', 'tc("Remove")'):
|
||||
assert label in table # 우클릭 메뉴
|
||||
assert "fixed: (fx: LaborFixed, name: string)" in table # 721-2 고정 줄 이음
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
"""PLAN 742-1 — M02 일위대가(SC) 안 줄 수량 = 사용자 입력 저장(요구 584-1 · 723-1 「null」 걷음).
|
||||
|
||||
- 저장 = 줄 · 자재 `수량 {손}` 그대로(빈 수량 = null · 비율 줄 율) · 다시 열기 같음
|
||||
- 층 셈 차례 = 알약 값(쓰는 쪽 given) → 저장 수량 → 없으면 「수량 없음」
|
||||
- 공사 굳힘(`with_pills`) = 저장 수량 `{저장}` 칸으로 옮김
|
||||
→ 공사 차례 = 굳힌 손(옛 사본 · 707-7) → 알약 → 저장
|
||||
- 단가산출(SK) 안 일위대가 = 그 저장 수량으로 금액(「수량 없음」 없어짐)
|
||||
- 화면 일위대가표 = 수량 칸 저장 · 새 줄 1 · 빈 칸 「수량 입력」 ·
|
||||
합계 열 「기준단위 단가」 · 「수량 반영 금액」
|
||||
"""
|
||||
|
||||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from resources.tester.test_723_4_b09_sc_qty import MAT, RID, SHEETS, _build, _doc
|
||||
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 임시 층 fixture
|
||||
|
||||
SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "M02_MasterTemplete")
|
||||
SYS = {"층": "system", "주인": None}
|
||||
STONE = "GF000219" # 원/㎡
|
||||
RATE = {"이름": "공구손료", "단위": "%", "단가": {"노무비": 0, "재료비": 0, "경비": 0}}
|
||||
|
||||
|
||||
def _stone() -> float:
|
||||
return labor.run_line(m01.loaded()[1], STONE, {})["기준"]["계"]
|
||||
|
||||
|
||||
def _sc(world, lines): # noqa: F811
|
||||
with store.at(world["system"]):
|
||||
return refs.create("precombo", {"이름": "저장", "단위": "m", "담은로직": lines})["이름"]
|
||||
|
||||
|
||||
def test_저장_다시_열기_수량_그대로_빈_수량_null_비율_율(world): # noqa: F811
|
||||
rate = {"고정": {**RATE, "비율": {"기준": "노무비", "비목": "경비"}}, "수량": {"손": 3}}
|
||||
key = _sc(world, [{"로직": STONE, "수량": {"손": 2.5}}, {"로직": "GF000297"}, rate])
|
||||
with store.at(world["system"]):
|
||||
doc = store.read("precombo", key)["문서"]
|
||||
assert [x["수량"] for x in doc["담은로직"]] == [{"손": 2.5}, None, {"손": 3}]
|
||||
frozen = refs.with_pills(doc) # 공사 굳힘 = 저장 칸으로(비율 율 · 빈 수량 그대로)
|
||||
assert [x["수량"] for x in frozen["담은로직"]] == [{"저장": 2.5}, None, {"손": 3}]
|
||||
assert frozen["알약"]
|
||||
|
||||
|
||||
def test_층_차례_알약_먼저_없으면_저장_없으면_수량_없음(world): # noqa: F811
|
||||
key = _sc(world, [{"로직": STONE, "수량": {"손": 2}}])
|
||||
blank = _sc(world, [{"로직": STONE}])
|
||||
ref, whole = {**SYS, "키": key}, m01.loaded()[1]
|
||||
with store.at(world["system"]):
|
||||
saved = refs.Expand(whole)(ref)
|
||||
fed = refs.Expand(whole, given={f"{refs.ref_id(ref)}#0": 5})(ref)
|
||||
none = refs.Expand(whole)({**SYS, "키": blank})
|
||||
assert saved["기준"]["계"] == pytest.approx(_stone() * 2) and saved["안"][0]["수량"] == 2
|
||||
assert fed["기준"]["계"] == pytest.approx(_stone() * 5) # 알약 있음 = 알약
|
||||
assert none["기준"] is None and "수량 없음" in none["까닭"] # 빈 수량 = 금액 빠짐
|
||||
|
||||
|
||||
def test_굳힌_사본_차례_굳힌_손_알약_저장(world): # noqa: F811
|
||||
key = _sc(world, [{"로직": STONE, "수량": {"손": 2}}])
|
||||
ref, whole = {**SYS, "키": key}, m01.loaded()[1]
|
||||
with store.at(world["system"]):
|
||||
doc = refs.read_ref(ref)
|
||||
frozen = refs.with_pills(doc)
|
||||
old = {**frozen, "담은로직": [{**frozen["담은로직"][0], "수량": {"손": 9}}]} # 옛 굳힌 손
|
||||
pill = {f"{refs.ref_id(ref)}#0": 4}
|
||||
with store.at(world["system"]):
|
||||
bare = refs.Expand(whole, read=lambda r: frozen)(ref)
|
||||
fed = refs.Expand(whole, read=lambda r: frozen, given=pill)(ref)
|
||||
kept = refs.Expand(whole, read=lambda r: old, given=pill)(ref)
|
||||
assert bare["기준"]["계"] == pytest.approx(_stone() * 2) # 저장
|
||||
assert fed["기준"]["계"] == pytest.approx(_stone() * 4) # 알약 > 저장
|
||||
assert kept["기준"]["계"] == pytest.approx(_stone() * 9) # 굳힌 손 > 알약
|
||||
|
||||
|
||||
def _saved_doc():
|
||||
"""723-4 SC 꼴(고정 노무 1000 · 2000 + 자재 MAT 500) · 저장 수량 2 · 2 · 자재 3
|
||||
= 굳힌 `{저장}`."""
|
||||
doc = _doc(hand=2, mat=3)
|
||||
return {
|
||||
**doc,
|
||||
"담은로직": [refs.saved_qty(x) for x in doc["담은로직"]],
|
||||
"자재": [refs.saved_qty(x) for x in doc["자재"]],
|
||||
}
|
||||
|
||||
|
||||
def test_B09_저장_수량_금액_알약_있으면_알약():
|
||||
daega, block = _build(_saved_doc(), ({}, "L:lib"))
|
||||
assert [ln["qty"] for ln in block["lines"]] == [2.0, 2.0, 3.0]
|
||||
assert not any(ln.get("ask") for ln in block["lines"]) and block["cover"] == f"C:{RID}"
|
||||
got = compute_labor(daega, SHEETS)["daega_blocks"][0]
|
||||
assert got["sum"] == 2000.0 + 4000.0 + 1500.0 and got["blank"] == 0
|
||||
given = {f"{RID}#0": 1.5, f"{RID}#자재|{MAT}": 4} # 첫 줄 · 자재만 알약 · 둘째 줄 = 저장
|
||||
daega, block = _build(_saved_doc(), (given, "S:pipe@100.0"))
|
||||
assert [ln["qty"] for ln in block["lines"]] == [1.5, 2.0, 4.0]
|
||||
assert block["cover"] == f"C:{RID}|S:pipe@100.0"
|
||||
got = compute_labor(daega, SHEETS)["daega_blocks"][0]
|
||||
assert got["sum"] == 1500.0 + 4000.0 + 2000.0
|
||||
|
||||
|
||||
def test_B09_옛_사본_null_은_그대로_빈_줄():
|
||||
daega, block = _build(_doc(), ({}, "L:lib")) # 723-1 때 굳힌 사본 = 전후 같음
|
||||
assert [ln["qty"] for ln in block["lines"]] == [None, None, None]
|
||||
assert compute_labor(daega, SHEETS)["daega_blocks"][0]["blank"] == 3
|
||||
|
||||
|
||||
def test_단가산출_안_일위대가_저장_수량_금액(world): # noqa: F811
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
|
||||
key = _sc(world, [{"로직": STONE, "수량": {"손": 2}}])
|
||||
sk = {
|
||||
"키": "SK-000001",
|
||||
"이름": "겹",
|
||||
"단위": "식",
|
||||
"더한줄": [{"참조": {**SYS, "키": key}, "수량": 3}],
|
||||
}
|
||||
with store.at(world["system"]):
|
||||
got = calc.calc_doc(calc.clean_doc(sk, refs.here()))
|
||||
assert not got["빠진줄"]
|
||||
assert got["1단위"]["계"] == pytest.approx(float(Decimal(str(_stone())) * 2 * 3), rel=1e-6)
|
||||
|
||||
|
||||
def _src(name: str) -> str:
|
||||
return open(os.path.join(SRC, name), encoding="utf-8").read()
|
||||
|
||||
|
||||
def test_화면_수량_칸_저장_새_줄_1_두_칸():
|
||||
table = _src("M02_MasterTemplete_Labor_Table.ts")
|
||||
assert "const temp = new Map" not in table and "withTemp" not in table # 휘발 칸 걷음
|
||||
assert "row.수량 = raw ? { 손: v } : null;" in table and "changed(true);" in table
|
||||
assert table.count("수량: { ...ONE }") == 4 # 기초 · 참조 · 고정 · 자재 새 줄 = 1
|
||||
assert 'BasePrice: ["기준단위 단가"' in table and 'QtyAmount: ["수량 반영 금액"' in table
|
||||
assert 'sum_price: t("BasePrice"), sum: t("QtyAmount")' in table
|
||||
assert 'NeedQty: ["수량 입력"' in table
|
||||
assert 'if (!edit || key !== "qty" || n === null) return false;' in table # 읽기 층 못 고침
|
||||
@@ -66,9 +66,10 @@ def test_확인한_줄만_별칭_수량_빠진_줄_장부(client):
|
||||
assert origin["확인"]["누가"] == {"id": 42, "이름": "갑"} and origin["확인"]["때"]
|
||||
stone, *made, soil = doc["담은로직"] # 705-3 — 확인 안 한 줄 · 「우리에 없음」 = 고정 줄
|
||||
assert stone["고정"]["단가"] == {"노무비": 0, "재료비": 1400000, "경비": 0}
|
||||
hands = origin["실무수량"]["줄"] # 723-1 — 수량 = 저장 안 함 · 엑셀 수량은 출처 기록만
|
||||
assert stone["고정"]["출처"]["줄"] == 0 and stone["수량"] is None and hands["0"] == 1
|
||||
assert soil["고정"]["이름"] == "잔토처리(인력100%)" and soil["수량"] is None
|
||||
hands = origin["실무수량"]["줄"] # 742-1 — 엑셀 수량 = 저장 수량 · 출처 기록도 같은 값
|
||||
assert stone["고정"]["출처"]["줄"] == 0 and stone["수량"] == {"손": hands["0"]} == {"손": 1}
|
||||
assert soil["고정"]["이름"] == "잔토처리(인력100%)"
|
||||
assert soil["수량"] == {"손": hands[str(len(doc["담은로직"]) - 1)]}
|
||||
assert hands[str(len(doc["담은로직"]) - 1)] == 0.09
|
||||
assert origin["품셈"] == [1, 2] and got["줄수"]["고정"] == 2 and got["빠진줄"] == []
|
||||
assert made == [
|
||||
@@ -76,14 +77,14 @@ def test_확인한_줄만_별칭_수량_빠진_줄_장부(client):
|
||||
"로직": "GF000101",
|
||||
"입력값": {"지역": "서울"},
|
||||
"별칭": "구조물터파기(인력100%)",
|
||||
"수량": None, # 723-1 — 엑셀 0.14 = 본뜬.실무수량
|
||||
"수량": {"손": 0.14}, # 742-1 — 엑셀 0.14 = 저장 수량
|
||||
"메모": "실무 0.14㎥ × 1 · 토사",
|
||||
},
|
||||
{
|
||||
"로직": "GF000113",
|
||||
"입력값": {},
|
||||
"별칭": "구조물되메우기(인력100%)",
|
||||
"수량": None, # 엑셀 0.05 × 배 2 = 본뜬.실무수량(723-1)
|
||||
"수량": {"손": 0.1}, # 엑셀 0.05 × 배 2 = 저장 수량(742-1)
|
||||
"메모": "실무 0.05㎥ × 2",
|
||||
},
|
||||
]
|
||||
@@ -117,18 +118,20 @@ def test_로직_여럿_같은_별칭_감쌈_자재_몫_덮음(client):
|
||||
first, second, fx, wrapped = doc["담은로직"]
|
||||
assert fx["고정"]["출처"]["줄"] == 2 # 확인 안 한 줄 = 고정(705-3)
|
||||
assert first["별칭"] == second["별칭"] == "구조물터파기(인력100%)" # 한 줄 로직 둘 = 같은 별칭
|
||||
hands = doc["본뜬"]["실무수량"]["줄"] # 723-1 — 저장 수량 없음 · 엑셀 수량 = 출처 기록
|
||||
assert second["수량"] is None and hands["1"] == 0.014
|
||||
hands = doc["본뜬"]["실무수량"]["줄"] # 742-1 — 엑셀 수량 = 저장 수량 · 출처 기록도 같은 값
|
||||
assert second["수량"] == {"손": 0.014} and hands["1"] == 0.014
|
||||
assert second["메모"] == "실무 0.14㎥ × 0.1(몫 0.1) · 토사"
|
||||
assert (
|
||||
wrapped["별칭"] == "잔토처리(인력100%)" and wrapped["수량"] is None and hands["3"] == 0.09
|
||||
wrapped["별칭"] == "잔토처리(인력100%)"
|
||||
and wrapped["수량"] == {"손": 0.09}
|
||||
and hands["3"] == 0.09
|
||||
)
|
||||
inner = _doc(mine, wrapped["참조"]["키"])
|
||||
assert inner["본뜬"]["키"] == "HP-000001 제3호표 4째 줄 GF000101"
|
||||
assert inner["담은로직"][0]["로직"] == "GF000101" and inner["담은로직"][0]["수량"] is None
|
||||
assert inner["담은로직"][0]["로직"] == "GF000101" and inner["담은로직"][0]["수량"] == {"손": 1}
|
||||
assert inner["본뜬"]["실무수량"]["줄"] == {"0": 1}
|
||||
assert [(m["코드"], m["수량"], m["별도계상"]) for m in doc["자재"]] == [
|
||||
("MT004258", None, "직접계산")
|
||||
("MT004258", {"손": 1}, "직접계산") # 742-1 저장 수량
|
||||
]
|
||||
assert doc["본뜬"]["실무수량"]["자재"] == {"MT004258": 1}
|
||||
stone = next(v for k, v in _book(mine).items() if k.startswith("임도표지석(받침"))
|
||||
@@ -251,12 +254,12 @@ def test_하위_호표_먼저_확인_퍼센트_빠짐_통째(client):
|
||||
made = _doc(mine, got["참조"]["키"])
|
||||
line = made["담은로직"][0]
|
||||
assert line["참조"] == child["참조"] and line["별칭"] == "고화매트재료비"
|
||||
assert line["수량"] is None and made["본뜬"]["실무수량"]["줄"]["0"] == 2.3 # 723-1
|
||||
assert line["수량"] == {"손": 2.3} and made["본뜬"]["실무수량"]["줄"]["0"] == 2.3 # 742-1
|
||||
assert "고화매트재료비||m2" not in _book(mine) # 호표 참조 줄 = 장부 안 씀
|
||||
whole = _build(client, 3, 통째={"대상": [{"로직": "GF000101", "배": 1}]})
|
||||
doc = _doc(mine, whole["참조"]["키"])
|
||||
assert [x["로직"] for x in doc["담은로직"]] == ["GF000101"]
|
||||
assert doc["담은로직"][0]["수량"] is None and doc["본뜬"]["실무수량"]["줄"] == {"0": 1}
|
||||
assert doc["담은로직"][0]["수량"] == {"손": 1} and doc["본뜬"]["실무수량"]["줄"] == {"0": 1}
|
||||
assert {x["까닭"] for x in whole["빠진줄"]} == {"호표 통째로 대신"}
|
||||
head = next(v for k, v in _book(mine).items() if k.startswith("호표|임도표지석|"))
|
||||
assert head["짝"][0]["로직"] == "GF000101" and head["원래"]["품명"] == "임도표지석"
|
||||
|
||||
@@ -134,8 +134,9 @@ def test_섞인_줄_몫_배(client):
|
||||
made = _build(client, "HP-000001", no, [{"차례": line["차례"], "대상": [one]}], decide=True)
|
||||
doc = _doc(made["참조"])
|
||||
at = next(i for i, x in enumerate(doc["담은로직"]) if x.get("로직") == "GF000211")
|
||||
assert doc["담은로직"][at]["수량"] is None # 723-1 — 수량 저장 안 함 · 엑셀 수량 = 출처 기록
|
||||
assert doc["본뜬"]["실무수량"]["줄"][str(at)] == round(line["수량"] * 0.1, 6)
|
||||
want = round(line["수량"] * 0.1, 6) # 742-1 — 엑셀 수량(몫) = 저장 수량 · 출처 기록도 같은 값
|
||||
assert doc["담은로직"][at]["수량"] == {"손": want}
|
||||
assert doc["본뜬"]["실무수량"]["줄"][str(at)] == want
|
||||
|
||||
|
||||
def test_막는_길(client):
|
||||
|
||||
@@ -228,11 +228,13 @@ def test_calc_는_참조를_펼침_승격은_기준_나누기_배_사전조합
|
||||
ref = {**SYS, "키": sc}
|
||||
with store.at(world["system"]):
|
||||
doc = labor.clean({"열": "rv", "담은로직": [{"참조": ref, "수량": {"손": 5}}]})
|
||||
bare = labor.calc(doc, {})["줄"][0] # 723-1 — 안 줄 손은 저장 안 함 · 안 이으면 수량 없음
|
||||
bare = labor.calc(doc, {})["줄"][0] # 742-1 — 안 줄 저장 수량(손 2)
|
||||
got = labor.calc(doc, {f"{refs.ref_id(ref)}#0": 2}) # 안 줄 알약 = 구조물 조합 칸 값
|
||||
assert bare["기준"] is None and "수량 없음" in bare["까닭"] and bare["안"][0]["수량"] is None
|
||||
one = got["줄"][0]
|
||||
fed = labor.calc(doc, {f"{refs.ref_id(ref)}#0": 4})["줄"][0] # 알약이 저장보다 앞섬
|
||||
want = Decimal(str(_base(TEN)["계"])) / 10 * 2
|
||||
assert bare["기준"]["계"] == pytest.approx(float(want)) and bare["안"][0]["수량"] == 2
|
||||
assert fed["기준"]["계"] == pytest.approx(float(want * 2)) and fed["안"][0]["수량"] == 4
|
||||
one = got["줄"][0]
|
||||
assert one["기준"]["계"] == pytest.approx(float(want))
|
||||
assert (one["분모"], one["배"], one["알약"]) == (
|
||||
"㎡",
|
||||
@@ -450,8 +452,9 @@ def test_층_목록_서고_줄은_이름_단위_기준금액을_실음(world):
|
||||
sc = _combo(world["system"], [{"참조": {**SYS, "키": sp}, "수량": {"손": 3}}], "셋")
|
||||
rows = [{"종류": "precombo", "이름": sc}, {"종류": "labor", "이름": "rv"}]
|
||||
refs.summaries(world["system"], rows)
|
||||
# 723-1 — 안 줄 수량 저장 안 함 → 목록 기준금액 없음(쓰는 쪽 알약으로만 값)
|
||||
assert (rows[0]["제목"], rows[0]["기준금액"]) == ("셋", None)
|
||||
# 742-1 — 목록 기준금액 = 안 줄 저장 수량(손 3)으로
|
||||
want = Decimal(str(_base(TEN)["계"])) / 10 * 3
|
||||
assert rows[0]["제목"] == "셋" and rows[0]["기준금액"] == pytest.approx(float(want))
|
||||
assert "기준금액" not in rows[1]
|
||||
|
||||
|
||||
@@ -468,8 +471,8 @@ def test_사전조합_로직_줄_섞임_합은_손셈_지우기_409_굳힘(world
|
||||
]
|
||||
sc = _combo(world["system"], lines, "섞음")
|
||||
saved = _read(world["system"], "precombo", sc)["담은로직"]
|
||||
assert saved[1:] == [ # 723-1 — 안 줄 수량 저장 안 함
|
||||
{"로직": STONE, "입력값": {}, "수량": None, "메모": "돌"},
|
||||
assert saved[1:] == [ # 742-1 — 안 줄 수량 저장(빈 수량 = null)
|
||||
{"로직": STONE, "입력값": {}, "수량": {"손": 3}, "메모": "돌"},
|
||||
{"로직": TEN, "입력값": {}, "수량": None, "메모": None},
|
||||
]
|
||||
ten, stone = (Decimal(str(_base(c)["계"])) for c in (TEN, STONE))
|
||||
|
||||
Reference in New Issue
Block a user