"""B09 내역서 화면 자료 — 화면이 곱하거나 더하지 않게 서버가 싣는 칸 (PLAN 12장 1차). 겨누는 것 ① 내역 줄 성분 단가 × 수량 = 성분 금액(성분마다 절사) — 화면은 실린 값만 찍음 ② 머리글 줄 금액 = 아래 줄 금액의 합(성분마다) · 본체 합계엔 안 들어감 ③ 일위대가 호표 번호 — 내역에 처음 쓰인 차례 · 같은 호표는 한 번 · 안에서 부르는 호표는 뒤에 ④ 단산 목록 — 부르는 일위대가 전부 · 성분 금액 """ from __future__ import annotations from decimal import Decimal from functools import lru_cache from B09_Estimation.B09_Estimation_BillOfQuantities import bill_line, build_bill from B09_Estimation.B09_Estimation_PriceBook import ( Money3, PriceBook, PriceDetail, PriceKind, PriceTitle, ) from B09_Estimation.B09_Estimation_UnitPrice import _slots, build_unit_prices from B09_Estimation.B09_Estimation_UnitPriceSheet import build_unit_price_sheet @lru_cache(maxsize=1) def _bill(): payload = { "work_items": [ {"work_item_code": "FP-09-03-02", "name": "토사깍기", "unit": "㎥", "quantity": 123.4}, {"work_item_code": "FP-09-12-01", "name": "측구터파기", "unit": "㎥", "quantity": 7}, {"work_item_code": "FP-09-03-02", "name": "토사깍기", "unit": "㎥", "quantity": 5}, ], "materials": [], } return build_bill(payload, build=build_unit_prices()) def test_줄_성분_단가와_금액이_실린다() -> None: priced = [r for r in _bill().rows if not r.is_group and r.amount_krw is not None] assert priced for row in priced: unit = Money3(row.unit_material_krw, row.unit_labor_krw, row.unit_expense_krw) line = bill_line(unit, row.quantity) assert (line.material, line.labor, line.expense) == ( row.material_krw, row.labor_krw, row.expense_krw, ) assert row.amount_krw == line.total body = row.as_dict() assert body["price_code"] == row.price_code and body["unit_labor_krw"] is not None def test_머리글_줄은_아래_줄_합이고_본체엔_안_든다() -> None: bill = _bill() detail_total = sum(r.amount_krw or 0 for r in bill.rows if not r.is_group) assert bill.body_total_krw == detail_total for group in (r for r in bill.rows if r.is_group): children = [ r for r in bill.rows if not r.is_group and r.item_no.startswith(f"{group.item_no}-") and r.amount_krw ] assert group.amount_krw == sum(r.amount_krw for r in children) assert group.labor_krw == sum(r.labor_krw for r in children) def test_호표_번호는_처음_쓰인_차례이고_안에서_부르는_호표는_뒤에() -> None: book = PriceBook() book.add_title(PriceTitle("L", PriceKind.LABOR, "보통인부", slots=_slots(Decimal(100000)))) for code in ("B-A", "B-B", "B-C"): book.add_title(PriceTitle(code, PriceKind.UNIT_PRICE, code)) book.add_detail(PriceDetail("B-C", "L", Decimal(1))) book.add_detail(PriceDetail("B-A", "B-C", Decimal(2))) # A 안에서 C 를 부름 book.add_detail(PriceDetail("B-B", "L", Decimal(1))) class Row: def __init__(self, code: str) -> None: self.price_code, self.name, self.spec, self.unit = code, code, "", "㎥" sheet = build_unit_price_sheet([Row("B-B"), Row("B-A"), Row("B-B"), Row("")], book) assert [(e.number, e.code) for e in sheet.entries] == [(1, "B-B"), (2, "B-A"), (3, "B-C")] assert sheet.entries[1].as_dict()["label"] == "제 2 호표" assert sheet.entries[1].money.labor == 200000 def test_내역에_호표_목록과_단산_목록이_함께_실린다() -> None: bill = _bill() codes = [e.code for e in bill.unit_price_sheet.entries] used = list(dict.fromkeys(r.price_code for r in bill.rows if r.price_code)) assert [c for c in codes if c in used] == used # 안에서 부르는 호표가 끼어도 차례는 그대로 for entry in bill.price_basis.entries: body = entry.as_dict() assert body["unit_price_codes"] and Money3( *(int(body[k]) for k in ("material_krw", "labor_krw", "expense_krw")) ).total == int(body["unit_price_krw"].split(".")[0])