fix(821-3): 줄 쌓기로 고른 내 기계 — 값 사본에 박음(고친 뒤 금액 그대로 + 새 값 · 갱신 · 지워짐) · 찾기 값 수로

- M02_Doc_Sk_Path.edited: 문서의 기계='개인 EQP…' 가운데 값 사본에 없는 것만 지금 값으로 더함(미리 셈 · 저장)
- M02_MyMachine_Look.find: Decimal → 수(글이면 양식 미리보기 0)
- 양식 막힘 글은 고치면 지움 · 좌측 칸 안내 짧게
- 시험 test_821_3 ⑤ 줄 쌓기 내 기계 사본 · 새값 · 갱신 · 지운 뒤 마지막 값

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 14:43:58 +09:00
co-authored by Claude Opus 5.5
parent 9b5275a78e
commit 804472d400
6 changed files with 84 additions and 27 deletions
+12
View File
@@ -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)["문서"]
@@ -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
+1
View File
@@ -117,6 +117,7 @@ export async function mountMyMachineForm(
checkSame();
},
onChange: () => {
notice.replaceChildren(); // 지난 저장 막힘 글 지움(다시 [저장] 때 새로)
opt.onDirty?.(dirty());
checkSame();
},
+2 -2
View File
@@ -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]:
+2
View File
@@ -38,6 +38,8 @@ const TEXT = {
No_Uses: "쓰는 단가산출 없음",
Leave_Ask: "저장 안 한 고침을 버리고 닫을까?",
Mine: "내 기계",
Side_Hint: "펼치면 가운데에 목록 · 양식",
Items_Side_Hint: "펼치면 가운데에 모아 보기(읽기)",
Won: "원",
Labor: "노",
Material: "재",
@@ -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}}