- 열 한 벌 = 키 · 구분 · 상세구분 · 이름 · 규격 · 단위 · 상하차 · 조달청 · 물가자료 · 유통물가 · 물가정보 · 거래가격 · 일반가격 · 출처 · 비고 · 면수(material_merge.COLUMNS 한 곳 · check_master 가 씀)
- 「관급」 칸 → 「조달청」(값 0 · EST 가격정보 값 안 씀) · 새 칸 「일반가격」(빈칸 · 시스템 관리자 손 입력 · 빌더가 원문번호로 이어 받음) · 상하차 = 단위 바로 뒤
- 남는 줄 지역 기준 = 전국 › 지역 없음 › 서울 › 서울 권역 › 경기 › 그 밖(값 중간) · 기준 지역은 비고 「통합: 기준 지역 …」(지역 없는 묶음은 안 적음)
- 재료 아닌 줄 삭제(요구 832) — 품질시험 수수료 60(가격정보 출처 「2026(경북)」) · 일반 도로용 블록 5(「~26.10.16」) · 치즐 2 줄은 남김 · 값 없음
- 재료_자재품목 24,092 → 17,692 줄(통합 6,335 · 재료 아닌 줄 65) · 남는 줄 키 = 830-1 키(7565ee787 이 지운 대장 키를 되살려 다시 지음 · 새 키 0) · 남는 줄 물가지 값 바뀐 것 0 · 빌더 두 번 = 같은 파일
- 가장 낮은 값 = 사급 칸(물가지 넷 + 일반가격) — master_material.SLOTS · M02_Elem_Alias.PRICES · M02 PRICE_SLOTS · 조달청 칸 안 봄 · M01 고르기 「관급」 표시 = 조달청 칸 · B09 대비표 「관급」 출처 = M01 조달청 칸(이름 그대로)
- M01 재료 화면 상하차 = 고르기(부품 createSelectField) · 새 줄 기본 「-」 · 로직 고르기 표 · 시험 계산 값 줄에 일반가격
- check_master 틀 · 로직 · 조합 0 · 단위 27 · 배 7(전과 같음) · _틀.md · _갱신절차.md 열 정의
- 시험: 새 test_830_2_material_merge · test_830_1 지움(EST 그대로 상태 = 830-2 로 끝남 · 남는 검사는 830_2 로) · 지역 줄 · 관급 이름 · 관급 칸을 보던 시험 기대값 고침(m01_api · m01_pick · masterdata_check · 814_1 · 815_1 · 827_2 · 827_3 · 750_1 · m02_hopyo_match · m02_labor_doc) · 「시멘트 전국」 검색어 시험 = xfail(830-3 연결 할 일)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
9.5 KiB
Python
187 lines
9.5 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
|
"""827-3 지킴 — 불러오기 재료 고름(요구 811-2 · 812-1 · 임시 storage).
|
|
|
|
① 파일 고름 → 그 파일 같은 종 줄 전부 이음(블록 둘) · 고른 줄 = 우리 값(엑셀 값 기억 없음) · 문서 머리 `가져온.재료`
|
|
② 다른 파일 영향 0 · 별칭 · 마스터 안 바뀜 · 고름 풀기 · [끝내기] 뒤 같은 파일 다시 올림 = 고름 그대로
|
|
③ 이미 만든 SK = 물음 예(`apply_made`)일 때만 · 사람이 따로 바꾼 줄 그대로 · 풀면 가져온 그대로로
|
|
④ 별도계상 줄 = 품목 「별도계상 · 품목: 이름 규격」 + 단가 0 그대로(줄 이음 없음)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy as cp
|
|
import json
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from M02_MasterTemplete import M02_Doc_Import as dim
|
|
from M02_MasterTemplete import M02_Doc_Sk_Path as skpath
|
|
from M02_MasterTemplete import M02_Elem_Alias as ea
|
|
from M02_MasterTemplete import M02_Hopyo_Forget as forget
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_San_MatPick as mp
|
|
from M02_MasterTemplete import M02_Sangeun_FromHopyo as fh
|
|
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
|
from resources.tester import test_810_1_stc as stc
|
|
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
|
|
|
|
WIRE = "결속선(철선)|#20 , 0.9M/M|KG|" # 봉화 산근 98 · 99 — 후보 여럿
|
|
PIPE_BLOCK = 71 # 동부청영월 파형강관(별도계상) Φ800 머리
|
|
|
|
|
|
@lru_cache(maxsize=2)
|
|
def _raw(tag: str) -> bytes:
|
|
p = stc.FILES[tag]
|
|
if not p.exists():
|
|
pytest.skip(f"STC 없음 {tag}")
|
|
return p.read_bytes()
|
|
|
|
|
|
def _upload(folder: Path, tag: str) -> str:
|
|
return fh.upload_native(folder, stc.FILES[tag].name, _raw(tag), {"이름": "sub3임시"})["키"]
|
|
|
|
|
|
def _lines(doc: dict, kind: str) -> list[dict]:
|
|
return [x for x in doc[imp.KEY]["로직"]["호표"] if mp.kind_of(x.get("자원") or {}) == kind]
|
|
|
|
|
|
def _var(conv: dict, sym: str) -> dict:
|
|
return next(v for v in conv["변수"] if dim.dc.md.var_id(v) == sym)
|
|
|
|
|
|
def _entry(conv: dict, kind: str) -> dict:
|
|
return next(e for e in conv["가져온"]["재료"] if e["종"] == kind)
|
|
|
|
|
|
def test_pick_whole_file(world) -> None:
|
|
me = world["personal"]
|
|
me.mkdir(parents=True, exist_ok=True)
|
|
alias = json.dumps(ea.book()["별칭"], ensure_ascii=False, sort_keys=True, default=str)
|
|
with store.at(me):
|
|
hp = _upload(me, "봉화")
|
|
fp, name = mp.fingerprint(me, hp)
|
|
groups = {g["종"]: g for g in mp.census(me, hp)}
|
|
wire = groups[WIRE]
|
|
assert wire["판정"] == "후보 여럿" and wire["블록"] == [98, 99] and len(wire["후보"]) > 1
|
|
assert all(g["판정"] == "이어짐" or g["후보"] is not None for g in groups.values())
|
|
pick = wire["후보"][1]
|
|
assert mp.write(me, fp, name, {WIRE: pick})[WIRE]["키"] == pick["키"]
|
|
assert mp.tally(me, fp, WIRE) == {"블록": 2, "줄": 2}
|
|
with mp.using(mp.read(me, fp)["고름"]):
|
|
made = [fh.make(me, hp, no)["이름"] for no in (98, 99)]
|
|
for key in made:
|
|
doc = store.read("sangeun", key)["문서"]
|
|
assert doc[imp.KEY]["원본"]["지문"] == fp
|
|
lines = _lines(doc, WIRE)
|
|
assert lines and all(x["이음"] == pick["키"] for x in lines)
|
|
assert all(x["자원"]["재료찾기"]["고름"]["키"] == pick["키"] for x in lines)
|
|
assert all(
|
|
x["자원"]["재료찾기"]["판정"] == "후보 여럿" for x in lines
|
|
) # 엔진 판정 그대로
|
|
conv = dim.derive(doc, None, imp._whole())
|
|
e = _entry(conv, WIRE)
|
|
assert e["고름"]["키"] == pick["키"] and e["처음"]["갈래"] == "숫자"
|
|
assert _var(conv, e["기호"])["연결"] == {"갈래": "요소", "키": pick["키"]} # 우리 값
|
|
assert sorted(mp.made(me, fp)) == sorted(made)
|
|
assert mp.uses(fp, WIRE, (made[0],)) == [made[1]]
|
|
# 다른 파일 = 영향 0
|
|
other = _upload(me, "울진대흥")
|
|
fp2, _ = mp.fingerprint(me, other)
|
|
assert fp2 != fp and mp.read(me, fp2)["고름"] == {}
|
|
# 고름 풀기
|
|
assert mp.write(me, fp, name, {WIRE: None}) == {}
|
|
assert mp.read(me, fp)["고름"] == {}
|
|
# [끝내기] 뒤 같은 파일 다시 올림 = 고름 그대로
|
|
mp.write(me, fp, name, {WIRE: pick})
|
|
forget.finish(me, hp)
|
|
again = _upload(me, "봉화")
|
|
assert again != hp and mp.fingerprint(me, again)[0] == fp
|
|
assert mp.read(me, fp)["고름"][WIRE]["키"] == pick["키"]
|
|
# 별칭 · 마스터 안 바뀜 · 고름 자리 = 개인 층 실무자료/재료고름 한 파일씩
|
|
assert json.dumps(ea.book()["별칭"], ensure_ascii=False, sort_keys=True, default=str) == alias
|
|
assert sorted(p.name for p in (me / "실무자료" / mp.DIRNAME).iterdir()) == [f"{fp}.json"]
|
|
assert not list(world["system"].rglob(f"{fp}*"))
|
|
|
|
|
|
def test_made_only_on_yes_and_keeps_user_lines(world) -> None:
|
|
me = world["personal"]
|
|
me.mkdir(parents=True, exist_ok=True)
|
|
with store.at(me):
|
|
hp = _upload(me, "봉화")
|
|
fp, name = mp.fingerprint(me, hp)
|
|
sk98, sk99 = (fh.make(me, hp, no)["이름"] for no in (98, 99)) # 고름 전에 만듦
|
|
conv = skpath.current(store.read("sangeun", sk98)["문서"])["문서"]
|
|
e = _entry(conv, WIRE)
|
|
before = _var(conv, e["기호"])["연결"]
|
|
assert before["갈래"] == "숫자" and "고름" not in e
|
|
pick = e["후보"][0]
|
|
mp.write(me, fp, name, {WIRE: pick}) # 판 [저장](물음 아니오) = SK 그대로
|
|
assert _var(skpath.current(store.read("sangeun", sk98)["문서"])["문서"], e["기호"])["연결"] == before # fmt: skip
|
|
# 99 = 사람이 그 줄을 따로 고름(줄 쌓기 이름 단추)
|
|
got = store.read("sangeun", sk99)
|
|
doc = cp.deepcopy(skpath.current(got["문서"])["문서"])
|
|
other = e["후보"][2]["키"]
|
|
_var(doc, _entry(doc, WIRE)["기호"])["연결"] = {"갈래": "요소", "키": other}
|
|
skpath.save(sk99, {"문서": doc, "판": got["판"]})
|
|
money = skpath.read(sk98)["셈"]
|
|
out = mp.apply_made(fp, mp.read(me, fp)["고름"], {WIRE}) # 물음 예
|
|
assert out == {"고침": [sk98], "그대로": [sk99]}
|
|
now = skpath.read(sk98)
|
|
e98 = _entry(now["문서"], WIRE)
|
|
assert _var(now["문서"], e98["기호"])["연결"] == {"갈래": "요소", "키": pick["키"]}
|
|
assert e98["고름"]["키"] == pick["키"] and now["셈"] != money # 우리 값으로 다시 셈
|
|
assert (
|
|
_var(skpath.read(sk99)["문서"], e["기호"])["연결"]["키"] == other
|
|
) # 따로 고친 줄 그대로
|
|
# 풂 = 가져온 그대로(엑셀 값)
|
|
mp.write(me, fp, name, {WIRE: None})
|
|
assert mp.apply_made(fp, {}, {WIRE})["고침"] == [sk98]
|
|
back = skpath.read(sk98)
|
|
assert _num(_var(back["문서"], e98["기호"])["연결"]) == _num(before)
|
|
assert "고름" not in _entry(back["문서"], WIRE) and back["셈"] == money
|
|
|
|
|
|
def _num(link: dict) -> tuple:
|
|
return link["갈래"], float(link["값"])
|
|
|
|
|
|
def test_apart_item_zero(world) -> None:
|
|
me = world["personal"]
|
|
me.mkdir(parents=True, exist_ok=True)
|
|
with store.at(me):
|
|
hp = _upload(me, "동부청영월")
|
|
out = fh.emit(me, hp, PIPE_BLOCK)
|
|
line = next(x for x in out["로직"]["호표"] if "파형강관" in str((x.get("자원") or {}).get("이름"))) # fmt: skip
|
|
kind = mp.kind_of(line["자원"])
|
|
found = line["자원"]["재료찾기"]
|
|
assert found["판정"] == "규격 없음 — 고르기" and line["이음"] is None
|
|
gov = next(c for c in found["후보"] if "파형강관" in c["이름"]) # 830-2 이름에 관급 없음
|
|
fp, name = mp.fingerprint(me, hp)
|
|
plain = fh.make(me, hp, PIPE_BLOCK)
|
|
mp.write(me, fp, name, {kind: gov})
|
|
with mp.using(mp.read(me, fp)["고름"]):
|
|
got = fh.make(me, hp, PIPE_BLOCK)
|
|
assert got["이름"] != plain["이름"] and got["검산"] == plain["검산"] # 금액 그대로(단가 0)
|
|
doc = store.read("sangeun", got["이름"])["문서"]
|
|
mine = _lines(doc, kind)[0]
|
|
assert mine["이음"] is None and mine["자원"]["재료찾기"]["고름"]["키"] == gov["키"]
|
|
conv = dim.derive(doc, None, imp._whole())
|
|
e = _entry(conv, kind)
|
|
var = _var(conv, e["기호"])
|
|
assert e["별도계상"] and var["연결"]["갈래"] == "숫자"
|
|
assert f"별도계상 · 품목: {gov['이름']} {gov['규격']} · 셈은 프로젝트에서" in var["설명"]
|
|
assert mp.item_key(mine["자원"]) == gov["키"]
|
|
assert skpath.read(got["이름"])["셈"] == skpath.read(plain["이름"])["셈"]
|
|
# 이미 만든(고름 전) SK 에 물음 예 = 품목 설명만 · 단가 0 그대로
|
|
money = skpath.read(plain["이름"])["셈"]
|
|
assert plain["이름"] in mp.apply_made(fp, {kind: gov}, {kind})["고침"]
|
|
now = skpath.read(plain["이름"])
|
|
assert _entry(now["문서"], kind)["고름"]["키"] == gov["키"] and now["셈"] == money
|
|
assert f"품목: {gov['이름']} {gov['규격']}" in _var(now["문서"], e["기호"])["설명"]
|
|
# 이음 없는 줄 목록(가져온 식 머리 [이음 고르기]) = 종 열쇠(같은 파일 물음)
|
|
assert any(
|
|
u.get("종") == kind for u in imp.unlinked(store.read("sangeun", plain["이름"])["문서"])
|
|
)
|