diff --git a/M02_MasterTemplete/M02_Hopyo_Build.py b/M02_MasterTemplete/M02_Hopyo_Build.py index 7da2f5d4d..615dda78f 100644 --- a/M02_MasterTemplete/M02_Hopyo_Build.py +++ b/M02_MasterTemplete/M02_Hopyo_Build.py @@ -315,7 +315,10 @@ class Builder: lines.append({"참조": ref, "별칭": alias, "메모": memo, "수량": {"손": _num(qty)}}) return row = {"로직": one["로직"], "입력값": one["입력값"], "메모": memo} - if (kept := sklink.kept(self, line, one, qty, alias, memo)) is not None: + kept, why = sklink.kept(self, line, one, qty, alias, memo) + if why: + left.append(sklink.left(line, why)) + if kept is not None: lines.append(kept) return if one["로직"] not in used: diff --git a/M02_MasterTemplete/M02_Hopyo_Sangeun.py b/M02_MasterTemplete/M02_Hopyo_Sangeun.py index 42455f336..ce7cfd995 100644 --- a/M02_MasterTemplete/M02_Hopyo_Sangeun.py +++ b/M02_MasterTemplete/M02_Hopyo_Sangeun.py @@ -7,7 +7,9 @@ 고름 대기 = SK 안 만듦(바탕 없는 SK 로 몰래 채우지 않음) · 후보 다섯(성격 다른 단위 = `계수` 칸 뜻) → 사용자가 고른 기초 `바탕 {번: {로직?, 입력값?, 환산?, 잇기?}}` 로 다시 부르면 그 기초에 견줘 SK(이미 있으면 그 키에 덮음) · 잇기 `[{id, 노임|기계|자재: 키} | {id, 뺌}]` = 못옮김 → SK 더한줄 · 남은 못옮김 = 응답 - `만듦[].못옮김` · 본뜬 그대로(그 SK 를 쓰는 일위대가 만들기 = 남은 줄 409 · `M02_Hopyo_SkLink`). + `만듦[].못옮김` · 본뜬 그대로(그 SK 를 쓰는 일위대가 만들기 = 남은 줄 409 · `M02_Hopyo_SkLink`) · + 그대로 SK 의 못옮김 = 응답 `남은못옮김 [{번, 이름, 키, 못옮김}]` · 잇기만 보냈는데 1위 기초가 고름 대기면 + 그 SK 바탕(로직 · 입력값 · 환산)으로 다시 셈(729-5). SK = 이름 블록 이름(없으면 줄 품명) · 단위 = 줄 단위(없으면 블록 · 「식」) · 구분 「실무자료」 · 상세구분 = 파일 · 본뜬 = {층 「실무자료」, 키 「HP-###### 단산N」, 파일, 원본, 확인, 갈래, 엑셀품, 못옮김} · 같은 본뜬(키 · 원본)이 이 층에 있으면 그대로(겹침 없음) · 만드는 층 = 부르는 층. @@ -136,6 +138,20 @@ def _doc(line: dict, block: dict, no: int, hp: dict, plan: dict, sure: dict) -> } +def _old_base(folder: Path, key: str) -> dict[str, Any]: + """이미 있는 SK 의 바탕 고름 `{로직, 입력값, 환산?}` · `본뜬` — 읽기 실패 = 빈 것.""" + try: + doc = json.loads((Path(folder) / KIND / f"{key}.json").read_bytes()) + except (OSError, ValueError): + return {} + base = {"로직": (doc.get("바탕") or {}).get("로직"), "입력값": doc.get("입력값") or {}} + return { + **base, + **({"환산": doc["환산"]} if doc.get("환산") else {}), + "본뜬": doc.get("본뜬") or {}, + } + + def _alias(line: dict, plan: dict) -> dict[str, Any]: origin = {"품명": line.get("이름"), "규격": line.get("규격"), "단위": line.get("단위")} clauses = (line.get("근거") or {}).get("출처") or [] @@ -182,7 +198,8 @@ class Maker: def build(folder: Path, key: str, who: Any = None, picks: Any = None) -> dict[str, Any]: """[단가산출 만들기] — 산근 블록마다 기초 · SK · 고름 대기 → `{블록, 만듦 [{번, 이름, 키, 갈래, 못옮김, - 환산?}], 그대로, 기초, SK, 고름대기 [{번, 이름, 단위, 까닭, 후보}], 별칭}` · `picks` = 고름.""" + 환산?}], 그대로, 남은못옮김 [{번, 이름, 키, 못옮김}], 기초, SK, 고름대기 [{번, 이름, 단위, 까닭, 후보}], + 별칭}` · `picks` = 고름.""" chosen = _picks(picks) maker = Maker(folder, key, who) bad = sorted(set(chosen) - set(maker.first)) @@ -193,22 +210,31 @@ def build(folder: Path, key: str, who: Any = None, picks: Any = None) -> dict[st raise _bad("맞춤 셈 중 — 끝나면 다시 누름", 409) have = made(folder, key, maker.here) out: dict[str, Any] = {"블록": len(maker.first), "만듦": [], "그대로": 0, "고름대기": []} + out["남은못옮김"] = [] out.update({skpatch.BASE: 0, skpatch.SK: 0}) books, alias = ledger.books(folder), {} for no in sorted(maker.first): old = have.get(mark(key, no)) + line = maker.first[no] + name = maker.blocks[no].get("이름") or line.get("이름") if old and no not in chosen: out["그대로"] += 1 + lost = _old_base(folder, old).get("본뜬", {}).get("못옮김") + if lost: + out["남은못옮김"].append({"번": no, "이름": name, "키": old, "못옮김": lost}) continue plan = maker.plan(no, chosen.get(no)) - line = maker.first[no] + if plan["갈래"] == skpatch.WAIT and old and not chosen[no].get("로직"): + base = _old_base(folder, old) # 고른 기초로 만든 SK 의 잇기 = 그 바탕 그대로 + if base.get("로직"): + pick = {k: v for k, v in base.items() if k != "본뜬"} + plan = maker.plan(no, {**pick, **chosen[no]}) if plan["갈래"] == skpatch.WAIT: head = {"번": no, "이름": maker.blocks[no].get("이름") or line.get("이름")} out["고름대기"].append({**head, "단위": line.get("단위"), **plan}) continue got = maker.write(no, plan, old) out[plan["갈래"]] += 1 - name = maker.blocks[no].get("이름") or line.get("이름") made_ = {"번": no, "이름": name, "키": got, "갈래": plan["갈래"], "못옮김": plan["못옮김"]} out["만듦"].append({**made_, **({"환산": plan["환산"]} if plan.get("환산") else {})}) found = ledger.recall(line, folder, books) diff --git a/M02_MasterTemplete/M02_Hopyo_SkLink.py b/M02_MasterTemplete/M02_Hopyo_SkLink.py index 8977178de..16603becb 100644 --- a/M02_MasterTemplete/M02_Hopyo_SkLink.py +++ b/M02_MasterTemplete/M02_Hopyo_SkLink.py @@ -3,7 +3,7 @@ `M02_Hopyo_Build.Builder` 가 부름(`b` = 그 만들기) — 확인 안 한 단가산출 줄 = 그 블록 SK 참조(이 층 · 같은 원본 · 없으면 `M02_Hopyo_Sangeun.Maker` 로 그 블록만 만듦 · 미리 보기 = 키만 · 맞춤 셈 전 · 고름 대기 · 단위 다름 = 못 이음) · -확인한 로직 줄이 차이 SK 의 바탕 = 그 SK 참조(엑셀 차이 보존 · 별칭 짝 = 기초까지만) · +확인한 로직 줄이 차이 SK 의 바탕 = 그 SK 참조(엑셀 차이 보존 · 별칭 짝 = 기초까지만 · 못옮김 남으면 남은 줄 · 729-5) · 남은 줄 = 엑셀 줄 · 갈래 · 까닭 · 후보(단가산출 고름 대기) · 못옮김 남은 SK = 못 이음(`단산` 번 · `못옮김` · 잇기 = [단가산출 만들기] `바탕[번].잇기` · 729-4). """ @@ -84,8 +84,7 @@ def sk_line(b: Any, line: dict[str, Any], create: bool) -> tuple[dict | None, di doc = sk_doc(b, key) lost = (doc.get("본뜬") or {}).get("못옮김") if lost is None else lost if lost: # 연결 안 되면 못 만듦(내역 20-4) - why = f"단가산출 못옮김 {len(lost)} — [단가산출 만들기]에서 이음" - return None, {"까닭": why, "단산": no, "SK": key, "못옮김": lost} + return None, _lost(no, key, lost) unit = doc.get("단위") or line.get("단위") if mm_unit(unit) != mm_unit(line.get("단위")): return None, {"까닭": f"단가산출 단위 「{unit}」 다름"} @@ -94,17 +93,26 @@ def sk_line(b: Any, line: dict[str, Any], create: bool) -> tuple[dict | None, di return _ref(key, line, alias, f"실무 {qty}{line.get('단위') or ''} · 단산{no}", qty), None -def kept(b: Any, line: dict, one: dict, qty: Decimal, alias: Any, memo: str) -> dict | None: - """확인한 로직 줄이 그 블록 차이 SK 의 바탕 = 그 SK 참조 · 아니면 None.""" +def _lost(no: int, key: str, lost: list) -> dict[str, Any]: + why = f"단가산출 못옮김 {len(lost)} — [단가산출 만들기]에서 이음" + return {"까닭": why, "단산": no, "SK": key, "못옮김": lost} + + +def kept( + b: Any, line: dict, one: dict, qty: Decimal, alias: Any, memo: str +) -> tuple[dict | None, dict | None]: + """확인한 로직 줄이 그 블록 차이 SK 의 바탕 = (그 SK 참조, 못옮김 남으면 까닭) · 아니면 (None, None).""" ref = line.get("참조") or {} if ref.get("종류") != "단산" or one.get("몫") is not None: - return None - key = sks(b).get(sangeun.mark(b.key, int(ref.get("번") or 0))) + return None, None + no = int(ref.get("번") or 0) + key = sks(b).get(sangeun.mark(b.key, no)) doc = sk_doc(b, key) if key else {} base = (doc.get("바탕") or {}).get("로직") if (doc.get("본뜬") or {}).get("갈래") != sangeun.skpatch.SK or base != one["로직"]: - return None - return _ref(key, line, alias, f"{memo} · 차이 SK", qty) + return None, None + lost = doc["본뜬"].get("못옮김") # sk_line 과 같은 규칙 — 차이 잃지 않음(내역 20-4) + return _ref(key, line, alias, f"{memo} · 차이 SK", qty), _lost(no, key, lost) if lost else None def left(line: dict[str, Any], why: dict | None) -> dict[str, Any]: diff --git a/resources/tester/test_729_hopyo_sk_link.py b/resources/tester/test_729_hopyo_sk_link.py index 2bd40edc1..84e0eaec2 100644 --- a/resources/tester/test_729_hopyo_sk_link.py +++ b/resources/tester/test_729_hopyo_sk_link.py @@ -186,6 +186,46 @@ def test_단위_성격_다름_계수_못옮김_잇기_남으면_일위대가_막 assert master_hash() == before +def test_별칭_체크_줄_못옮김_SK_남은줄_그대로_SK_남은못옮김(client): + """729-5 — 확인한 로직 줄이 못옮김 남은 차이 SK 의 바탕이면 남은 줄(차이 잃지 않음) · 만들기 409 · + 그대로 SK 의 못옮김 = 응답 남은못옮김 · 잇기 뒤 사라짐.""" + before = master_hash() + folder = layers.personal_dir(7, 42) + key = _post(client).json()["키"] + url = f"{URL.format('personal')}/{key}/sangeun" + fit.run_job(folder, key) + made = {x["번"]: x for x in client.post(url, json={}).json()["만듦"]} + sk = made[60] # 제27호표 2째 줄 와이어메시바닥깔기 — SK(바탕 GC000665) · 엑셀만 와이어메쉬 + assert sk["갈래"] == "SK" and len(sk["못옮김"]) == 1 + check = {"차례": 2, "대상": [{"로직": "GC000665", "배": 1}]} # 별칭 자동 체크와 같은 꼴 + body = {"층": "personal", "키": key, "번": 27, "줄": [check]} + + def mine(rows: list) -> list: + return [x for x in rows if x["차례"] == 2] + + left = client.post(LEFT, json=body).json()["남은줄"] + assert [(x["단산"], x["SK"], x["못옮김"]) for x in mine(left)] == [(60, sk["키"], sk["못옮김"])] + res = client.post(BUILD, json=body) + assert res.status_code == 409 and mine(res.json()["detail"]["남은줄"]) == mine(left) + # 그대로 SK 의 못옮김 = 응답 남은못옮김(만듦[].못옮김 과 같은 꼴) + again = client.post(url, json={}).json() + rest = {x["번"]: x for x in again["남은못옮김"]} + assert again["만듦"] == [] and set(rest) == {n for n, x in made.items() if x["못옮김"]} + assert rest[60] == {"번": 60, "이름": sk["이름"], "키": sk["키"], "못옮김": sk["못옮김"]} + cut = {"id": sk["못옮김"][0]["id"], "자재": "MT000010"} # 이음 = SK 더한줄(차이 남음) + linked = client.post(url, json={"바탕": {"60": {"잇기": [cut]}}}).json() + assert [(x["번"], x["키"], x["못옮김"]) for x in linked["만듦"]] == [(60, sk["키"], [])] + assert 60 not in {x["번"] for x in client.post(url, json={}).json()["남은못옮김"]} + left = client.post(LEFT, json=body).json()["남은줄"] + assert mine(left) == [] + picks = [{"차례": x["차례"], "뺌": "우리에 없음"} for x in left] + ok = client.post(BUILD, json={**body, "줄": [check, *picks]}) + assert ok.status_code == 200, ok.text + doc = json.loads((folder / "precombo" / f"{ok.json()['참조']['키']}.json").read_bytes()) + assert any((x.get("참조") or {}).get("키") == sk["키"] for x in doc["담은로직"]) # 차이 SK 참조 + assert master_hash() == before + + def _eq(one: dict) -> str: return str((one.get("입력값") or {}).get("기계") or "")