feat(826-2): 내 기계 = 층 폴더 내기계/ 한 대 한 파일
- 층 내기계.json {줄, 다음} → 내기계/<키>.json + _다음.json · 옛 파일은 읽을 때 한 번 옮기고 지움(내용 · 다음 번호 같음)
- 만들기 = 번호 먼저 · 대 파일 하나 · 고침 = 그 대 파일만 · 지움 = 그 대 파일 지움 · 셈 색인 기억 = 폴더 자국
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user