fix(842-1): B09 고정 줄 이은 자재 별도 · 관급 = 자재대에 한 번 — 거창 안내판 줄 사라진 50,000 되살림
- 고정 줄 이은 자재가 별도 · 관급이면 대가 안 0 원 + 그 몫 `aside` → 집계 `aside` 줄(자재대 수량) (`Calc_Labor` py · ts 짝 `_fixed_apart` · `Summary._fixed_lines`) — 기본 표시는 M02 자재표시 그대로 - 거창 직접비 합 = 엑셀(384,916,227) · 견본 넷 사라진 재료 0 · 겹쳐 셈 0 - 지킴 시험 `test_842_1_b09_fixed_aside`(재료 · 고정 · 관급 · 기계 겸 · 거울) + `test_707_7` 전수 시험 - `test_707_7` 낡은 기대(거창 gap 50000 · 다시 부르기 384866227) 고침 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -139,14 +139,18 @@ def _none() -> dict[str, float | None]:
|
||||
def _fixed(line: dict[str, Any], books: dict[str, Any]) -> dict[str, float | None]:
|
||||
"""고정 줄 단가(705-1) — 줄 단가 그대로 · 이은 자재가 관급 · 별도계상이면 재료 0(841-1 단가 책
|
||||
값으로 다시 안 덮음)."""
|
||||
own, link = line.get("own") or {}, line.get("link") or {}
|
||||
out = {part: num(own.get(part)) or 0.0 for part in PARTS}
|
||||
row = books["material"].get(link.get("material")) if link.get("material") else None
|
||||
if row and row.get("supply") in SUPPLY:
|
||||
out = {part: num((line.get("own") or {}).get(part)) or 0.0 for part in PARTS}
|
||||
if _fixed_apart(line, books):
|
||||
out["material"] = 0.0
|
||||
return out
|
||||
|
||||
|
||||
def _fixed_apart(line: dict[str, Any], books: dict[str, Any]) -> bool:
|
||||
"""고정 줄이 이은 자재가 관급 · 별도(842-1) — 대가 안 0 · 그 몫은 자재대(`aside`)로."""
|
||||
key = (line.get("link") or {}).get("material")
|
||||
return bool(key) and (books["material"].get(key) or {}).get("supply") in SUPPLY
|
||||
|
||||
|
||||
def _base(spec: str, lines: list[dict[str, Any]], src: list) -> float | None:
|
||||
"""% 줄 밑수 — `part:비목` 그 비목 줄 금액 합 · `lines:labor` 노무 줄 금액 합 ·
|
||||
`line:이름` 그 줄 · `bundle:labor|material|machine` 그 종류 줄(+ 그 이음 고정 줄) 합 ·
|
||||
@@ -212,6 +216,9 @@ def _block(
|
||||
}
|
||||
if line["kind"] == "material" and _apart(line, books): # 관급 · 별도계상 = 대가 안 0원
|
||||
extra["aside"], prices["material"], amounts["material"] = amounts["material"], 0.0, 0.0
|
||||
if line["kind"] == "fixed" and _fixed_apart(line, books): # 842-1 이은 자재 = 자재대 몫
|
||||
mat = num((line.get("own") or {}).get("material")) or 0.0
|
||||
extra["aside"] = None if qty is None else cut(mat * qty, line.get("place"))
|
||||
ref = {"ref": {"key": line["key"]}} if line.get("key") else {} # 집계표(145-5) 이음
|
||||
ask = {"why": line["ask"]} if qty is None and line.get("ask") else {} # 723-4
|
||||
lines.append(
|
||||
|
||||
@@ -121,14 +121,18 @@ function linePrices(line: Row, books: Row, sums: Record<string, Row>): Parts {
|
||||
/** 고정 줄 단가(705-1) — 줄 단가 그대로 · 이은 자재가 관급 · 별도계상이면 재료 0(짝 `_fixed`) */
|
||||
function fixedPrices(line: Row, books: Row): Parts {
|
||||
const own: Row = line.own || {};
|
||||
const link: Row = line.link || {};
|
||||
const out = zeros();
|
||||
for (const part of PARTS) out[part] = num(own[part]) ?? 0;
|
||||
const row: Row | undefined = link.material ? books.material[link.material] : undefined;
|
||||
if (row && (SUPPLY as readonly string[]).includes(row.supply)) out.material = 0;
|
||||
if (fixedApart(line, books)) out.material = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 고정 줄이 이은 자재가 관급 · 별도(842-1) — 대가 안 0 · 그 몫은 자재대(`aside`)로(짝 `_fixed_apart`) */
|
||||
function fixedApart(line: Row, books: Row): boolean {
|
||||
const key: string | undefined = (line.link || {}).material;
|
||||
return !!key && (SUPPLY as readonly string[]).includes((books.material[key] || {}).supply);
|
||||
}
|
||||
|
||||
/** % 줄 밑수 — part:비목 그 비목 줄 금액 합 · lines:labor 노무 줄 금액 합 · line:이름 그 줄 ·
|
||||
* bundle:labor|material|machine 그 종류 줄(+ 그 이음 고정 줄) 합 · rows:0,2 블록 그 차례 줄(src cid) 합
|
||||
* (778-4 · 짝 `.py` `_base`) */
|
||||
@@ -204,6 +208,11 @@ function block(b: Row, books: Row, sums: Record<string, Row>, gaps: Gap[], own:
|
||||
prices.material = 0;
|
||||
amounts.material = 0;
|
||||
}
|
||||
if (line.kind === "fixed" && fixedApart(line, books)) {
|
||||
// 842-1 이은 자재 = 자재대 몫(짝 `_block`)
|
||||
extra.aside =
|
||||
qty === null ? null : cut((num((line.own || {}).material) ?? 0) * qty, line.place);
|
||||
}
|
||||
const ref = line.key ? { ref: { key: line.key } } : {}; // 집계표(145-5) 이음
|
||||
const ask: Row = qty === null && line.ask ? { why: line.ask } : {}; // 723-4
|
||||
lines.push({ ...line, ...ref, ...ask, qty, prices, amounts, ...extra });
|
||||
|
||||
@@ -168,6 +168,8 @@ def _fixed_lines(block: dict[str, Any], line: dict[str, Any]) -> list[dict[str,
|
||||
link = line.get("link") or {}
|
||||
pairs = [("machine", link.get("machine"))] if link.get("machine") else []
|
||||
pairs = pairs or [(k, link.get(k)) for k in ("labor", "material") if link.get(k)]
|
||||
if "aside" in line and link.get("material"): # 842-1 이은 자재 별도 · 관급 = 자재대 몫
|
||||
pairs = [(k, v) for k, v in pairs if k != "material"] + [("aside", link["material"])]
|
||||
if not pairs:
|
||||
pairs = [("fixed", f"{block['key']}|{line.get('cid') or line.get('name')}")]
|
||||
return [{"kind": k, "key": str(v), "qty": line["qty"]} for k, v in pairs]
|
||||
|
||||
@@ -35,8 +35,8 @@ FILES = {
|
||||
#: 영월 = 내역 줄이 코드로만 가리킴(번 채움) · STmate 하위 호표 반올림 끝수 28 줄
|
||||
#: (줄당 1 원/단위 · 브레인 정함 = 그래도 이음)
|
||||
WANT = {
|
||||
# 841-1 이은 안내판 줄 = M02 사본 셈(엑셀보다 50,000 적음 · 노임 · 기계 덮개 걷음)
|
||||
"거창": (14, 68, 56, 12, 11, 8, 8, 384916227, 0, 50000),
|
||||
# 842-1 이은 안내판 줄 표지판지주 = 별도 → 대가 안 0 · 자재대 50,000 (직접비 합 = 엑셀)
|
||||
"거창": (14, 68, 56, 12, 11, 8, 8, 384916227, 0, 0),
|
||||
"대흥": (34, 171, 143, 28, 0, 26, 45, 710359136, 0, 0),
|
||||
"소광": (25, 117, 98, 19, 0, 0, 0, 173311386, 0, 0),
|
||||
"영월": (23, 138, 135, 3, 0, 21, 34, 767148700, 28, 2951),
|
||||
@@ -117,6 +117,28 @@ def test_실무_엑셀_통째_빠짐_0_합계_같음_층_쓰기_0(world, name):
|
||||
assert not [p for p in world["storage"].rglob("*") if p.is_file()] # 층 쓰기 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", ["거창", "대흥", "소광", "영월"])
|
||||
def test_대가_안_0_원_재료_자재대에_한_번(world, name):
|
||||
"""842-1 전수 — 별도 · 관급 이은 자재(재료 줄 · 고정 줄)마다 집계 `aside` 줄 · 4 자재대 · 관급
|
||||
자재대 수량 = 그 모음(사라짐 0) · 재료 줄 아닌 같은 자재 이중 집계 0."""
|
||||
from B09_Estimation.B09_Estimation_Summary import block_lines
|
||||
|
||||
got = world["run"](name)["payload"]
|
||||
sheets, aside = got["sheets"], got["inputs"].get("aside_qty") or {}
|
||||
apart = {r["key"] for r in sheets["compare"] if r["supply"] in ("별도", "관급")}
|
||||
items = {r["ref"]["key"]: r["qty"] for r in sheets["bill"]["rows"]
|
||||
if r.get("kind") == "item" and str(r["key"]).startswith("mat:")} # fmt: skip
|
||||
for tab in ("sg", "daega_blocks"):
|
||||
for block in sheets[tab]:
|
||||
lines = block_lines([block])[str(block["key"])]["lines"]
|
||||
for ln in block["lines"]:
|
||||
key = (ln.get("ref") or {}).get("key") or (ln.get("link") or {}).get("material")
|
||||
if key in apart and ln["kind"] in ("material", "fixed") and ln.get("qty"):
|
||||
assert any(x["kind"] == "aside" and x["key"] == key for x in lines), ln["name"]
|
||||
assert all(x["kind"] != "material" or x["key"] != key for x in lines)
|
||||
assert items == {k: v for k, v in aside.items() if k in apart}
|
||||
|
||||
|
||||
def test_대흥_총공사비_엑셀과_같음_하위는_참조사본(world):
|
||||
got = world["run"]("대흥")
|
||||
assert got["합계"]["우리총"] == got["합계"]["엑셀총"] == 1201879000
|
||||
@@ -145,7 +167,7 @@ def test_다시_부르기_같은_사본_두_번_셈_0(world):
|
||||
first = world["run"]("거창")
|
||||
again = world["run"]("거창", fresh=False)
|
||||
assert _libs(again["root"]) == _libs(first["root"])
|
||||
assert again["합계"]["우리직접"] == first["합계"]["우리직접"] == 384866227
|
||||
assert again["합계"]["우리직접"] == first["합계"]["우리직접"] == 384916227
|
||||
rows = again["user"]["bill"]["rows"]
|
||||
assert len({r["key"] for r in rows}) == len(rows)
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""842-1 B09 — 대가 안 0 원이 된 재료는 자재대(`aside`)에 꼭 한 번 · 겹쳐 세지 않음.
|
||||
|
||||
고정 줄이 이은 자재(`link.material`)가 별도 · 관급이면 대가 안 0 원 + 그 몫 = 줄 `aside` →
|
||||
집계표 `aside` 줄(자재대 수량) · 재료 줄 · 붙인 줄 · 관급도 같은 길 · 직접계산 = `aside` 없음.
|
||||
거울: `B09_Estimation_Calc_Labor.py` ↔ `.ts` (`fixedApart`).
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Summary import block_lines, totals # noqa: E402
|
||||
from resources.tester.test_145_2_b09_labor_calc import _b, _l, _same, _ts # noqa: E402
|
||||
|
||||
SHEETS = {
|
||||
"labor": [],
|
||||
"material": [
|
||||
{"key": "MA", "price": 50000, "supply": "별도"},
|
||||
{"key": "MB", "price": 800, "supply": "관급"},
|
||||
{"key": "MC", "price": 300, "supply": None},
|
||||
],
|
||||
"machine": [],
|
||||
"gaps": [],
|
||||
}
|
||||
|
||||
|
||||
def _fix(name, qty, mat, price, **link):
|
||||
own = {"labor": 0.0, "material": price, "expense": 0.0}
|
||||
return {
|
||||
"kind": "fixed",
|
||||
"name": name,
|
||||
"qty": qty,
|
||||
"own": own,
|
||||
"link": {"material": mat, **link},
|
||||
}
|
||||
|
||||
|
||||
DAEGA = {
|
||||
"blocks": [
|
||||
_b(
|
||||
"DG1",
|
||||
"품셈",
|
||||
"안내판",
|
||||
[
|
||||
_fix("지주", 1, "MA", 50000), # 별도 → 대가 안 0 · 자재대 50000
|
||||
_fix("관급 줄", 2, "MB", 800), # 관급 → 자재대(관급)
|
||||
_fix("직접 줄", 3, "MC", 300), # 직접계산 → 대가 안
|
||||
_fix(
|
||||
"기계 겸", 1, "MA", 50000, machine="X1"
|
||||
), # 기계 이음이 먼저여도 자재대 몫은 감
|
||||
{**_l("material", "MA", 4), "own": {"material": 50000}}, # 재료 줄도 같은 자재
|
||||
],
|
||||
),
|
||||
],
|
||||
"owners": {"x": "DG1"},
|
||||
}
|
||||
|
||||
|
||||
def test_고정_줄_자재_대가_안_0_자재대에_한_번():
|
||||
got = compute_labor(DAEGA, SHEETS)["daega_blocks"][0]
|
||||
by = {ln["name"]: ln for ln in got["lines"]}
|
||||
assert by["지주"]["amounts"]["material"] == 0 and by["지주"]["aside"] == 50000
|
||||
assert by["관급 줄"]["amounts"]["material"] == 0 and by["관급 줄"]["aside"] == 1600
|
||||
assert by["직접 줄"]["amounts"]["material"] == 900 and "aside" not in by["직접 줄"]
|
||||
assert got["material"] == 900 # 대가 안 = 직접계산 줄만
|
||||
lines = block_lines([got])[got["key"]]["lines"]
|
||||
assert [x for x in lines if x["kind"] == "material"] == [ # 직접 줄만 일반 재료 줄
|
||||
{"kind": "material", "key": "MC", "qty": 3}
|
||||
]
|
||||
aside = totals({"bill": {"rows": [{"kind": "item", "qty": 1, "ref": {"key": got["key"]}}]}},
|
||||
{got["key"]: {"lines": lines}})[0]["aside"] # fmt: skip
|
||||
assert aside == {"MA": 1 + 1 + 4, "MB": 2} # 재료 줄 4 + 고정 줄 둘 = 한 번씩 모음
|
||||
|
||||
|
||||
def test_거울_파이썬_TS():
|
||||
py = json.loads(json.dumps(compute_labor(DAEGA, SHEETS), ensure_ascii=False))
|
||||
assert _same(py, _ts(DAEGA, SHEETS, {})["out"])
|
||||
Reference in New Issue
Block a user