99 lines
4.3 KiB
Python
99 lines
4.3 KiB
Python
"""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])
|