feat(B09 설계내역서): 경쟁사 내역 엑셀 줄 불러오기 서버 — 읽기 · 짜기 · 고정 단가 칸 (PLAN 707-1)

- 읽기(Bill_Excel) — STmate 「설계내역서」 숨은 코드 · 부모 코드 열 · EST 「내역서」 공종 번호 깊이 · 비고 대가 · 산근 · 단산 · 일위 · 제외 묶음(관급 · 제외금액) · 조달수수료 = 율 · DRM = 「읽을 수 없음」 · 열 머리 = M02 실무자료 칸 읽기 재사용
- 짜기(Bill_Import) — 공종 = 새 묶음 · 항목 = 새 줄 + 고정 단가 칸 · 수량 갈래 take · price · 이름 같은 자동 줄 붙임 · 남은 자동 줄 지움 · 같은 파일 다시 = 갈아 끼움 · 어긋난 줄 목록
- 길(Router_Import) — import/bill(file · file_key) 미리 보기 · import/bill/apply(같은 파일 이름 실무자료 사전조합 → 서고 사본 연결 · 층 쓰기 0) · 저장 안 함
- 셈 짝(Calc_Bill py · ts) — 줄 price = 연결이 단가를 못 낼 때 금액 · fixed · src_qty · Store 다듬기
- 확인 = 기번3 710,359,136 · 영월 767,148,700 · 봉화 781,283,415 · 거창 384,916,227 직접비 합 = 엑셀 · 빈 줄 0 · 어긋남 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 15:43:51 +09:00
co-authored by Claude Opus 5.5
parent df5da11b37
commit c0ea73501f
8 changed files with 910 additions and 8 deletions
+23 -3
View File
@@ -27,7 +27,10 @@
(제목 `sec:*` = name 만 · `mat:` = ref 없음 ·
`fee` = 덮개 없음 · 차례에 안 넣음 — `sec:G` 끝 고정)
새 줄(`hand:*`) = 모든 칸 + "kind": "group"(묶음 · 수량 · 연결 없음) | 없음 = 항목 ·
"cost": "direct"(맨 위 새 묶음 · 없음도 direct)}],
"cost": "direct"(맨 위 새 묶음 · 없음도 direct) ·
고정 단가 칸(707-1 · 새 줄 · 자동 줄 둘 다) = "price" {labor, material, expense}(제 단가 ·
연결이 단가를 못 내면 이것으로 금액 · 줄 `fixed`) · "src_qty"(불러온 수량 = 비교 칸) ·
"note"(새 줄 비고 · 연결 비고가 이김)}],
"gone": [지운 자동 줄 키 — `sec:G` · `fee` · `mat:` 는 안 받음(자재 줄 주인 = 대비표 표시)]}
덮개 없음 = 기본 표 그대로 · [초기화] = `bill` 지움 ·
`rows` 에 없고 `gone` 에도 없는 자동 줄(B08 새 열 · 대비표 새 표시) =
@@ -113,6 +116,8 @@ def _item_row(item: dict[str, Any], prices: dict[str, Any] | None) -> dict[str,
"ref": item.get("ref"),
**{k: v for k, v in item.items() if k.startswith("auto_")},
**({"hand": True} if item.get("hand") else {"auto": True}),
**({"fixed": True} if item.get("fixed") else {}),
**({"src_qty": item["src_qty"]} if num(item.get("src_qty")) is not None else {}),
**_amounts(qty, prices),
}
row["_gaps"] = []
@@ -170,16 +175,27 @@ def ref_price(
return hopyo.get(key) if key else None
def fixed_price(price: Any) -> dict[str, Any] | None:
"""고정 단가 칸(707-1) → 1 단위 단가 — 빈 비목 0 · 칸 없음 = None."""
if not isinstance(price, dict):
return None
out = {p: num(price.get(p)) or 0.0 for p in PARTS}
return {"no": "", **out, "sum": out["labor"] + out["material"] + out["expense"]}
def hand_items(user: dict[str, Any], books: dict[str, Any], hopyo: dict) -> list[tuple]:
"""새 줄(`hand:`) → (항목, 단가) — 연결 = 고른 호표(로직 = 새 산근 키로 풀어 둠)."""
"""새 줄(`hand:`) → (항목, 단가) — 연결 = 고른 호표(로직 = 새 산근 키로 풀어 둠) ·
연결이 단가를 못 내면 고정 단가 칸(707-1)."""
out = []
for hand in (user.get("bill") or {}).get("rows") or []:
if not str(hand.get("key") or "").startswith("hand:"):
continue
price = ref_price(hand.get("ref"), books, hopyo, hand["key"])
ref = {"tab": price["tab"], "key": price["key"]} if price and price.get("tab") else None
fixed = price is None and (price := fixed_price(hand.get("price"))) is not None
why = WHY_LOGIC if (hand.get("ref") or {}).get("tab") == "logic" else WHY_HAND
item = {**hand, "hand": True, "note": (price or {}).get("no") or "", "ref": ref}
note = (price or {}).get("no") or hand.get("note") or ""
item = {**hand, "hand": True, "note": note, "ref": ref, "fixed": fixed}
out.append((item, price, why))
return out
@@ -209,6 +225,10 @@ def _covered(
out["note"] = (prices or {}).get("no") or ""
if (cover["ref"] or {}).get("tab") == "logic":
out["why"] = WHY_LOGIC
if prices is None and (prices := fixed_price(cover.get("price"))) is not None:
out["fixed"] = True # 707-1 연결이 단가를 못 냄 = 고정 단가 칸
if num(cover.get("src_qty")) is not None:
out["src_qty"] = num(cover["src_qty"])
return out, prices