- 정본 로직(08장 아홉 · 산림 10장 4륜 덤프) 시간환산 중간 식 = 세 줄 분자 ÷ 분모 곱(713-4 전 꼴) · 수식 문서 = 빌더 변환으로 다시 지음(변환 대조 1,520 같음 · 채택 1 원 셋 = 같은 까닭 714-3) - 체인톱 GF000337 은 그대로(시간환산 = 하루 연료 · 손료 → 시간 · 조종원 없음) - 짝: 기계 양식 시간환산(M01 lists = 세 줄 곱) · master_machine_coef ↔ common_util_machine_coef 기본값 5/24 · 기계 덮음 새 조종원 줄 꼴(master_doc_cover ↔ common_util_formula_doc_cover) · 기호 이름 상여분자 · 상여분모 · 휴지분자 · 휴지분모 - 전후: 조종원 기계 295 대 노무비만 ×5/3 · 조종원 없는 기계 불변 · 로직 1,520 중 기계 부르는 431 만 오름 - B09 는 안 고침(계수표 세 줄을 스스로 곱함 · 두 번 곱 0) 낡은 시험 고침: 713-4 지킴을 세 계수 꼴로 · masterdata_check · 796-1 · 821-2 · 821-4 · 822-2 · 836-1 · 839-1 기대값 = 조종원 5/24 · 802-2 채택 차이 셋 · 821-3 줄 쌓기 내 기계 = 836-2 뒤 개인 층 자동 따라옴(옛 단가새값 · 단가갱신 기대 걷음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
172 lines
6.9 KiB
Python
172 lines
6.9 KiB
Python
"""PLAN 821-2 M01 기계 저장 — 지킴 시험(임시 사본 폴더만 · 정본 안 씀).
|
|
|
|
① 새 기계 = 단위 · 출처 자동(마스터 단위 · 「자체」) · 계수 서버 재계산 · 다른 줄 그대로
|
|
② 고침 = 그 칸 한 줄만 · 품셈 입력이 같으면 저장 계수 그대로(점보드릴 777) · 바뀌면 다시 셈
|
|
③ 기계 길은 시스템 관리자만(아니면 403) · 양식 목록 · 비슷한 기계 찾기 · 쓰는 곳
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import difflib
|
|
import json
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import Depends, FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from common_util import common_util_auth as auth
|
|
from M01_MasterData import M01_MasterData_Router as router_module
|
|
from M01_MasterData import M01_MasterData_Store as store
|
|
|
|
sys.path.insert(
|
|
0, str(Path(__file__).resolve().parents[2] / "resources" / "master_data" / "scripts")
|
|
)
|
|
import master_machine_coef as mc # noqa: E402
|
|
|
|
REAL = store.FOLDER
|
|
MACHINE = "기계.json"
|
|
|
|
|
|
@pytest.fixture
|
|
def copy(tmp_path: Path, monkeypatch) -> Path:
|
|
for path in REAL.glob("*.json"):
|
|
if not path.name.startswith("_") or path.name == store.mk.BOOK.name:
|
|
shutil.copy(path, tmp_path / path.name)
|
|
shutil.copytree(REAL / "기계", tmp_path / "기계") # 기계 = 폴더(826-2)
|
|
monkeypatch.setattr(store, "FOLDER", tmp_path)
|
|
return tmp_path
|
|
|
|
|
|
def _app(role: str = "SYSTEM_ADMIN") -> TestClient:
|
|
app = FastAPI()
|
|
app.dependency_overrides[auth.verify_session] = lambda: {
|
|
"user_id": 1,
|
|
"name": "시험",
|
|
"role": role,
|
|
"company_id": 1,
|
|
}
|
|
deps = [Depends(auth.verify_session), Depends(auth.require_system_admin)]
|
|
app.include_router(router_module.router, dependencies=deps)
|
|
return TestClient(app)
|
|
|
|
|
|
def _save(client: TestClient, version: str, change: dict) -> None:
|
|
res = client.post(
|
|
"/api/m01/save",
|
|
json={"files": [{"file": MACHINE, "version": version, "changes": [change]}]},
|
|
)
|
|
assert res.status_code == 200, res.text
|
|
|
|
|
|
def _files(copy: Path) -> dict[str, str]:
|
|
"""기계 폴더(826-2 · 한 대 한 파일) — {파일 이름: 글}."""
|
|
return {p.name: p.read_text(encoding="utf-8") for p in (copy / "기계").glob("*.json")}
|
|
|
|
|
|
def _rows(files: dict[str, str]) -> list[dict]:
|
|
return [json.loads(t) for n, t in sorted(files.items()) if not n.startswith("_")]
|
|
|
|
|
|
def _diff(before: str, after: str) -> list[str]:
|
|
return [
|
|
x
|
|
for x in difflib.unified_diff(before.splitlines(), after.splitlines(), lineterm="", n=0)
|
|
if x[:1] in "+-" and not x.startswith(("+++", "---"))
|
|
]
|
|
|
|
|
|
def test_새_기계_단위_출처_자동_계수_서버_재계산(copy: Path) -> None:
|
|
client = _app()
|
|
lists = client.get("/api/m01/machine/lists").json()
|
|
assert {"경유", "휘발유"} <= {f["value"] for f in lists["fuels"]}
|
|
assert (
|
|
any(j["value"] == "1048" for j in lists["jobs"]) and abs(lists["hour"] - 5 / 24) < 1e-12
|
|
) # 840-5
|
|
found = client.get("/api/m01/machine/find", params={"q": "굴착기 1.0㎥"}).json()["rows"]
|
|
seed = next(r for r in found if r["키"] == "EQ000022")
|
|
before = _files(copy)
|
|
version = client.get("/api/m01/machine/row", params={"key": "EQ000022"}).json()["version"]
|
|
values = {**seed["값"], "내용시간": 8000, "손료계수": 1, "상각비계수": 1} # 계수 = 틀린 값
|
|
row = {
|
|
"키": "",
|
|
"원문번호": "",
|
|
**{k: seed[k] for k in ("원문", "구분", "상세구분")},
|
|
"이름": "시험기계sub1",
|
|
"규격": "9.9㎥",
|
|
"단위": {},
|
|
"값": values,
|
|
"출처": {},
|
|
"비고": None,
|
|
}
|
|
_save(client, version, {"op": "add", "row": row})
|
|
after = _files(copy)
|
|
rows = _rows(after)
|
|
new = rows[-1]
|
|
assert new["이름"] == "시험기계sub1" and new["키"].startswith("EQ")
|
|
assert new["값"] | mc.coef(8000, 1250, 0.9, 0.7, 0.1) == new["값"] # 서버가 다시 셈
|
|
assert new["값"]["손료계수"] == 1125 + 875 + 496
|
|
assert set(new["단위"]) == set(new["값"]) == set(new["출처"])
|
|
assert new["단위"] == {
|
|
k: seed_unit for k, seed_unit in _units(before).items() if k in new["값"]
|
|
}
|
|
assert set(new["출처"].values()) == {"자체"}
|
|
assert _rows(before) == rows[:-1] # 다른 줄 그대로
|
|
assert {n: after[n] for n in before} == before # 다른 대 파일 그대로 · 새 대 파일 하나만
|
|
assert set(after) - set(before) == {f"{new['키']}.json"}
|
|
|
|
|
|
def _units(files: dict[str, str]) -> dict:
|
|
return json.loads(files["EQ000022.json"])["단위"]
|
|
|
|
|
|
def _edit(client: TestClient, key: str, change) -> tuple[dict, dict]:
|
|
got = client.get("/api/m01/machine/row", params={"key": key}).json()
|
|
row = got["row"]
|
|
_save(client, got["version"], {"op": "edit", "key": key, "row": change(row)})
|
|
now = client.get("/api/m01/machine/row", params={"key": key}).json()["row"]
|
|
return row, now
|
|
|
|
|
|
def test_고침_그_줄_그_칸만(copy: Path) -> None:
|
|
client = _app()
|
|
before = _files(copy)
|
|
row, now = _edit(client, "EQ000022", lambda r: {**r, "값": {**r["값"], "가격": 150000}})
|
|
assert now == {**row, "값": {**row["값"], "가격": 150000}}
|
|
after = _files(copy)
|
|
assert {n for n in after if after[n] != before.get(n)} == {"EQ000022.json"} # 그 대 파일만
|
|
lines = _diff(before["EQ000022.json"], after["EQ000022.json"])
|
|
assert len(lines) == 2 and "142069" in lines[0] and "150000" in lines[1] # 그 칸 한 줄만
|
|
|
|
|
|
def test_입력_같으면_저장_계수_그대로_바뀌면_다시_셈(copy: Path) -> None:
|
|
client = _app()
|
|
# 가격만 고침 + 화면이 계수를 틀리게 보내도 — 입력이 같으면 원문 표 값(정비비계수 777) 그대로
|
|
row, now = _edit(
|
|
client,
|
|
"EQ000347",
|
|
lambda r: {**r, "값": {**r["값"], "가격": 999, "정비비계수": 778, "손료계수": 1}},
|
|
)
|
|
assert now["값"] == {**row["값"], "가격": 999}
|
|
_, now = _edit(client, "EQ000347", lambda r: {**r, "값": {**r["값"], "내용시간": 10000}})
|
|
assert {k: now["값"][k] for k in mc.COEFS} == mc.coef(10000, 800, 0.9, 0.7, 0.1)
|
|
|
|
|
|
def test_기계_길은_시스템_관리자만() -> None:
|
|
for role in ("USER", "COMPANY_ADMIN"):
|
|
client = _app(role)
|
|
assert client.get("/api/m01/machine/lists").status_code == 403
|
|
assert client.get("/api/m01/machine/hourly", params={"keys": "EQ000022"}).status_code == 403
|
|
assert client.post("/api/m01/save", json={"files": []}).status_code == 403
|
|
|
|
|
|
def test_쓰는_곳_찾기(copy: Path, monkeypatch) -> None:
|
|
from M02_MasterTemplete import M02_Template_Layers as layers
|
|
|
|
monkeypatch.setattr(layers, "layer_files", lambda kind: []) # 공유 storage 안 읽음
|
|
uses = _app().get("/api/m01/machine/uses", params={"key": "EQ000022"}).json()["uses"]
|
|
assert any("GC000267" in u for u in uses)
|
|
assert _app().get("/api/m01/machine/uses", params={"key": "EQ999999"}).status_code == 404
|