fix(M02 실무자료): 361-1 지역만 다른 자재 = 전국 — 줄 글 지역 · (전국) 품목 · 없으면 키 없는 전국 자리표 · 고르기 창 지역 변형 접음 · 전국 되돌림 · 만들기 본뜬.전국자재
- 맞춤: 줄 글 지역 = 그 지역 변형(꼬리표 「지역 — 줄 글」) · 지역 없는 변형((전국) 품목 먼저) · 없으면(레미콘) 「전국」 자리표(바탕 이름(전국)-관급/사급 · 규격 · 단위 · 키 · 단가 없음 · 꼬리표 「전국 — 지역은 프로젝트에서」) · 옛 「못 가림 — N개 중 첫째」 없앰
- 후보 = 그 품목 변형 모두(`변형`) · 고르기 창 1위 / 전국 / 지역 변형(접힘) · 대상 꼴 `{전국: true}`
- 미리 셈 · 만들기: 전국 대상은 서버가 줄에서 다시 셈 · 일위대가 자재 칸이 M01 키만 받아 사전조합 `본뜬.전국자재` 에 적고 빠진 줄 까닭 · 장부엔 안 남김 · 만들기 몸 꼴에 `전국` 칸
- 시험: 전국 · 줄 글 지역 · (전국) 품목 · 고르기 지역 → 전국 되돌림 · 만들기 결과 · 화면 도우미
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
This commit is contained in:
@@ -148,6 +148,7 @@ let out0 = null;
|
||||
toTarget: [
|
||||
check.toTarget({ 호표: 46, 이름: "고화매트", 입력값: {}, 배: 2.3 }),
|
||||
check.toTarget({ 자재: "MT1", 이름: "레미콘", 입력값: {}, 배: 1, 몫: null }),
|
||||
check.toTarget({ 전국: true, 이름: "레미콘(전국)-관급", 입력값: {}, 배: 1, 몫: null }),
|
||||
],
|
||||
newest: [a(), b(), c()],
|
||||
dropped: (check.dropDraft(key), store.has(key)),
|
||||
@@ -200,6 +201,25 @@ let out0 = null;
|
||||
material: choose
|
||||
.candGroups({ 후보: [{ 자재: "M1" }, { 자재: "M2" }] }, true)
|
||||
.map((x) => x.제목),
|
||||
// 361-1 지역만 다른 자재 — 1위 전국 · 지역 변형 접힘 · 고르면 그 지역 · 전국 되돌림
|
||||
national: (() => {
|
||||
const all = [
|
||||
{ 전국: true, 이름: "레미콘(전국)-관급", 규격: "25-21-120", 단위: "㎥" },
|
||||
{ 자재: "M1", 이름: "레미콘(서울)-사급", 변형: true },
|
||||
{ 자재: "M2", 이름: "레미콘(관급)-강원도(강릉시)", 변형: true },
|
||||
];
|
||||
const g = choose.candGroups({ 후보: all }, true);
|
||||
const id = (c) => c.자재 ?? (c.전국 ? "전국" : "");
|
||||
return {
|
||||
groups: g.map((x) => [x.제목, x.접힘, x.후보.map(id)]),
|
||||
text: choose.candText(all[0], new Set()),
|
||||
region: choose.pickedTargets([{ c: all[2], 몫: null }]),
|
||||
back: choose.pickedTargets([{ c: all[0], 몫: null }]),
|
||||
hit: choose
|
||||
.candGroups({ 후보: [all[2], all[0], all[1]] }, true)
|
||||
.map((x) => [x.제목, x.후보.map(id)]),
|
||||
};
|
||||
})(),
|
||||
pick: [
|
||||
choose.canPickMaterial({ 갈래: "단가산출", 단가: { 재료비: 10 } }),
|
||||
choose.canPickMaterial({ 갈래: "단가산출", 단가: { 재료비: null } }),
|
||||
|
||||
@@ -146,6 +146,34 @@ def test_자재_세_칸_로직_자재_두_번_막음(client):
|
||||
assert any(m["까닭"] == hb.TWICE and m["차례"] == 0 for m in got["빠진줄"])
|
||||
|
||||
|
||||
def test_전국_자재_키_없이_본뜬에(client):
|
||||
"""361-1 — 「전국」 자재 = 자재 칸(M01 키만) 아니고 `본뜬.전국자재` · 이름 · 규격 · 단위 =
|
||||
서버가 줄에서 다시 셈(몸은 이름 칸 못 받음) · 빠진 줄 까닭 · 장부엔 안 남김 · 자리표 없는 줄 = 400."""
|
||||
assert _post(client).status_code == 200
|
||||
line = {"차례": 0, "대상": [{"전국": True, "배": 1}]} # 레미콘 별도계상 줄
|
||||
got = _build(client, 1, [line])
|
||||
mine = layers.personal_dir(7, 42)
|
||||
doc = _doc(mine, got["참조"]["키"])
|
||||
assert doc["자재"] == [] and not any(m.get("코드") for m in doc["자재"])
|
||||
qty = hs.read(mine, "HP-000001")["호표"][0]["줄"][0]["수량"]
|
||||
assert doc["본뜬"]["전국자재"] == [
|
||||
{
|
||||
"이름": "레미콘(전국)-관급",
|
||||
"규격": "25-21-120",
|
||||
"단위": "㎥",
|
||||
"수량": {"손": qty},
|
||||
"별도계상": "별도계상",
|
||||
"자재구분": "관급",
|
||||
}
|
||||
]
|
||||
assert [m["까닭"] for m in got["빠진줄"] if m["차례"] == 0] == [hb.NATIONAL]
|
||||
assert got["장부"]["씀"] == 0
|
||||
_build(client, 1, [{"차례": 1, "대상": [{"전국": True, "배": 1}]}], want=400) # 로직 줄
|
||||
both = [{"차례": 0, "대상": [{"전국": True, "자재": "MT004258", "배": 1}]}]
|
||||
_build(client, 1, both, want=400)
|
||||
_build(client, 1, [{"차례": 0, "대상": [{"전국": True, "입력값": {"x": 1}}]}], want=400)
|
||||
|
||||
|
||||
def test_몸_없음_재검사_400(client):
|
||||
assert _post(client).status_code == 200
|
||||
one = {"로직": "GF000101", "배": 1}
|
||||
|
||||
@@ -92,7 +92,9 @@ def test_끝까지_확인한_줄만_별칭(client):
|
||||
assert {p["로직"] for p in two} == {"GC000206", "GC000207"} # 다른 점 → 사용자가 체크
|
||||
body = body + [{"차례": rebar["차례"], "대상": two}]
|
||||
made = _build(client, "HP-000001", no, body)
|
||||
assert made["다시씀"] is False and made["장부"]["씀"] == len(body)
|
||||
national = [x for x in body if any(t.get("전국") for t in x.get("대상", []))] # 361-1
|
||||
assert national and all(x["대상"] == [{"전국": True, "배": 1}] for x in national)
|
||||
assert made["다시씀"] is False and made["장부"]["씀"] == len(body) - len(national)
|
||||
assert all(m["까닭"] for m in made["빠진줄"]) # 확인 안 함 · % 줄
|
||||
doc = _doc(made["참조"])
|
||||
logics = [x for x in doc["담은로직"] if x.get("로직")]
|
||||
|
||||
@@ -115,11 +115,25 @@ def test_새_상태_체크_제안(client):
|
||||
assert remicon["품"]["같음"]["콘크리트공"] and remicon["품"]["엑셀"]["인력"]["콘크리트공"]
|
||||
plywood = first["합판거푸집"]
|
||||
assert plywood["상태"] == "맞음" and set(plywood["갈리는칸"]) == {"측압", "폼타이규격"}
|
||||
mat = first["레미콘(25-21-120)"] # 358-2 — 「별도계상」 글 = 표시 · 지역 변형 = 첫째 + 꼬리표
|
||||
mat = first["레미콘(25-21-120)"] # 358-2 · 361-1 — 「별도계상」 표시 · 지역 변형만 = 전국
|
||||
assert mat["상태"] == "맞음" and mat["체크"] is True and "별도계상" in mat["꼬리표"]
|
||||
assert mat["대상"][0]["규격"].endswith("25-21-120") and "레미콘" in mat["대상"][0]["이름"]
|
||||
assert any(t.startswith("지역 · 관급/사급 못 가림") for t in mat["꼬리표"])
|
||||
assert all(c["규격"].endswith("25-21-120") for c in mat["후보"]) # 변형 = 고를 수 있게
|
||||
assert mat["대상"][0] | {"환산": None} == {
|
||||
"전국": True,
|
||||
"이름": "레미콘(전국)-관급",
|
||||
"규격": "25-21-120",
|
||||
"단위": "㎥",
|
||||
"입력값": {},
|
||||
"못읽음": [],
|
||||
"출처": {},
|
||||
"환산": None,
|
||||
"몫": None,
|
||||
"배": 1,
|
||||
} # 특정 지역 키 없음
|
||||
assert "전국 — 지역은 프로젝트에서" in mat["꼬리표"]
|
||||
assert not any("못 가림" in t for t in mat["꼬리표"])
|
||||
assert mat["후보"][0]["전국"] and len(mat["후보"]) == 129 # 전국 + 지역 변형 128(접어 고름)
|
||||
assert all(c["변형"] and c["자재"] for c in mat["후보"][1:])
|
||||
assert all(c["규격"].endswith("25-21-120") for c in mat["후보"])
|
||||
dig = _hopyo(client, 3)["구조물터파기(인력100%)"]
|
||||
assert dig["상태"] == "다른 점" and dig["체크"] is False
|
||||
assert dig["대상"][0]["로직"] == "GC000083" and len(dig["후보"]) == 5
|
||||
@@ -330,6 +344,61 @@ def test_북부청_자재_별도계상_맞음_늚(client):
|
||||
assert mortar["후보"][0]["자재"] == "MT004251"
|
||||
|
||||
|
||||
# ── 지역만 다른 자재 = 전국(361-1) ─────────────────────────────────────
|
||||
REMICON = {"이름": "레미콘(25-21-120)", "규격": "별도계상", "단위": "㎥", "갈래": "별도계상"}
|
||||
H_BEAM = {"이름": "H 형강", "규격": "SS275, 100*100*6*8mm", "단위": "Ton", "갈래": "자재"}
|
||||
|
||||
|
||||
def test_전국_지역_줄글_전국품목():
|
||||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||||
|
||||
rows, tags = hmat.sure(hmat.clean(REMICON)[0]) # 지역 낱말 없음 · (전국) 품목 없음
|
||||
assert rows[0]["one"] == {
|
||||
"전국": True,
|
||||
"이름": "레미콘(전국)-관급",
|
||||
"규격": "25-21-120",
|
||||
"단위": "㎥",
|
||||
}
|
||||
assert tags == [hmat.NATIONAL_TAG] and all("자재" in r["one"] for r in rows[1:])
|
||||
paid = {**REMICON, "규격": "", "갈래": "자재", "단가": {"재료비": 90000}}
|
||||
assert hmat.national(paid)["이름"] == "레미콘(전국)-사급" # 재료비 줄 = 사급 꼬리
|
||||
named = {**REMICON, "비고": "강원도 강릉시"} # 줄 글에 지역 = 그 지역(원문)
|
||||
rows, tags = hmat.sure(hmat.clean(named)[0])
|
||||
assert rows[0]["one"]["이름"] == "레미콘(관급)-강원도(강릉시)" and tags == [hmat.REGION_TAG]
|
||||
assert hmat.national(named) is None
|
||||
rows, tags = hmat.sure(hmat.clean(H_BEAM)[0]) # (전국) 품목 있음 = 그것
|
||||
assert rows[0]["one"]["자재"] == "MT001694" and tags == []
|
||||
assert hmat.national(H_BEAM) is None
|
||||
seoul = hmat.sure(hmat.clean({**H_BEAM, "비고": "서울"})[0])[0][0]["one"]
|
||||
assert seoul["이름"] == "H 형강(서울)"
|
||||
|
||||
|
||||
def test_전국_고르기_지역_되돌림(client):
|
||||
_ready(client)
|
||||
no, at = (
|
||||
1,
|
||||
next(
|
||||
x["차례"]
|
||||
for x in client.get(f"{URL.format('personal')}/HP-000001/1").json()["호표"]["줄"]
|
||||
if x["이름"] == "레미콘(25-21-120)"
|
||||
),
|
||||
)
|
||||
path = f"{URL.format('personal')}/HP-000001/{no}/guess"
|
||||
seoul = client.post(path, json={"차례": at, "대상": [{"자재": "MT004258", "배": 1}]})
|
||||
assert seoul.status_code == 200 and seoul.json()["대상"][0]["자재"] == "MT004258"
|
||||
back = client.post(path, json={"차례": at, "대상": [{"전국": True, "배": 1}]})
|
||||
assert back.status_code == 200, back.text
|
||||
got = back.json()["대상"][0]
|
||||
assert got["전국"] and "자재" not in got and got["이름"] == "레미콘(전국)-관급"
|
||||
other = next( # 전국 자리표 없는 줄(로직 줄) = 400
|
||||
x["차례"]
|
||||
for x in client.get(f"{URL.format('personal')}/HP-000001/1").json()["호표"]["줄"]
|
||||
if x["이름"] == "레미콘타설(장비사용)"
|
||||
)
|
||||
bad = client.post(path, json={"차례": other, "대상": [{"전국": True, "배": 1}]})
|
||||
assert bad.status_code == 400
|
||||
|
||||
|
||||
# ── 단계 대조 보임(358-3) — 목록 수 = 확인 창 수 · 연결 두 칸 · 목록 열 때 뒤 셈 ─────────────
|
||||
def test_목록_수_확인_창_수_한_규칙(client):
|
||||
_ready(client)
|
||||
|
||||
@@ -129,7 +129,11 @@ def test_확인_창_체크한_줄만_몸():
|
||||
assert got["sure"] == [1, 2] # [맞음 · 별칭 일괄 체크]
|
||||
assert got["compare"] == "콘크리트공 0.1091 → 0.1091 ✓ · 굴착기 0.29 → 0.31 ≠"
|
||||
assert got["compareMany"] == "굴착기 0.1594 → 0.0889 ≠" # 같은 장비 여러 줄 = 합쳐 한 줄
|
||||
assert got["toTarget"] == [{"호표": 46, "배": 2.3}, {"자재": "MT1", "배": 1}]
|
||||
assert got["toTarget"] == [
|
||||
{"호표": 46, "배": 2.3},
|
||||
{"자재": "MT1", "배": 1},
|
||||
{"전국": True, "배": 1}, # 361-1 — 키 없음 · 이름은 서버가
|
||||
]
|
||||
assert got["newest"] == [False, True, True] # 같은 줄 늦은 응답 버림 · 다른 줄은 따로
|
||||
assert got["dropped"] is False # 만들기 성공 = 캐시 지움
|
||||
|
||||
@@ -145,6 +149,16 @@ def test_고르기_창_도우미():
|
||||
]
|
||||
assert got["groups"]["text"][:2] == ["되메우기 · A · 산림", "되메우기 · C · 건설"]
|
||||
assert got["groups"]["material"] == ["1위", "그 밖 후보"]
|
||||
nat = got["groups"]["national"] # 361-1
|
||||
assert nat["groups"] == [["1위", False, ["전국"]], ["지역 변형", True, ["M1", "M2"]]]
|
||||
assert nat["text"] == "레미콘(전국)-관급 25-21-120 [㎥] · 키 없음"
|
||||
assert nat["region"] == [{"자재": "M2", "입력값": {}, "배": 1, "몫": None}]
|
||||
assert nat["back"] == [{"전국": True, "입력값": {}, "배": 1, "몫": None}]
|
||||
assert nat["hit"] == [ # 줄 글 지역 = 1위 · 전국 = 되돌릴 자리 · 남은 변형 접힘
|
||||
["1위", ["M2"]],
|
||||
["전국", ["전국"]],
|
||||
["지역 변형", ["M1"]],
|
||||
]
|
||||
assert got["groups"]["pick"] == [True, False, True] # 재료비 있는 단가산출 줄도 자재 검색
|
||||
assert got["picked"] == [
|
||||
{"로직": "GC000206", "입력값": {}, "배": 1, "몫": None},
|
||||
|
||||
Reference in New Issue
Block a user