From bb63ce17426937e8f461c1c7461b21b57c8056fc Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 19:45:11 +0900 Subject: [PATCH] =?UTF-8?q?feat(826-2):=20=EB=82=B4=20=EA=B8=B0=EA=B3=84?= =?UTF-8?q?=20=3D=20=EC=B8=B5=20=ED=8F=B4=EB=8D=94=20=EB=82=B4=EA=B8=B0?= =?UTF-8?q?=EA=B3=84/=20=ED=95=9C=20=EB=8C=80=20=ED=95=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 층 내기계.json {줄, 다음} → 내기계/<키>.json + _다음.json · 옛 파일은 읽을 때 한 번 옮기고 지움(내용 · 다음 번호 같음) - 만들기 = 번호 먼저 · 대 파일 하나 · 고침 = 그 대 파일만 · 지움 = 그 대 파일 지움 · 셈 색인 기억 = 폴더 자국 Co-Authored-By: Claude Opus 5.5 --- M02_MasterTemplete/M02_MyMachine.py | 70 ++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/M02_MasterTemplete/M02_MyMachine.py b/M02_MasterTemplete/M02_MyMachine.py index 1187be642..d09a85d72 100644 --- a/M02_MasterTemplete/M02_MyMachine.py +++ b/M02_MasterTemplete/M02_MyMachine.py @@ -1,6 +1,7 @@ """M02 내 기계(PLAN 821-1 · 요구 795 · 요구서_796 3 · 7 장) — 개인 층 기계 줄 목록 · 서버 단독. -저장 = 층 뿌리 `내기계.json` {줄: [줄], 다음: 수} — 줄 = M01 기계 줄 꼴 +저장 = 층 뿌리 폴더 `내기계/` — 한 대 한 파일 `<키>.json`(줄) + `_다음.json` {다음: 수}(826-2 · + 옛 `내기계.json` {줄, 다음} 은 읽을 때 한 번 옮기고 지움) — 줄 = M01 기계 줄 꼴 {키 `EQP{사용자}-####`, 원문번호 「개인 {키}」, 원문 · 구분 · 출처 = 개인, 상세구분 · 이름, 규격, 단위, 값, 비고, 방식 바로|품셈|직접, 시간당?(직접 {노무비, 재료비, 경비}), 넣은날, 고친날, 본뜬?} 값 칸 이름 = M01 기계 줄과 같음 · 바로 = 손료계수 한 칸 · 품셈 = 비율 다섯 → 계수 넷(저장 때 서버가 @@ -20,6 +21,7 @@ GC000275) · 연료만 GC000272(손료계수 한 칸 GC001198) · 조종원만 G from __future__ import annotations import json +import os import re from datetime import datetime from decimal import Decimal @@ -30,7 +32,7 @@ from common_util.common_util_json import atomic_write_json from M01_MasterData import M01_MasterData_Store as m01 from M02_MasterTemplete import M02_MasterTemplete_Store as store -FILE = "내기계.json" +FILE, DIR, NEXT = "내기계.json", "내기계", "_다음.json" # FILE = 옛 한 파일(옮기기만) PREFIX, NO = "EQP", "개인 " FIND = re.compile(rf"{NO}({PREFIX}\d+-\d+)") # 문서 안 기계 입력 「개인 EQP…」 EASY, BOOK, DIRECT = "바로", "품셈", "직접" @@ -63,17 +65,51 @@ def _bad(text: str, status: int = 400) -> store.StoreError: # ── 저장 ────────────────────────────────────────────────────────────── -def _path(folder: Path | None = None) -> Path: - return Path(folder or store.root()) / FILE +def _dir(folder: Path | None = None) -> Path: + return Path(folder or store.root()) / DIR + + +def _load(path: Path) -> Any: + try: + return json.loads(path.read_bytes()) + except (OSError, ValueError): + return {} + + +def _order(path: Path) -> tuple: + """넣은 차례 = 키 끝 번호 차례(「-0009」 다음 「-0010」).""" + head, _, n = path.stem.rpartition("-") + return (head, int(n) if n.isdigit() else 0, path.stem) + + +def _move(folder: Path | None = None) -> None: + """옛 한 파일 `내기계.json` → 폴더(한 번 · 내용 같음 · 옛 파일 지움).""" + old = Path(folder or store.root()) / FILE + if not old.is_file(): + return + got = _load(old) + base = _dir(folder) + for row in got.get("줄") or []: + if isinstance(row, dict) and mine(row.get("키")): + atomic_write_json(base / f"{row['키']}.json", row) + if not (base / NEXT).is_file(): + atomic_write_json(base / NEXT, {"다음": int(got.get("다음") or 1)}) + old.unlink() + + +def _put(row: dict) -> None: + atomic_write_json(_dir() / f"{row['키']}.json", row) def _read(folder: Path | None = None) -> dict[str, Any]: - try: - got = json.loads(_path(folder).read_bytes()) - except (OSError, ValueError): - got = {} - rows = [r for r in got.get("줄") or [] if isinstance(r, dict) and mine(r.get("키"))] - return {"줄": rows, "다음": int(got.get("다음") or 1)} + _move(folder) + base = _dir(folder) + rows = [] + for path in sorted(base.glob(f"{PREFIX}*.json"), key=_order): + row = _load(path) + if isinstance(row, dict) and mine(row.get("키")) and path.stem == row["키"]: + rows.append(row) + return {"줄": rows, "다음": int(_load(base / NEXT).get("다음") or 1)} def rows(folder: Path | None = None) -> list[dict]: @@ -103,12 +139,13 @@ def whole(base: Any = None) -> Any: from M02_MasterTemplete import M02_Sangeun_Copy as skc base = m01.loaded()[1] if base is None else base - path = _path() + _move() + path = _dir() try: - st = path.stat() + with os.scandir(path) as it: # 폴더 자국 — 대마다 (이름, 시각, 크기) + sig = tuple(sorted((e.name, e.stat().st_mtime_ns, e.stat().st_size) for e in it)) except OSError: return base - sig = (st.st_mtime_ns, st.st_size) got = _MEMO.get(str(path)) if got and got[0] == sig and got[1] is base: return got[2] @@ -309,7 +346,8 @@ def create(raw: Any = None, base: str | None = None) -> dict[str, Any]: when = _now() row = {"키": key, "원문번호": f"{NO}{key}", "원문": "개인", "구분": "개인", "상세구분": body["이름"], **body, "넣은날": when, "고친날": when, **({"본뜬": base} if base else {})} # fmt: skip - atomic_write_json(_path(), {"줄": [*data["줄"], row], "다음": data["다음"] + 1}) + atomic_write_json(_dir() / NEXT, {"다음": data["다음"] + 1}) # 번호 먼저 — 겹쳐 쓰지 않게 + _put(row) return answer(row) @@ -322,7 +360,7 @@ def update(key: str, raw: Any) -> dict[str, Any]: body = clean(raw) keep = {k: old[k] for k in ("키", "원문번호", "원문", "구분", "넣은날", "본뜬") if k in old} row = {**keep, "상세구분": body["이름"], **body, "고친날": _now()} - atomic_write_json(_path(), {**data, "줄": [row if r["키"] == key else r for r in data["줄"]]}) + _put(row) return answer(row) @@ -333,7 +371,7 @@ def delete(key: str) -> dict[str, Any]: if not any(r["키"] == key for r in data["줄"]): raise _bad(f"없는 내 기계 「{key}」", 404) used = uses().get(key, []) - atomic_write_json(_path(), {**data, "줄": [r for r in data["줄"] if r["키"] != key]}) + (_dir() / f"{key}.json").unlink(missing_ok=True) return {"지움": key, "쓰는곳": used}