163 lines
7.0 KiB
Python
163 lines
7.0 KiB
Python
"""STMATE 재현 앱 저장 덧붙임 — COSTN 새 코드 행 · BDQTY 구성 고침 · QCODE · 제잡비 설정(PRJLST.SMT).
|
|
|
|
정답은 원본 STC 자체 — 고치지 않으면 원래 바이트 · 고친 것만 바뀜 · 다시 읽으면 고친 값.
|
|
쓰기는 임시 폴더(server.WORK_DBF_ROOT 바꿈)에서만.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "tools/stmate-test"))
|
|
import server # noqa: E402
|
|
from bdqty import decode_bdqty # noqa: E402
|
|
from dbf_rw import DbfTable # noqa: E402
|
|
from kanjub_cfg import build_kanjub_config, stage_slots # noqa: E402
|
|
from qty_qcode import read_qcode # noqa: E402
|
|
from smt_parser import parse_dbf_bytes, parse_prjlst_smt # noqa: E402
|
|
from stc_archive import files_of, read_entries # noqa: E402
|
|
from stc_save import apply_save # noqa: E402
|
|
|
|
STCS = sorted((ROOT / "resources/knowledge/original/실무문서").rglob("*.STC"))
|
|
IDS = [p.stem[:16] for p in STCS]
|
|
|
|
|
|
def _files(p):
|
|
return files_of(read_entries(p.read_bytes()))
|
|
|
|
|
|
def _kv(files):
|
|
_, prows, _ = parse_dbf_bytes(files["PRJLST.DBF"])
|
|
return parse_prjlst_smt(files["PRJLST.SMT"], prows[0]["PRJ_MEMO_HEX"])
|
|
|
|
|
|
@pytest.fixture
|
|
def work(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(server, "WORK_DBF_ROOT", tmp_path)
|
|
return tmp_path
|
|
|
|
|
|
@pytest.mark.parametrize("stc", STCS, ids=IDS)
|
|
def test_unchanged_extras_keep_bytes(stc):
|
|
f = _files(stc)
|
|
kv = _kv(f)
|
|
req = {
|
|
"kanjub_stages": {n: build_kanjub_config(kv, n) for n in stage_slots(kv)},
|
|
"bdqty_rows": decode_bdqty(f.get("BDQTY.DBF", b"")),
|
|
"qcode_rows": read_qcode(f)["rows"],
|
|
}
|
|
out, log = apply_save(f, req)
|
|
assert log == [] and all(out[k] is f[k] for k in f)
|
|
|
|
|
|
def test_costn_new_code_row_and_nagcd():
|
|
f = _files(STCS[0])
|
|
ct = DbfTable(f["COSTN.DBF"])
|
|
m_last = max(i for i in range(len(ct)) if ct.get(i, "CODE").startswith("M"))
|
|
some = ct.get(0, "CODE")
|
|
ups = [
|
|
{"CODE": "M09991", "NAME": "새 자재(계약 변환)", "UNIT": "EA", "MAMT1": 1234, "TAMT1": 1234},
|
|
{"CODE": some, "NAGCD": 1},
|
|
]
|
|
_, brows, _ = parse_dbf_bytes(f["BOQ11.DBF"])
|
|
boq = [{"_recno": r["_recno"]} for r in brows] + [
|
|
{"_new": True, "BDCODE1": "M09991", "NAME1": "새 자재(계약 변환)", "QTY1": 3}
|
|
]
|
|
out, log = apply_save(f, {"costn_updates": ups, "boq_updates": boq})
|
|
nt = DbfTable(out["COSTN.DBF"])
|
|
assert len(nt) == len(ct) + 1
|
|
assert nt.get(m_last + 1, "CODE") == "M09991" and nt.get(m_last + 1, "NAME") == "새 자재(계약 변환)"
|
|
assert float(nt.get(m_last + 1, "MAMT1")) == 1234 and float(nt.get(m_last + 1, "LAMT1")) == 0
|
|
assert nt.raw(m_last + 1, "IFORM").strip() == b""
|
|
for i in range(len(ct)): # 다른 행 바이트 그대로(NAGCD 고친 칸만)
|
|
j = i if i <= m_last else i + 1
|
|
if ct.get(i, "CODE") != some:
|
|
assert nt.recs[j] == ct.recs[i]
|
|
assert float(nt.get(0, "NAGCD")) == 1
|
|
bt = DbfTable(out["BOQ11.DBF"])
|
|
k = len(bt) - 1
|
|
assert bt.get(k, "BDCODE1") == "M09991" and float(bt.get(k, "MAMT1")) == 1234
|
|
assert int(float(bt.get(k, "M_AMT1"))) == 3 * 1234
|
|
assert any(x["field"] == "_insert" and x["table"] == "COSTN.DBF" for x in log)
|
|
|
|
|
|
@pytest.mark.parametrize("stc", STCS[:3], ids=IDS[:3])
|
|
def test_bdqty_edit_add_delete(stc):
|
|
f = _files(stc)
|
|
rows = decode_bdqty(f["BDQTY.DBF"])
|
|
t0 = DbfTable(f["BDQTY.DBF"])
|
|
codes = []
|
|
for r in rows:
|
|
if r["BDCODE"] not in codes:
|
|
codes.append(r["BDCODE"])
|
|
edit, drop = codes[0], codes[-1]
|
|
new = [dict(r) for r in rows if r["BDCODE"] != drop]
|
|
i0 = next(i for i, r in enumerate(new) if r["BDCODE"] == edit)
|
|
q = float(new[i0]["QTY1"]) + 0.25
|
|
new[i0]["QTY1"] = q
|
|
extra = dict(next(r for r in rows if r["BDCODE"] != edit), BDCODE=edit, QTY1=2.5)
|
|
new.insert(i0 + 1, extra)
|
|
out, log = apply_save(f, {"bdqty_rows": new})
|
|
got = decode_bdqty(out["BDQTY.DBF"])
|
|
assert len(got) == len(new)
|
|
assert float(got[i0]["QTY1"]) == q
|
|
assert got[i0 + 1]["BDCODE"] == edit and got[i0 + 1]["CODE"] == extra["CODE"]
|
|
assert float(got[i0 + 1]["QTY1"]) == 2.5
|
|
assert drop not in {r["BDCODE"] for r in got}
|
|
# 손대지 않은 호표 행은 원래 레코드 바이트 그대로
|
|
nt = DbfTable(out["BDQTY.DBF"])
|
|
kept = {bytes(r) for r in t0.recs}
|
|
same = [bytes(nt.recs[i]) in kept for i, r in enumerate(got) if r["BDCODE"] not in (edit,)]
|
|
assert all(same)
|
|
assert {x["table"] for x in log} == {"BDQTY.DBF"}
|
|
out2, log2 = apply_save(out, {"bdqty_rows": got}) # 다시 저장 — 그대로
|
|
assert log2 == [] and out2["BDQTY.DBF"] is out["BDQTY.DBF"]
|
|
|
|
|
|
def test_qcode_rows_saved_and_loaded(work):
|
|
stc = STCS[0]
|
|
rows = [
|
|
{"CODE": "Q00001", "NAME": "토사 절취량", "BQCODE": "", "QTY0": 12.5, "IFORM": "L*B*H", "XCODE": ""},
|
|
{"CODE": "Q00002", "NAME": "암 절취량", "QTY0": 3, "IFORM": "", "XCODE": "A=1"},
|
|
]
|
|
res = server.api_save(server.SaveDbfReq(source_path=str(stc), target_folder_name="qc", qcode_rows=rows))
|
|
assert set(res["changed_files"]) == {"QCODE.DBF", "QCODE.SMT"}
|
|
load = server.api_load(server.LoadReq(source_path=res["saved_stc"]))
|
|
got = load["qcode_rows"]
|
|
assert [r["CODE"] for r in got] == ["Q00001", "Q00002"]
|
|
assert float(got[0]["QTY0"]) == 12.5 and got[0]["IFORM"] == "L*B*H" and got[1]["XCODE"] == "A=1"
|
|
# 불러온 행 그대로 다시 저장 — 수량코드 파일은 바뀌지 않음
|
|
res2 = server.api_save(
|
|
server.SaveDbfReq(source_path=res["saved_stc"], target_folder_name="qc2", qcode_rows=got)
|
|
)
|
|
assert res2["change_count"] == 0
|
|
with pytest.raises(Exception):
|
|
server.api_save(
|
|
server.SaveDbfReq(source_path=str(stc), target_folder_name="qc3", qcode_rows=[{"CODE": "X1"}])
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("stc", STCS[:3], ids=IDS[:3])
|
|
def test_kanjub_stage_change_writes_only_changed_keys(stc):
|
|
f = _files(stc)
|
|
kv = _kv(f)
|
|
st = {n: build_kanjub_config(kv, n) for n in stage_slots(kv)}
|
|
key = next(k for k in st[1]["rates"] if f"1_R_{'T_KITA' if k == 'KITA' else k}" in kv)
|
|
st[1]["rates"][key] += 0.5
|
|
st[1]["addRows"].append(
|
|
{"group": 4, "name": "시험 추가금액", "unit": "식", "bigo": "", "amts": [1000, 0, 0, 0, 0]}
|
|
)
|
|
# 화면 옛 칸(smt_updates)이 원래 값을 보내도 설정 쪽 고침이 이김
|
|
sk = f"1_R_{'T_KITA' if key == 'KITA' else key}"
|
|
out, log = apply_save(f, {"kanjub_stages": st, "smt_updates": {sk: kv[sk]}})
|
|
kv2 = _kv(out)
|
|
assert float(kv2[sk]) == pytest.approx(float(kv[sk]) + 0.5)
|
|
g4 = int(float(kv.get("ADD_CNT_4", 0) or 0)) + 1
|
|
assert kv2["ADD_CNT_4"] == str(g4) and kv2[f"ADD_NAME_4_{g4:02d}"] == "시험 추가금액"
|
|
assert kv2[f"ADD_AMT_4_{g4:02d}_1"] == "1000"
|
|
changed = {x["field"] for x in log if x["table"] == "PRJLST.SMT"}
|
|
assert sk in changed and all(k == sk or k.startswith("ADD_") for k in changed)
|
|
assert {k: v for k, v in kv2.items() if k not in changed} == {k: v for k, v in kv.items() if k not in changed}
|