diff --git a/M02_MasterTemplete/M02_Doc_Sk_Path.py b/M02_MasterTemplete/M02_Doc_Sk_Path.py index a7cd41b01..0b0da4de6 100644 --- a/M02_MasterTemplete/M02_Doc_Sk_Path.py +++ b/M02_MasterTemplete/M02_Doc_Sk_Path.py @@ -21,6 +21,7 @@ from M02_MasterTemplete import M02_Doc_Import as dim from M02_MasterTemplete import M02_Doc_Screen as screen from M02_MasterTemplete import M02_Doc_Sk as dsk from M02_MasterTemplete import M02_MasterTemplete_Store as store +from M02_MasterTemplete import M02_MyMachine as mym from M02_MasterTemplete import M02_Sangeun_Copy as skc from M02_MasterTemplete import M02_Sangeun_Doc as sd @@ -128,11 +129,22 @@ def edited(copy: dict, body: dict, key: str, sk: dict | None = None) -> dict: *(c for d in out.get("끊음", {}).values() for c in dc.calls(d)), ] if not dim.is_imported(sk): + out |= _mine(out) out["부름"] = dc.closure(dc.over(out), calls, copy.get("부름")) out["따로"] = True return out +def _mine(copy: dict) -> dict: + """줄 쌓기에서 고른 내 기계(기계='개인 EQP…') 가운데 값 사본에 없는 것만 지금 값으로 더함 + (821-3) — 그 뒤 내 기계를 고쳐도 금액 그대로 + 「단가새값」 · [단가 갱신] 으로 반영 · + 지워도 마지막 값.""" + keys = mym.keys_in(copy.get("문서")) + if not keys or "로직" not in copy: + return {} + return skc.grow(dc.values_of(copy), skc.snap([], keys)) + + def preview(key: str, body: dict) -> dict[str, Any]: """미리 셈(저장 없음) — {셈, 전, 후, 바뀐자원?, 버림?}.""" doc = _file(key)["문서"] diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts index f6d2d42c2..df39ccbcc 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts @@ -158,36 +158,34 @@ export function buildSide(opt: SideOptions): SideHandle { }); const sangeunSec = section(L("M02_SangeunTitle"), sangeun.body); // 내 기계 · 내 품목(821-3) — 자기 개인 층 · 펼치면 가운데 화면(목록 · 양식) - const hint = (): HTMLElement => createEmpty({ text: mt("Own_Note"), small: true }); - const myMachSec = openOnExpand(section(mt("Title"), hint()), async () => { + const hint = (key: "Side_Hint" | "Items_Side_Hint"): HTMLElement => + createEmpty({ text: mt(key), small: true }); + const myMachSec = openOnExpand(section(mt("Title"), hint("Side_Hint")), async () => { if (!(await opt.confirmLeave())) return; let mark = (_d: boolean): void => {}; const node = buildMyMachineView({ owner: null, setDirty: (d) => mark(d) }); mark = opt.showView(mt("Title"), node); }); myMachSec.dataset.myMachines = ""; - const myItemsSec = openOnExpand( - section(mt("Items_Title"), createEmpty({ text: mt("Items_Note"), small: true })), - async () => { - if (!(await opt.confirmLeave())) return; - const node = buildMyItemsView({ - owner: null, - openSk: async (u) => { - if (!(await opt.confirmLeave())) return; - opt.onOpen({ - layer: "personal", - owner: null, - canWrite: true, - projectId: null, - kind: "sangeun", - name: u.키, - label: `${u.키} ${u.이름}`.trim(), - }); - }, - }); - opt.showView(mt("Items_Title"), node); - }, - ); + const myItemsSec = openOnExpand(section(mt("Items_Title"), hint("Items_Side_Hint")), async () => { + if (!(await opt.confirmLeave())) return; + const node = buildMyItemsView({ + owner: null, + openSk: async (u) => { + if (!(await opt.confirmLeave())) return; + opt.onOpen({ + layer: "personal", + owner: null, + canWrite: true, + projectId: null, + kind: "sangeun", + name: u.키, + label: `${u.키} ${u.이름}`.trim(), + }); + }, + }); + opt.showView(mt("Items_Title"), node); + }); // 실무자료로 일위대가를 만들었음(확인 창 · 엑셀 보기 창) → 목록 다시 · 이 패널이 내려갔으면 듣기 끝 const onBuilt = (): void => precombo.body.isConnected diff --git a/M02_MasterTemplete/M02_MyMachine_Form.ts b/M02_MasterTemplete/M02_MyMachine_Form.ts index 454bd749a..c422dc61d 100644 --- a/M02_MasterTemplete/M02_MyMachine_Form.ts +++ b/M02_MasterTemplete/M02_MyMachine_Form.ts @@ -117,6 +117,7 @@ export async function mountMyMachineForm( checkSame(); }, onChange: () => { + notice.replaceChildren(); // 지난 저장 막힘 글 지움(다시 [저장] 때 새로) opt.onDirty?.(dirty()); checkSame(); }, diff --git a/M02_MasterTemplete/M02_MyMachine_Look.py b/M02_MasterTemplete/M02_MyMachine_Look.py index fd6c12619..e288f4955 100644 --- a/M02_MasterTemplete/M02_MyMachine_Look.py +++ b/M02_MasterTemplete/M02_MyMachine_Look.py @@ -52,7 +52,7 @@ def find(q: str, limit: int = 30) -> list[dict]: ] exact = (m01.loaded()[1].numbers.get("EQ") or {}).get(q.strip()) head = [exact] if exact is not None and not mym.mine(exact.get("키")) else [] - m01_rows = [json.loads(json.dumps(r, default=float)) for r in head] + _m01m().find(q, limit) + m01_rows = [*head, *_m01m().find(q, limit)] seen = set() for r in m01_rows: if r["키"] in seen: @@ -60,7 +60,7 @@ def find(q: str, limit: int = 30) -> list[dict]: seen.add(r["키"]) brief = {k: r[k] for k in ("키", "원문번호", "이름", "규격", "값") if k in r} out.append({**brief, "구분": r.get("상세구분") or r.get("구분") or "", "출처": "M01"}) - return out[:limit] + return json.loads(json.dumps(out[:limit], default=_m01m()._plain)) # Decimal → 수(글 아님) def _items_of(doc: dict) -> list[dict]: diff --git a/M02_MasterTemplete/M02_MyMachine_Text.ts b/M02_MasterTemplete/M02_MyMachine_Text.ts index 1423918ce..19b1e1732 100644 --- a/M02_MasterTemplete/M02_MyMachine_Text.ts +++ b/M02_MasterTemplete/M02_MyMachine_Text.ts @@ -38,6 +38,8 @@ const TEXT = { No_Uses: "쓰는 단가산출 없음", Leave_Ask: "저장 안 한 고침을 버리고 닫을까?", Mine: "내 기계", + Side_Hint: "펼치면 가운데에 목록 · 양식", + Items_Side_Hint: "펼치면 가운데에 모아 보기(읽기)", Won: "원", Labor: "노", Material: "재", diff --git a/resources/tester/test_821_3_my_machine_screen.py b/resources/tester/test_821_3_my_machine_screen.py index 4a2f26f6e..10039bce0 100644 --- a/resources/tester/test_821_3_my_machine_screen.py +++ b/resources/tester/test_821_3_my_machine_screen.py @@ -55,7 +55,7 @@ def test_find_mine_first_and_number(world): no = str(m01.loaded()[1].get(DIG)["원문번호"]) first = _at(me, look.find, no)[0] assert first["키"] == DIG and first["원문번호"] == no and first["출처"] == "M01" - assert first["값"]["가격"] and first["구분"] + assert isinstance(first["값"]["가격"], int | float) and first["구분"] # 수(글이면 미리보기 0) dig = [r for r in _at(me, look.find, "굴착기") if r["출처"] == "M01"] assert dig and len({r["키"] for r in dig}) == len(dig) # 겹침 없음 @@ -98,3 +98,47 @@ def test_personal_only(world): look.get_items(layer, owner, user) assert e.value.status_code == code assert look.get_items("personal", None, user) == {"rows": []} + + +def _stack_doc(key, no): + """줄 쌓기 기계 줄 하나(화면이 저장하는 꼴) — 기계='개인 …'.""" + var = {"기호": "P", "아래": "새", "위": "", "이름": NAME, "단위": "", + "연결": {"갈래": "단가산출", "키": "GC000268", "입력": {"기계": f"'{no}'"}}} # fmt: skip + line = {"이름": NAME, "단위": "hr", "비목": "묶음", "수량": "1", "단가": "P_새"} + return {"꼴": "수식문서", "판": 1, "키": key, "이름": "sub1임시", "결과단위": "원", + "변수": [var], "식": [], "금액": [line]} # fmt: skip + + +def test_stack_line_copy(world): + """⑤ 줄 쌓기로 고른 내 기계 — 미리 셈 됨 · 저장 때 값 사본에 박힘 → + 고쳐도 금액 그대로 + 단가새값 → [단가 갱신] 바뀐 자원 1 → 지워도 마지막 값 + 지운키.""" + from M02_MasterTemplete import M02_Doc_Sk_Path as path + from M02_MasterTemplete import M02_Labor_Ref as refs + + me = world["personal"] + made = _at(me, mym.create, {**_mine_row(), "값": {**_mine_row()["값"], "주연료": 2, + "주연료종류": "경유", "조종원": 1, "조종원직종": "1048"}}) # fmt: skip + key, no, was = made["줄"]["키"], made["줄"]["원문번호"], made["시간당"]["계"] + sk = _at( + me, + lambda: refs.create("sangeun", {"이름": "sub1임시", "단위": "시간", "더한줄": []})["이름"], + ) + doc = _stack_doc(sk, no) + look_ = _at(me, path.preview, sk, {"문서": doc}) + assert look_["셈"]["계"] == was # 사본에 아직 없는 내 기계도 셈 + 판 = _at(me, path.read, sk)["판"] + _at(me, path.save, sk, {"문서": doc, "판": 판}) + copy = _at(me, lambda: store.read("sangeun", sk)["문서"]["수식"]) + assert key in [r["키"] for r in copy["단가"]] # 값 사본에 박힘 + _at(me, mym.update, key, {**made["줄"], "값": {**made["줄"]["값"], "가격": 3000}}) + now = _at(me, path.read, sk) + assert now["셈"]["계"] == was and now["사본"]["단가새값"] is True + job = _at(me, path.preview, sk, {"할일": "단가갱신"}) + assert [x["키"] for x in job["바뀐자원"]] == [key] and job["후"]["계"] > was + _at(me, mym.delete, key) + gone = _at(me, path.read, sk) + assert gone["셈"]["계"] == was and gone["사본"]["지운키"] == [key] + + +def _mine_row(): + return {"이름": NAME, "규격": "1㎾", "방식": "바로", "값": {"가격": 1000, "손료계수": 2000}}