- master_material.label = 이름 + 지역 칸 · 검색어 지역 낱말 · 자재지역 입력 · 고르는 차례(master_pick)가 이름 글 대신 지역 칸과 맞대짐 - 로직 1,520: 같음 1,517 · 전부터 멈춤 3(GC001199 ~ 1201) — 지역 칸만 고쳤을 때 다르던 11(시멘트 전국 · 포항 모래 · 포항 레미콘)이 원래 줄로 - 계산 줄 품목 칸에 지역 · 상하차 · M01 고르기 창(가격 연결 · 로직 재료 후보) 글에 지역 · 상하차(모름 「-」 뺌) - M01 표 찾기(Store_Find) 낱말이 지역 칸에도 맞음 — 「레미콘 서울」 · 저장 뒤 찾기 기억 지움도 지역 칸 포함 - 새 줄 지역 기본 「-」 · test_m01_pick 합판 서울 기대를 지역 칸으로 - check_master 틀 · 본문 · 로직 · 조합 · 목차 0 · 단위 27 · 배 7(전과 같음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
6.6 KiB
Python
154 lines
6.6 KiB
Python
"""M01 재료 기본 검색어 · 고름 · 단위 환산 · 단위 경고 — PLAN 8-1 · 8-2 계약.
|
||
|
||
검색어 든 견본 로직 줄을 시험 안에서 만들어(`POST /calc` 의 `row` — 저장 안 함) 돌림. 정본 폴더는 읽기만.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from decimal import Decimal
|
||
|
||
import pytest
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from M01_MasterData import M01_MasterData_Router as router_module
|
||
from M01_MasterData import M01_MasterData_Store as store
|
||
|
||
mf = store.cm.mf
|
||
mp = mf.mp
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def client() -> TestClient:
|
||
app = FastAPI()
|
||
app.include_router(router_module.router)
|
||
return TestClient(app)
|
||
|
||
|
||
def _sample(term: str, unit: str, **cond) -> dict:
|
||
"""재료 줄 하나짜리 견본 로직 — 검색어 줄 하나 · 수량 1."""
|
||
return {
|
||
"이름": "검색어 견본",
|
||
"구분": "자체",
|
||
"입력": [],
|
||
"호표": [
|
||
{
|
||
"종류": "재료",
|
||
"요소": {"검색어": term, **cond},
|
||
"이름": "견본 재료",
|
||
"단위": unit,
|
||
"수량": "1",
|
||
"비목": "재료비",
|
||
}
|
||
],
|
||
}
|
||
|
||
|
||
def _line(client: TestClient, row: dict, picks: dict | None = None, path: str = "calc") -> dict:
|
||
body = {"key": "GF999999", "inputs": {}, "row": row, **({"고름": picks} if picks else {})}
|
||
got = client.post(f"/api/m01/{path}", json=body).json()
|
||
assert got.get("ok", True) is True, got
|
||
return got["lines"][0] if path == "calc" else got
|
||
|
||
|
||
def test_검색어로_찾고_지역_낱말이_맞는_줄이_먼저(client) -> None:
|
||
seoul = _line(client, _sample("합판(내수) 12t 서울", "㎡"))
|
||
assert seoul["출처"].startswith("MT000018") and seoul["단가"] == 11354 # 값 열 가운데 낮은 값
|
||
busan = _line(client, _sample("합판(내수) 12t 부산", "㎡"))
|
||
assert busan["출처"].startswith("MT000020") and busan["단가"] == 11488
|
||
assert "단위경고" not in seoul
|
||
|
||
|
||
def test_고름은_그_줄만_그_품목으로_셈(client) -> None:
|
||
row = _sample("합판(내수) 12t 서울", "㎡")
|
||
got = _line(client, row, {"0": "MT000019"})
|
||
assert got["출처"].startswith("MT000019") and got["단가"] == 11556
|
||
assert _line(client, row)["단가"] == 11354 # 요청마다 — 저장 안 함
|
||
text = _line(client, row, {"0": "MT000019"}, "text")
|
||
assert text["ok"] is True # 읽는 식도 같은 몸으로 돎
|
||
|
||
|
||
def test_같은_성격_단위는_줄_단위로_환산(client) -> None:
|
||
got = _line(client, _sample("어닐링철선 서울 4.0mm", "톤")) # 자재 kg → 줄 톤
|
||
assert got["단가"] == 1610000 and "단위경고" not in got
|
||
got = _line(client, _sample("어닐링철선 서울 4.0mm", "g"))
|
||
assert got["단가"] == pytest.approx(1.61)
|
||
|
||
|
||
def test_성격이_다르면_계산은_하고_단위경고(client) -> None:
|
||
got = _line(client, _sample("어닐링철선 서울 4.0mm", "m"))
|
||
assert got["단가"] == 1610 and got["단위경고"] == "줄 m · 자재 kg"
|
||
assert got["금액"] == 1610
|
||
|
||
|
||
def test_검색어_후보가_없으면_그_줄만_비움(client) -> None:
|
||
got = client.post(
|
||
"/api/m01/calc", json={"key": "GF999999", "inputs": {}, "row": _sample("없는물건zzz", "개")}
|
||
).json()
|
||
assert got["ok"] is True and got["lines"][0]["금액"] is None
|
||
assert "후보 없음" in got["lines"][0]["까닭"]
|
||
|
||
|
||
def test_기계_입력은_목록_밖_원문번호도_받음(client) -> None:
|
||
key = "GC000267"
|
||
inputs = {**store.auto(key)["값"], "기계": "0101-0007"} # 고르기 목록 밖 · 기계 마스터엔 있음
|
||
got = client.post("/api/m01/calc", json={"key": key, "inputs": inputs}).json()
|
||
assert got["ok"] is True, got
|
||
bad = client.post("/api/m01/calc", json={"key": key, "inputs": {**inputs, "기계": "9999-9999"}})
|
||
assert bad.json()["ok"] is False # 기계 마스터에 없는 번호는 그대로 막힘
|
||
|
||
|
||
def test_단위_환산_표() -> None:
|
||
assert mp.convert(Decimal(1610), "kg", "톤") == 1610000
|
||
assert mp.convert(Decimal(5), "Ton", "kg") == Decimal("0.005")
|
||
assert mp.convert(Decimal(100), "㎡", "㎠") == Decimal("0.01")
|
||
assert mp.convert(Decimal(7), "kg", "m") == 7 # 성격이 다르면 그대로
|
||
assert mp.same_kind("ℓ", "㎥") and not mp.same_kind("kg", "m")
|
||
|
||
|
||
def test_check_master_검색어_후보0_단위경고(client) -> None:
|
||
whole = store.loaded()[1]
|
||
assert mf.check_pick(whole, "줄", {"검색어": "합판(내수) 12t 서울"}) == []
|
||
assert "후보 0" in mf.check_pick(whole, "줄", {"검색어": "없는물건zzz"})[0]
|
||
assert mf.check_unit(whole, "줄", {"검색어": "어닐링철선 서울 4.0mm"}, "kg") == []
|
||
assert "성격이 다름" in mf.check_unit(whole, "줄", {"검색어": "어닐링철선 서울 4.0mm"}, "m")[0]
|
||
|
||
|
||
def test_기계_입력은_EQ_키도_원문번호로_풀어_받음(client) -> None:
|
||
key = "GC000267"
|
||
number = "0101-0007" # 고르기 목록 밖
|
||
eq = next(k for k, r in store.loaded()[1].index["EQ"].items() if r.get("원문번호") == number)
|
||
base = {**store.auto(key)["값"]}
|
||
by_number = client.post(
|
||
"/api/m01/calc", json={"key": key, "inputs": {**base, "기계": number}}
|
||
).json()
|
||
by_key = client.post("/api/m01/calc", json={"key": key, "inputs": {**base, "기계": eq}}).json()
|
||
assert by_key["ok"] is True, by_key
|
||
assert by_key["sums"] == by_number["sums"] # EQ 키 = 원문번호와 같은 계산
|
||
bad = client.post("/api/m01/calc", json={"key": key, "inputs": {**base, "기계": "EQ999999"}})
|
||
assert bad.json()["ok"] is False
|
||
|
||
|
||
def test_재료_줄마다_품목_칸(client) -> None:
|
||
key = "GF000160"
|
||
body = {"key": key, "inputs": {k: str(v) for k, v in store.auto(key)["값"].items()}}
|
||
lines = client.post("/api/m01/calc", json=body).json()["lines"]
|
||
idx = next(
|
||
i for i, x in enumerate(lines) if x.get("품목", {}).get("이름", "").startswith("합판(내수)")
|
||
)
|
||
first = lines[idx]["품목"]
|
||
assert (first["이름"], first["지역"]) == ("합판(내수)", "서울") and first[
|
||
"값칸"
|
||
] # 830-10 지역 칸
|
||
assert {"키", "이름", "규격", "단위", "값칸"} <= set(first)
|
||
lines = client.post("/api/m01/calc", json={**body, "고름": {str(idx): "MT000019"}}).json()[
|
||
"lines"
|
||
]
|
||
assert lines[idx]["품목"]["키"] == "MT000019" and "인천" in lines[idx]["품목"]["지역"]
|
||
|
||
|
||
def test_목록_검색은_띄어쓰기를_무시(client) -> None:
|
||
for q in ("함석", "골 함 석", "골함석"):
|
||
got = client.get("/api/m01/rows", params={"file": "재료_자재품목.json", "q": q}).json()
|
||
assert any(r["이름"] == "골 함 석" for r in got["rows"]), q
|