fix(m02): 일위대가 별도계상 자재 셈에서 뺌 · 원문 같은 책 절 모두 · 단위 까닭 시험(222-1)
- 로직 자재 별도계상(빈 칸 = 기본) = 기준에서 빼고 일위대가 상세 줄에서 지움 · 자재목록엔 남김 - 사용자 자재 별도계상 = 합에서 뺌(파이썬 · TS settle 짝 · 거울 시험) - 사전조합 안 줄 · 프로젝트 사본 셈도 같은 길 - 별도계상 바꾸면 다시 셈(로직 자재 = 서버 · 사용자 자재 = 짝 셈) - 원문 출처 = 로직 출처 + 호표 · 입력 출처 같은 책 절(산림품셈 12-1-2 · 12-1-3) - 기존 시험 셋 = 직접계산 표시 · 대조용 apart 끔 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
@@ -20,9 +20,11 @@
|
||||
고름 = `{호표 차례: MT키}`(엔진 `choosing` 그대로) · 금액은 로직 재료비 안이라 합 · 알약에 안 더함(보기만) · 사용자 자재와
|
||||
같은 품목 두 번은 [저장] 때 400.
|
||||
승격(134-1) = 로직 줄 「단가승격」 표시(`true` 만 · 없음 = 단가산출) · 금액 무관 · B09 호표 몫.
|
||||
자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 금액 무관) — 사용자 자재 = 줄 덧칸 ·
|
||||
자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 별도계상만 금액에 닿음) — 사용자 자재 = 줄 덧칸 ·
|
||||
로직 자재 = 로직 줄 `자재표시 {호표 차례: {세 칸}}`(차례가 재료 줄인 것만 · 틀리면 버림) ·
|
||||
빈 칸 = 읽을 때 `MARK_DEFAULT`(`mark_of` · 저장은 비움 · 옛 「일반」 = 버림 → 사급 · 162-1).
|
||||
별도계상(222-1) = 그 자재 금액은 기준 · 합에서 빠지고 로직 `줄` 에서 지움(`drop_apart` · `settle` ·
|
||||
짝 `Labor_Calc.ts`) · `재료` · `자재`(자재목록 · B08 수량)엔 남음.
|
||||
% 덧줄(149-4) = 식 「X * Y / 100」 덧줄에 덧칸 `율`(Y) · `규격`(「노무비의 %」 등 · `pct_extras`) — 표기만 ·
|
||||
단위 · 수량 · 금액 그대로(「식 · 1」) · 화면이 율 · % 로 그림 · M01 시험계산 표도 이 함수.
|
||||
"""
|
||||
@@ -224,6 +226,35 @@ def mark_of(one: Any, key: str) -> str:
|
||||
return marks(one).get(key) or MARK_DEFAULT[key]
|
||||
|
||||
|
||||
def apart(one: Any) -> bool:
|
||||
"""별도계상(222-1) — 세 칸 「별도계상」(빈 칸 = 기본) 자재는 일위대가 셈에서 빠짐(본 프로그램에서
|
||||
따로 계상 · 자재목록은 상세 보기라 따로 안 더함)."""
|
||||
return mark_of(one, "별도계상") == "별도계상"
|
||||
|
||||
|
||||
def drop_apart(got: dict[str, Any], shown: Any) -> dict[str, Any]:
|
||||
"""로직 줄 답(`run_line`)에서 별도계상 로직 자재 빼기(222-1) — 그 재료 줄 금액을 기준에서 빼고
|
||||
`줄`(일위대가 상세)에서 지움 · `재료`(자재목록 · 세 칸 고침)는 그대로.
|
||||
ponytail: 「재료비의 %」 덧줄은 엔진 값 그대로(뺀 재료 몫 안 고침) — 그런 로직이 별도계상을 쓰면 엔진에 넘김."""
|
||||
shown = shown if isinstance(shown, dict) else {}
|
||||
lines = got.get("줄") or []
|
||||
out = {
|
||||
n
|
||||
for m in got.get("재료") or []
|
||||
if (n := m["차례"]) < len(lines) and apart(shown.get(str(n)))
|
||||
}
|
||||
if not out or not got.get("기준"):
|
||||
return got
|
||||
base = {k: _number(v) or Decimal(0) for k, v in got["기준"].items()}
|
||||
for n in out:
|
||||
for k, v in (lines[n].get("비목") or {}).items():
|
||||
cut = _number(v) or Decimal(0)
|
||||
base[k] = base.get(k, Decimal(0)) - cut
|
||||
base["계"] -= cut
|
||||
kept = [x for i, x in enumerate(lines) if i not in out]
|
||||
return {**got, "기준": _json(base), "줄": kept}
|
||||
|
||||
|
||||
def _mat_marks(value: Any, logic: dict) -> dict[str, dict[str, str]] | None:
|
||||
"""로직 자재 세 칸(148-4) `{호표 차례: {세 칸}}` — 맨 위 층 재료 줄 차례 · 허용 값만 ·
|
||||
빈 것 = None."""
|
||||
@@ -386,6 +417,7 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]:
|
||||
have = {str(k): v for k, raw in given.items() if (v := _number(raw)) is not None}
|
||||
hands = {line_id(one): one.get("수량") for one in doc.get("담은로직") or []}
|
||||
mhands = {one.get("코드"): one.get("수량") for one in doc.get("자재") or []}
|
||||
aside = {one.get("코드") for one in doc.get("자재") or [] if apart(one)} # 222-1 합에서 뺌
|
||||
zero = dict.fromkeys((*COSTS, "계"), Decimal(0))
|
||||
lines, mats, bases, reflects = [], [], [], []
|
||||
for line in result.get("줄") or []:
|
||||
@@ -399,9 +431,11 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]:
|
||||
qty, source = _pick(mhands.get(one.get("코드")), one.get("알약"), have)
|
||||
price = _number(one.get("기준단가"))
|
||||
cost = price * qty if price is not None and qty is not None else None
|
||||
mats.append({**one, "수량": qty, "수량출처": source, "반영": cost})
|
||||
if one.get("코드") in aside:
|
||||
continue
|
||||
bases.append({**zero, "재료비": price, "계": price} if price is not None else None)
|
||||
reflects.append({**zero, "재료비": cost, "계": cost} if cost is not None else None)
|
||||
mats.append({**one, "수량": qty, "수량출처": source, "반영": cost})
|
||||
return _json({"줄": lines, "자재": mats, "기준합": _sum(bases), "반영합": _sum(reflects)})
|
||||
|
||||
|
||||
@@ -468,7 +502,7 @@ def calc(
|
||||
"배": pill.get("배") or 1,
|
||||
}
|
||||
got = run_line(whole, code, one.get("입력값"), one.get("켜쌓기율"), one.get("고름"))
|
||||
lines.append({**line, **got})
|
||||
lines.append({**line, **drop_apart(got, one.get("자재표시"))})
|
||||
mats = [
|
||||
{
|
||||
"코드": one["코드"],
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
@@ -16,6 +17,7 @@ from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
|
||||
_SPEC = ("이름", "설명", "단위", "고르기", "범위")
|
||||
_LAYERED = "골쌓기 기준" # 85-2 — 표 주문에 이 글이 있으면 켜쌓기율 대상 로직
|
||||
_NUMBER = re.compile(r"(?<![\d-])\d+(?:-\d+)+(?![\d-])") # 품셈 절 번호(「12-1-3」)
|
||||
|
||||
|
||||
def _num(value: Any) -> Any:
|
||||
@@ -63,6 +65,26 @@ def _labels(whole: Any, spec: dict) -> dict[str, str]:
|
||||
return out
|
||||
|
||||
|
||||
def sources(logic: dict) -> Any:
|
||||
"""원문 출처(222-1) — 로직 `출처` + 호표 · 입력 · 중간 · 덧줄 `출처` 가운데 같은 책(번호 앞 글이
|
||||
같음)의 절 번호를 덧붙임(「산림품셈 12-1-2 · 12-1-3」 · 원문 패널이 절마다 찾음 · M01
|
||||
`make.source`) · 다른 책 절은 안 붙임 · 번호 없는 출처는 그대로."""
|
||||
main = str(logic.get("출처") or "")
|
||||
head = _NUMBER.search(main)
|
||||
if not head:
|
||||
return logic.get("출처")
|
||||
book, seen = main[: head.start()], set(_NUMBER.findall(main))
|
||||
extra = []
|
||||
for part in ("호표", "입력", "중간", "덧줄"):
|
||||
for row in logic.get(part) or []:
|
||||
src = str(row.get("출처") or "") if isinstance(row, dict) else ""
|
||||
hit = _NUMBER.search(src)
|
||||
if hit and src[: hit.start()] == book and hit.group() not in seen:
|
||||
seen.add(hit.group())
|
||||
extra.append(hit.group())
|
||||
return " · ".join([main, *extra])
|
||||
|
||||
|
||||
def _listed(logics: list[dict]) -> list[dict[str, Any]]:
|
||||
"""로직 줄마다 `{로직, 이름, 결과단위, 출처, 켜쌓기, 입력: [{이름, 설명, 단위, 고르기?, 고르기이름?, 범위?, 자동값}]}`."""
|
||||
whole = m01.loaded()[1]
|
||||
@@ -84,7 +106,7 @@ def _listed(logics: list[dict]) -> list[dict[str, Any]]:
|
||||
"로직": logic.get("키"),
|
||||
"이름": logic.get("이름"),
|
||||
"결과단위": logic.get("결과단위"),
|
||||
"출처": logic.get("출처"),
|
||||
"출처": sources(logic),
|
||||
"켜쌓기": logic.get("키") in layers,
|
||||
"입력": specs,
|
||||
}
|
||||
|
||||
@@ -423,6 +423,7 @@ class Expand:
|
||||
got = labor.run_line(
|
||||
self.whole, one["로직"], one.get("입력값"), one.get("켜쌓기율"), one.get("고름")
|
||||
)
|
||||
got = labor.drop_apart(got, one.get("자재표시")) # 별도계상 로직 자재 뺌(222-1)
|
||||
times = labor._number(pill.get("배")) or Decimal(1)
|
||||
base = got.get("기준")
|
||||
return {
|
||||
@@ -463,7 +464,7 @@ class Expand:
|
||||
rows.append({k: v * qty for k, v in sub["기준"].items()})
|
||||
for mat in doc.get("자재") or []: # 사용자 자재(175-2) = 기준단가 × 손 · 빈칸 = 안 셈
|
||||
qty = labor._number((mat.get("수량") or {}).get("손"))
|
||||
if qty is None:
|
||||
if qty is None or labor.apart(mat): # 별도계상 = 안 셈 · 안 줄에도 없음(222-1)
|
||||
continue
|
||||
got = labor.material_price(self.whole, mat["코드"], mat.get("단위"))
|
||||
price = labor._number(got["기준단가"])
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* 거울 시험 `resources/tester/test_m02_labor_calc_mirror.py`.
|
||||
* 기준(결과단위 그대로)은 서버 calc 길이 셈 · 여기는 받은 기준에
|
||||
* 수량(손이 칸보다 앞섬 · 칸 = 알약 키 값) · 반영 = 기준 × 수량 ÷ 배 · 자재 = 재료비만.
|
||||
* 별도계상 자재(222-1 · 빈 칸 = 기본 별도계상) = 반영은 보이되 합에 안 더함(짝 `apart`).
|
||||
* 참조 줄(단가승격 · 사전조합 · 50-3)은 `lineId` = 「참조|층|주인|키」(짝 `M02_Labor_Ref.ref_id`). */
|
||||
|
||||
import type { CalcLine as LogicLine } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
|
||||
@@ -79,7 +80,7 @@ export interface LaborCalc {
|
||||
/** 수량만 보는 문서 조각 — 담은 로직 · 자재의 손 값 */
|
||||
export interface LaborQtyDoc {
|
||||
담은로직?: { 로직?: string; 참조?: LaborRef; 수량?: Qty }[];
|
||||
자재?: { 코드: string; 수량?: Qty }[];
|
||||
자재?: { 코드: string; 수량?: Qty; 별도계상?: string }[];
|
||||
}
|
||||
|
||||
function num(raw: unknown): number | null {
|
||||
@@ -130,6 +131,10 @@ export function settle(
|
||||
}
|
||||
const hands = new Map((doc.담은로직 ?? []).map((one) => [lineId(one), one.수량 ?? null]));
|
||||
const mhands = new Map((doc.자재 ?? []).map((one) => [one.코드, one.수량 ?? null]));
|
||||
// 별도계상(222-1) — 「직접계산」 아니면(빈 · 옛 값 = 기본 별도계상) 합에서 뺌(짝 `M02_Labor_Doc.apart`)
|
||||
const aside = new Set(
|
||||
(doc.자재 ?? []).filter((m) => m.별도계상 !== "직접계산").map((m) => m.코드),
|
||||
);
|
||||
const base = zero();
|
||||
const done = zero();
|
||||
const add = (sum: Money, m: Money | null): void => {
|
||||
@@ -146,8 +151,8 @@ export function settle(
|
||||
const [qty, source] = pick(mhands.get(one.코드), one.알약, have);
|
||||
const price = num(one.기준단가);
|
||||
const cost = price !== null && qty !== null ? price * qty : null;
|
||||
if (price !== null) add(base, { ...zero(), 재료비: price, 계: price });
|
||||
if (cost !== null) add(done, { ...zero(), 재료비: cost, 계: cost });
|
||||
if (price !== null && !aside.has(one.코드)) add(base, { ...zero(), 재료비: price, 계: price });
|
||||
if (cost !== null && !aside.has(one.코드)) add(done, { ...zero(), 재료비: cost, 계: cost });
|
||||
return { ...one, 수량: qty, 수량출처: source, 반영: cost };
|
||||
});
|
||||
return { 줄, 자재, 기준합: base, 반영합: done };
|
||||
|
||||
@@ -383,14 +383,16 @@ export function mountLaborDetail(
|
||||
else delete cur.담은로직[i].승격;
|
||||
opt.onChanged(dirty());
|
||||
}
|
||||
// 자재 세 칸(148-4) — 화면 캐시만 · 금액 무관이라 다시 셈 없음
|
||||
// 자재 세 칸(148-4) — 화면 캐시만 · 별도계상만 금액에 닿음(222-1 · 사용자 자재 = 짝 셈 · 로직 자재 = 서버 기준)
|
||||
function markMat(i: number, key: MatMarkKey, v: string): void {
|
||||
setMark(cur.자재[i], key, v);
|
||||
opt.onChanged(dirty());
|
||||
if (key === "별도계상") changed(true);
|
||||
else opt.onChanged(dirty());
|
||||
}
|
||||
function markLogicMat(i: number, at: number, key: MatMarkKey, v: string): void {
|
||||
setLogicMark(cur.담은로직[i], at, key, v);
|
||||
opt.onChanged(dirty());
|
||||
if (key === "별도계상") changed();
|
||||
else opt.onChanged(dirty());
|
||||
}
|
||||
function removeMat(i: number): void {
|
||||
cur.자재.splice(i, 1);
|
||||
|
||||
@@ -79,7 +79,8 @@ def _world_combo(world): # noqa: F811
|
||||
{"로직": STONE, "수량": {"손": 1}},
|
||||
{"로직": NET, "수량": {"손": 0}, "고름": {"4": NET_ITEM}},
|
||||
]
|
||||
mats = [{"코드": MAT, "수량": {"손": 2}}, {"코드": WIRE, "수량": None}]
|
||||
# MAT = 직접계산(222-1 · 기본 별도계상이면 셈에서 빠짐)
|
||||
mats = [{"코드": MAT, "수량": {"손": 2}, "별도계상": "직접계산"}, {"코드": WIRE, "수량": None}]
|
||||
with store.at(world["system"]):
|
||||
key = refs.create("precombo", {"이름": "자재 조합", "담은로직": lines, "자재": mats})[
|
||||
"이름"
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"""M02 구조물조합 일위대가 기계비빔타설(PLAN 222-1) — 단위 까닭 · 별도계상 빼기 · 원문 여럿.
|
||||
|
||||
① 단위 — GF000156 모래 · 자갈 = 원문 kg(QF000353) ÷ 단위중량(㎏/㎥) · 칸에 t/㎥ 값(2 · 2.1)을 넣으면
|
||||
838 ÷ 2 = 419 ㎥ 처럼 1000 배 · 0 = 표 위끝(1,700 · 1,800 ㎏/㎥) → 0.49 · 0.57 ㎥ · 칸을 비우면
|
||||
자동값(master_auto 「나누는 자리 = 1」)이 1 이라 838 ㎥(자료 · 기본값 몫 — 223 보고).
|
||||
② 별도계상 — 로직 자재(`자재표시` · 빈 칸 = 기본 별도계상) = 기준에서 빠지고 `줄` 에서 지움 · `재료` 는
|
||||
남음 · 사용자 자재 = 합에서 뺌(짝 TS `settle`) · 사전조합 안 줄 · 사본 셈 같은 길.
|
||||
③ 원문 — 로직 출처 + 호표 · 입력 출처 가운데 같은 책 절 번호(「산림품셈 12-1-2 · 12-1-3」).
|
||||
"""
|
||||
|
||||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M01_MasterData import M01_MasterData_Store_Make as make
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
from M02_MasterTemplete import M02_Labor_Inputs as inputs
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from resources.tester.test_m02_labor_calc_mirror import _same, _ts
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "..", "..", "M02_MasterTemplete")
|
||||
MIX = "GF000156" # 기계비빔타설 — 호표 3 시멘트 · 4 모래 · 5 자갈
|
||||
GIVEN = {"재료배합": "(A)", "골재최대치수": 40}
|
||||
MAT = "MT000008"
|
||||
DIRECT = {"별도계상": "직접계산"}
|
||||
|
||||
|
||||
def _whole():
|
||||
return m01.loaded()[1]
|
||||
|
||||
|
||||
def _rows(got):
|
||||
return {x["이름"]: {**x, "수량": float(x["수량"])} for x in got["줄"]}
|
||||
|
||||
|
||||
# ── ① 단위 ──────────────────────────────────────────────────────────
|
||||
def test_단위중량_칸에_t_값이면_1000배_0이면_표_위끝():
|
||||
wrong = labor.run_line(_whole(), MIX, {**GIVEN, "모래단위중량": 2, "자갈단위중량": 2.1})
|
||||
rows = _rows(wrong)
|
||||
assert rows["시멘트"]["단위"] == "kg" and rows["시멘트"]["수량"] == pytest.approx(335)
|
||||
assert rows["모래(건조상태)"]["수량"] == pytest.approx(838 / 2) # 419 ㎥
|
||||
assert rows["자갈 또는 부순돌"]["수량"] == pytest.approx(1032 / 2.1) # 491.4 ㎥
|
||||
zero = {**GIVEN, "모래단위중량": 0, "자갈단위중량": 0}
|
||||
right = _rows(labor.run_line(_whole(), MIX, zero))
|
||||
assert right["모래(건조상태)"]["수량"] == pytest.approx(838 / 1700)
|
||||
assert right["자갈 또는 부순돌"]["수량"] == pytest.approx(1032 / 1800)
|
||||
|
||||
|
||||
# ── ② 별도계상 ───────────────────────────────────────────────────────
|
||||
def _calc(shown=None, mats=()):
|
||||
line = {"로직": MIX, "입력값": GIVEN, "수량": {"손": 1}}
|
||||
if shown:
|
||||
line["자재표시"] = shown
|
||||
doc = labor.clean({"열": "rv", "담은로직": [line], "자재": list(mats)})
|
||||
return labor.calc(doc, {})
|
||||
|
||||
|
||||
def test_로직_자재_기본_별도계상은_기준_줄에서_빠지고_재료엔_남음():
|
||||
raw = labor.run_line(_whole(), MIX, GIVEN)
|
||||
cut = sum(Decimal(str(m["금액"])) for m in raw["재료"])
|
||||
one = _calc()["줄"][0]
|
||||
names = {x["이름"] for x in one["줄"]}
|
||||
assert not names & {"시멘트", "모래(건조상태)", "자갈 또는 부순돌"}
|
||||
assert {"콘크리트공", "보통인부"} <= names
|
||||
assert len(one["재료"]) == 3 # 자재목록(세 칸 고침)엔 남음
|
||||
want = Decimal(str(raw["기준"]["재료비"])) - cut
|
||||
assert one["기준"]["재료비"] == pytest.approx(float(want))
|
||||
assert one["기준"]["계"] == pytest.approx(float(Decimal(str(raw["기준"]["계"])) - cut))
|
||||
assert one["기준"]["노무비"] == pytest.approx(float(raw["기준"]["노무비"]))
|
||||
|
||||
|
||||
def test_직접계산은_그대로_섞이면_그_자재만_빠짐():
|
||||
raw = labor.run_line(_whole(), MIX, GIVEN)
|
||||
full = _calc({str(n): DIRECT for n in (3, 4, 5)})["줄"][0]
|
||||
assert full["기준"]["계"] == pytest.approx(float(raw["기준"]["계"]))
|
||||
assert len(full["줄"]) == len(raw["줄"])
|
||||
part = _calc({"3": DIRECT})["줄"][0] # 시멘트만 셈
|
||||
names = {x["이름"] for x in part["줄"]}
|
||||
assert "시멘트" in names and "모래(건조상태)" not in names
|
||||
cement = next(m for m in raw["재료"] if m["이름"] == "시멘트")
|
||||
others = sum(Decimal(str(m["금액"])) for m in raw["재료"]) - Decimal(str(cement["금액"]))
|
||||
assert part["기준"]["계"] == pytest.approx(float(Decimal(str(raw["기준"]["계"])) - others))
|
||||
|
||||
|
||||
def test_사용자_자재_별도계상은_합에서_빠지고_반영은_보임():
|
||||
aside = _calc(None, [{"코드": MAT, "수량": {"손": 2}}])
|
||||
direct = _calc(None, [{"코드": MAT, "수량": {"손": 2}, **DIRECT}])
|
||||
price = direct["자재"][0]["기준단가"]
|
||||
assert aside["자재"][0]["반영"] == pytest.approx(price * 2) # 목록엔 금액 보임
|
||||
assert direct["반영합"]["계"] - aside["반영합"]["계"] == pytest.approx(price * 2)
|
||||
assert direct["기준합"]["재료비"] - aside["기준합"]["재료비"] == pytest.approx(price)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mark", [None, "직접계산", "별도계상", "옛값"])
|
||||
def test_사용자_자재_별도계상_파이썬과_TS_같은_답(mark):
|
||||
one = {"코드": MAT, "수량": {"손": 3}, **({"별도계상": mark} if mark else {})}
|
||||
doc = labor.clean({"열": "rv", "담은로직": [{"로직": MIX, "입력값": GIVEN}], "자재": [one]})
|
||||
doc["자재"][0].update({"별도계상": mark} if mark else {}) # 옛 값도 그대로 넘겨 봄
|
||||
answer = labor.calc(doc, {})
|
||||
values = {"기계비빔타설|㎥": 2}
|
||||
_same(labor.settle(answer, doc, values), _ts(answer, doc, values))
|
||||
|
||||
|
||||
def test_사전조합_안_줄도_별도계상_뺌():
|
||||
raw = labor.run_line(_whole(), MIX, GIVEN)
|
||||
cut = sum(Decimal(str(m["금액"])) for m in raw["재료"])
|
||||
doc = {
|
||||
"이름": "비빔 조합",
|
||||
"단위": "㎥",
|
||||
"담은로직": [{"로직": MIX, "입력값": GIVEN, "수량": {"손": 1}}],
|
||||
"자재": [
|
||||
{"코드": MAT, "수량": {"손": 2}}, # 기본 별도계상 = 안 셈 · 안 줄에도 없음
|
||||
],
|
||||
}
|
||||
ref = {"층": "system", "주인": None, "키": "SC999999"}
|
||||
got = refs.Expand(_whole(), lambda r: doc)(ref)
|
||||
assert [x.get("자재") for x in got["안"]] == [None]
|
||||
pill = got["안"][0]
|
||||
times = Decimal(1) # 기계비빔타설 알약 배 1
|
||||
assert pill["기준"]["계"] * times == pytest.approx(Decimal(str(raw["기준"]["계"])) - cut)
|
||||
assert got["기준"]["계"] == pytest.approx(Decimal(str(raw["기준"]["계"])) - cut)
|
||||
|
||||
|
||||
def test_화면_별도계상_바꾸면_다시_셈():
|
||||
detail = open(os.path.join(SRC, "M02_MasterTemplete_Labor_Detail.ts"), encoding="utf-8").read()
|
||||
assert 'if (key === "별도계상") changed(true);' in detail # 사용자 자재 = 짝 셈
|
||||
assert 'if (key === "별도계상") changed();' in detail # 로직 자재 = 서버 기준
|
||||
|
||||
|
||||
# ── ③ 원문 여럿 ──────────────────────────────────────────────────────
|
||||
def test_원문_출처는_같은_책_절_모두():
|
||||
src = inputs.sources(_whole().logic(MIX))
|
||||
assert src == "산림품셈 12-1-2 · 12-1-3"
|
||||
assert inputs.logic_inputs([MIX])["로직"][0]["출처"] == src
|
||||
assert [f["number"] for f in make.source(src)["found"]] == ["12-1-2", "12-1-3"]
|
||||
|
||||
|
||||
def test_원문_출처_번호_없거나_더할_것_없으면_그대로():
|
||||
assert inputs.sources({"출처": "자체"}) == "자체"
|
||||
assert inputs.sources({"출처": None}) is None
|
||||
one = {
|
||||
"출처": "산림품셈 3-1",
|
||||
"호표": [{"출처": "건설품셈 공통 1-2-4 3"}, {"출처": "산림품셈 3-1"}],
|
||||
}
|
||||
assert inputs.sources(one) == "산림품셈 3-1" # 다른 책 · 같은 절은 안 붙임
|
||||
@@ -184,7 +184,10 @@ def test_입력이_없으면_빠진_입력_목록_셈은_자동값(world):
|
||||
assert _stuck(got) == [] and got["기준합"]["계"] > 0
|
||||
|
||||
|
||||
def test_마스터_단가를_바꿔도_계산_그대로_새로_가져오면_바뀜(world):
|
||||
def test_마스터_단가를_바꿔도_계산_그대로_새로_가져오면_바뀜(world, monkeypatch):
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor_doc
|
||||
|
||||
monkeypatch.setattr(labor_doc, "apart", lambda one: False) # M01 조합 셈과 대조(222-1 안 뺌)
|
||||
root, master_dir = world["root"], world["master"]
|
||||
_save(root, [_pipe()])
|
||||
values = _fill_values(root)
|
||||
|
||||
@@ -87,7 +87,7 @@ def world(tmp_path: Path, monkeypatch, master_copy: Path) -> dict[str, Any]:
|
||||
{"로직": LOGICS[1], "입력값": {name: value}, "수량": {"손": HAND}},
|
||||
{"로직": LOGICS[2]},
|
||||
],
|
||||
"자재": [{"코드": MATERIAL}],
|
||||
"자재": [{"코드": MATERIAL, "별도계상": "직접계산"}], # 222-1 합에 셈
|
||||
}
|
||||
)
|
||||
_write(system / "labor" / f"{COLUMN}.json", doc)
|
||||
|
||||
Reference in New Issue
Block a user