merge(M02): origin/dev(39 통합) 받음 — G1 · G2 지움과 부딪힌 곳 풂
- 남의 새 줄(labor · 일위대가번 SU · 실무문서 · 「전체 항목」 · 수평선 · 알약 켬끔 · 복사해 가져오기)은 살리고 material · 자재조합 · 재료번 · matset 줄만 뺌 - dev 쪽 새 시험(test_m02_item_labor · test_m02_card_sheet_edit)의 재료산출 · 자재조합 자리 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
@@ -64,11 +64,12 @@ IN_USE = {
|
||||
# (종류, 문서 번호 칸, 머리 글자, 시트 이름) — 시트 이름이 None 이면 구조물 도면
|
||||
PARTS = (
|
||||
("structure", "도번", "SD", None),
|
||||
("labor", "일위대가번", "SU", None), # 일위대가 — `M02_Labor_Doc`(39장)
|
||||
("area", "면적번", "SA", "면적산출"),
|
||||
)
|
||||
_KEY = {kind: key for kind, key, _, _ in PARTS}
|
||||
SHEETS = ("area",) # 통합문서 종류 — [저장] 때 서버가 계산값을 새로 적음
|
||||
_CODE = re.compile(r"^S[DA](\d{8}|-\d{6})$")
|
||||
_CODE = re.compile(r"^S[DAU](\d{8}|-\d{6})$")
|
||||
_OLD_CODE = re.compile(r"^S[DCA](\d{6})$") # PLAN 12장 6자리 — 끝에 00 을 붙여 읽음
|
||||
_BAD_NAME = re.compile(r'[\\/:*?"<>|\x00-\x1f]|\.\.')
|
||||
# 도각 없는 빈 CAD 문서의 기본 도면층 — 없으면 B07 CAD 가 못 엶("Cannot read properties
|
||||
@@ -153,8 +154,9 @@ def write(kind: str, name: str, version: str, doc: Any, screen: bool = False) ->
|
||||
구조물집계표를 저장하면 — 새 구조물 열의 빈 구조물 도면 · 면적산출을 번호와 같이
|
||||
만들고 지운 열(또는 구조물이 아니게 된 열)의 세 파일을 지움(잠금 밖에서 · 안에서 부르면 죽음).
|
||||
통합문서(area)는 서버가 같은 TS 엔진을 Node 로 돌려 `계산값` 을 새로 적음
|
||||
(브라우저 값 버림 · 5장 ②) · 알약 `연결` 은 문서와 같이 그대로 적힘.
|
||||
번호 칸(도번 · 면적번)은 서버 것 — 있던 파일 번호를 그대로 둠(화면 값을 안 받음).
|
||||
(브라우저 값 버림 · 5장 ②) · 알약 `연결` 은 문서와 같이 그대로 적힘 · 일위대가(labor)는
|
||||
`M02_Labor_Doc.server` 가 정리 · `계산값` 을 새로 적음(면적산출 저장 때 그 열 일위대가도).
|
||||
번호 칸(도번 · 일위대가번 · 면적번)은 서버 것 — 있던 파일 번호를 그대로 둠(화면 값을 안 받음).
|
||||
표 열의 `본뜬열`(분류 더하기로 복사한 새 열)은 적지 않음 — 집계표면 아직 파일이 없는 새 열이
|
||||
원본 열의 일위대가 키와 세 파일 내용을 받음(새 번호 · 그 뒤로는 열마다 따로).
|
||||
`screen` = 화면 표 [저장](PLAN 26-7e) — 있던 항목의 경로 · 층 이름 · 빈 폴더는 저장본 그대로
|
||||
@@ -164,8 +166,7 @@ def write(kind: str, name: str, version: str, doc: Any, screen: bool = False) ->
|
||||
origins = _take_origins(doc) if kind == "table" else {}
|
||||
if kind == "table" and name == STRUCTURE_TABLE:
|
||||
doc = tree.normalize_table(doc) # 경로 · 머리 줄 수 · 빈 폴더(표 저장 · 나무 고침 한 줄기)
|
||||
if kind in SHEETS:
|
||||
doc = _server_values(doc)
|
||||
doc = _server(kind, doc)
|
||||
with _LOCK:
|
||||
have = version_of(path.read_bytes()) if path.is_file() else ""
|
||||
if have != (version or ""):
|
||||
@@ -177,10 +178,13 @@ def write(kind: str, name: str, version: str, doc: Any, screen: bool = False) ->
|
||||
doc = {**doc, _KEY[kind]: number} if number else doc
|
||||
atomic_write_json(path, doc)
|
||||
info = _info(kind, path)
|
||||
if kind == "area":
|
||||
_refresh_labor(name) # 면적 칸 값이 바뀌면 일위대가 계산값(목록 합계)도
|
||||
if kind == "table" and name == STRUCTURE_TABLE:
|
||||
for column, source in origins.items(): # 빈 파일 채우기 앞 — 원본 내용으로 만듦
|
||||
for part in _KEY:
|
||||
_ensure(part, column, source)
|
||||
_refresh_labor(column) # 면적이 일위대가 뒤에 서므로 칸 값으로 다시
|
||||
_copy_inputs(source, column)
|
||||
backfill_missing_structures() # 지우기 앞 — 같은 저장의 새 열이 지운 번호를 안 받게
|
||||
prune_removed_structures()
|
||||
@@ -280,6 +284,17 @@ def _retired() -> list[str]:
|
||||
def _blank(kind: str, column: str) -> dict[str, Any]:
|
||||
"""빈 문서(번호는 부르는 쪽이 붙임) — 구조물 도면 = 도각 없음 · 기본 도면층 하나 /
|
||||
면적산출 = 진짜 빈 시트 하나(식 없어 `계산값` 없음 · 첫 [저장] 때 서버가 적음)."""
|
||||
if kind == "labor":
|
||||
return {
|
||||
"종류": "일위대가",
|
||||
"판": 1,
|
||||
"열": column,
|
||||
"단위": None,
|
||||
"본뜬": None,
|
||||
"담은로직": [],
|
||||
"자재": [],
|
||||
"계산값": {},
|
||||
}
|
||||
if kind == "structure":
|
||||
return {
|
||||
"양식": "구조물도면",
|
||||
@@ -309,9 +324,7 @@ def _ensure(kind: str, column: str, source: str | None = None) -> bool:
|
||||
base = _blank(kind, column)
|
||||
if source and _path(kind, source).is_file():
|
||||
got = json.loads(_path(kind, source).read_bytes())
|
||||
base = {**{k: v for k, v in got.items() if k != "계산값"}, "열": column}
|
||||
if kind in SHEETS:
|
||||
base = _server_values(base)
|
||||
base = _server(kind, {**{k: v for k, v in got.items() if k != "계산값"}, "열": column})
|
||||
with _LOCK:
|
||||
if path.is_file():
|
||||
return False
|
||||
@@ -321,7 +334,7 @@ def _ensure(kind: str, column: str, source: str | None = None) -> bool:
|
||||
|
||||
def _take_origins(doc: Any) -> dict[str, str]:
|
||||
"""표 열의 `본뜬열` 을 떼어 냄 → `{새 열: 원본 열}` (구조물 파일이 아직 없는 열만) ·
|
||||
그 열에 일위대가 키가 비었으면 원본 열 것을 받음."""
|
||||
일위대가는 원본 열 문서를 새 번호로 복사(`_ensure` · 39-1b — 조합 키는 안 받음)."""
|
||||
cols = (
|
||||
[c for c in (doc.get("열") or []) if isinstance(c, dict)] if isinstance(doc, dict) else []
|
||||
)
|
||||
@@ -334,34 +347,47 @@ def _take_origins(doc: Any) -> dict[str, str]:
|
||||
if (root() / "structure" / f"{col['id']}.json").is_file():
|
||||
continue
|
||||
out[col["id"]] = source
|
||||
col["일위대가"] = col.get("일위대가") or by_id[source].get("일위대가")
|
||||
return out
|
||||
|
||||
|
||||
def create_structure(column: str) -> dict[str, Any]:
|
||||
"""구조물 열 하나의 구조물 도면 새로 — 도번 SD + 새 일련(`-` + 6자리) · 이미 있으면 409.
|
||||
면적산출 파일이 없으면 빈 통합문서도 같이 만듦(같은 뒷자리)."""
|
||||
일위대가 · 면적산출 파일이 없으면 빈 문서도 같이 만듦(같은 뒷자리)."""
|
||||
if column not in _column_ids():
|
||||
raise StoreError(404, f"{STRUCTURE_TABLE}에 없는 구조물 열 「{column}」")
|
||||
if not _ensure("structure", column):
|
||||
raise StoreError(409, f"이미 있는 구조물 도면 「{column}」")
|
||||
for kind in SHEETS:
|
||||
_ensure(kind, column)
|
||||
for kind in _KEY:
|
||||
_ensure(kind, column) # 도면은 위에서 만듦 · 있는 것은 그대로
|
||||
return read("structure", column)
|
||||
|
||||
|
||||
def copy_from(kind: str, column: str, source: str) -> dict[str, Any]:
|
||||
def copy_from(
|
||||
kind: str,
|
||||
column: str,
|
||||
source: str,
|
||||
source_root: Path | None = None,
|
||||
body: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""다른 열(`source`) 내용을 받는 열(`column`)로 가져옴 — 번호 · 열은 받는 쪽 그대로(두 열은
|
||||
따로 움직임) · 통합문서는 알약 `연결` 도 같이 오고 계산값은 서버가 다시 풂."""
|
||||
따로 움직임) · 통합문서는 알약 `연결` 도 같이 오고 계산값은 서버가 다시 풂 ·
|
||||
`source_root` = 원본 층 폴더(없으면 지금 층 · 권한은 부르는 쪽) · `body` = 원본 문서를 바로 줌
|
||||
(실무문서 조합 → 일위대가 · 39-1b)."""
|
||||
if kind not in _KEY:
|
||||
raise StoreError(404, f"없는 종류 「{kind}」")
|
||||
target = read(kind, column)["문서"]
|
||||
doc = {**read(kind, source)["문서"], **{k: target.get(k) for k in ("열", _KEY[kind])}}
|
||||
if kind in SHEETS:
|
||||
doc = _server_values({k: v for k, v in doc.items() if k != "계산값"})
|
||||
if body is None and source_root is not None:
|
||||
with at(source_root):
|
||||
body = read(kind, source)["문서"]
|
||||
body = read(kind, source)["문서"] if body is None else body
|
||||
doc = {**body, **{k: target.get(k) for k in ("열", _KEY[kind])}}
|
||||
doc = _server(kind, {k: v for k, v in doc.items() if k != "계산값"})
|
||||
with _LOCK:
|
||||
atomic_write_json(_path(kind, column), doc)
|
||||
return _info(kind, _path(kind, column))
|
||||
info = _info(kind, _path(kind, column))
|
||||
if kind == "area":
|
||||
_refresh_labor(column)
|
||||
return info
|
||||
|
||||
|
||||
def _copy_inputs(source: str, column: str) -> None:
|
||||
@@ -454,6 +480,30 @@ def renumber() -> dict[str, list[str]]:
|
||||
return {"옮김": moved, "지움": gone, "바꿈": changed, "새로": made}
|
||||
|
||||
|
||||
def _server(kind: str, doc: Any) -> Any:
|
||||
"""서버가 적는 값 — 통합문서 계산값(5장 ②) · 일위대가 정리 · 계산값(`M02_Labor_Doc`)."""
|
||||
if kind in SHEETS:
|
||||
return _server_values(doc)
|
||||
if kind == "labor":
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
return labor.server(doc)
|
||||
return doc
|
||||
|
||||
|
||||
def _refresh_labor(column: str) -> None:
|
||||
"""그 열 일위대가 `계산값` 을 지금 면적 값으로 다시 적음 — 문서가 틀어졌으면 그대로 둠."""
|
||||
path = _path("labor", column)
|
||||
if not path.is_file():
|
||||
return
|
||||
try:
|
||||
doc = _server("labor", json.loads(path.read_bytes()))
|
||||
except StoreError:
|
||||
return
|
||||
with _LOCK:
|
||||
atomic_write_json(path, doc)
|
||||
|
||||
|
||||
def _server_values(doc: Any) -> dict[str, Any]:
|
||||
try:
|
||||
return spreadsheet.with_server_values(doc)
|
||||
|
||||
Reference in New Issue
Block a user