Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1

This commit is contained in:
2026-11-02 22:33:34 +09:00
8 changed files with 203 additions and 10 deletions
+17 -6
View File
@@ -17,6 +17,8 @@ SK 단위 환산(728-2) = 배 = 바탕 기준단위(「100본」) → SK 단위(
화면 셈 길 줄 · 계 = SK 1 단위 몫(`per_unit`).
바탕 재료(747-1) = 별도계상 빈 칸 = 직접계산(M01 기초처럼 재료비에 듦 · 「별도계상」 고른 줄만 밖) ·
일위대가 덮개 줄(`line` · `M02_Labor_Patch.run`)은 일위대가 기본(222-1 · 162-1) 그대로.
보기 넷 한 답(750-1) = 화면 셈 길 답에 `글`(읽는 식) · `중간`(`M02_Sangeun_Views`) · 일위대가 줄
모달 = 문서 `줄모드` true → `line`(일위대가 그 줄 셈과 같은 값).
"""
from __future__ import annotations
@@ -32,6 +34,7 @@ from M02_MasterTemplete import M02_Labor_Fixed as fixed
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Sangeun_Doc as sd
from M02_MasterTemplete import M02_Sangeun_Views as views
COSTS = labor.COSTS
Ref = dict[str, Any]
@@ -328,10 +331,12 @@ def run(
trail: tuple = (),
detail: bool = False,
line: bool = False,
seen: bool = True,
) -> dict[str, Any]:
"""SK 셈(저장 안 함) — `run_line` 답 + `{1단위, 배, 정체, 짝잃음, 바탕판}` · 줄마다 `cid` ·
고친 칸 `덮개`(칸 이름) · `원값`. 틀린 문서 · 순환 = `sd.DocError` ·
`detail` = 둘째 기초 세부 펼침(`_detail` · 화면 셈 길만) · `line` = 일위대가 덮개 줄."""
`detail` = 둘째 기초 세부 펼침(`_detail` · 화면 셈 길만) · 그때 `seen` = 보기 덧칸(750-1) ·
`line` = 일위대가 덮개 줄."""
from M02_MasterTemplete.M02_Labor_Expand import Expand
got = resolve(doc, whole, read, trail, given, line)
@@ -345,6 +350,10 @@ def run(
names = {str(i.get("이름")) for i in row.get("입력") or []}
values = {k: v for k, v in {**got["입력값"], **(given or {})}.items() if k in names}
out = labor.run_line(over, key, values, got["켜쌓기율"], got["고름"] or None)
eng = out.get("줄") or []
seen = (
views.text_of(over, key, values, got["고름"], got["정체"], eng) if detail and seen else {}
)
covers = doc.get("덮개") or {}
lines = []
for at, (cid, line) in enumerate(zip(got["정체"], out.get("줄") or [])):
@@ -368,7 +377,7 @@ def run(
]
base = doc.get("바탕") or {}
tag = tag_of(whole, base["로직"]) if "로직" in base else (base.get("참조") or {}).get("키")
return {**out, **head, "1단위": unit, "빠진줄": gone, "꼬리표": tag}
return {**out, **head, **seen, "1단위": unit, "빠진줄": gone, "꼬리표": tag}
def unit(doc: dict, whole: Any, expand: Any, trail: tuple) -> dict[str, Any]:
@@ -495,8 +504,9 @@ def _was(line: dict) -> dict[str, Any]:
return {k: sd.plain(Decimal(str(v))) for k, v in keep.items() if v is not None}
def calc_doc(doc: Any, given: dict | None = None) -> dict[str, Any]:
"""셈 길(`M02_Sangeun_Router`) — 지금 층 문서 · 저장 전 화면 문서 · 틀림 400 · 순환 422."""
def calc_doc(doc: Any, given: dict | None = None, seen: bool = True) -> dict[str, Any]:
"""셈 길(`M02_Sangeun_Router`) — 지금 층 문서 · 저장 전 화면 문서 · 틀림 400 · 순환 422 ·
`seen` = 보기 덧칸(`글` · `중간`) 지음."""
from M01_MasterData import M01_MasterData_Store as m01
whole = m01.loaded()[1]
@@ -504,10 +514,11 @@ def calc_doc(doc: Any, given: dict | None = None) -> dict[str, Any]:
clean = sd.clean(doc, None, whole)
_check_refs(clean, refs.here()) # 저장 전 문서도 위 층만 · 있는 문서 · 순환 422
me = refs.ref_id({"층": refs.here()[0], "주인": refs.here()[1], "키": clean["키"]})
got = run(clean, whole, given, trail=(me,), detail=True)
line = isinstance(doc, dict) and doc.get("줄모드") is True # 일위대가 줄 모달(750-1)
got = run(clean, whole, given, trail=(me,), detail=True, line=line, seen=seen)
except sd.DocError as e:
raise _fail(e) from e
return labor._json({**per_unit(got, clean), "머리": head_of(clean)})
return labor._json({**views.finish(per_unit(got, clean)), "머리": head_of(clean)})
def from_line(
+4 -2
View File
@@ -2,7 +2,8 @@
[단가산출로 저장](715-3) `POST /api/m02/layers/{층}/sangeun/from-line`(층 쓰기 권한).
저장 안 함 · 로그인 사용자 · 층 읽기 권한(`Router_Items._root`) · 화면은 최신 응답만.
몸 = `문서`(저장 전 화면 문서) 또는 `키`(그 층 저장본) + `입력값`(열린입력 · 시험 값).
몸 = `문서`(저장 전 화면 문서) 또는 `키`(그 층 저장본) + `입력값`(열린입력 · 시험 값) ·
`보기` false = 보기 덧칸(`글` · `중간` · 750-1) 안 지음(바탕 원값 셈처럼 금액만 쓸 때).
문서 읽기 · 쓰기 · 새로 · 복사 · 지우기는 양식 길 · 서고 길 그대로(종류 `sangeun`).
⚠ 등록은 `main.py`(로그인만).
"""
@@ -27,6 +28,7 @@ class SangeunCalcBody(BaseModel):
문서: dict[str, Any] | None = None
키: str | None = None
입력값: dict[str, Any] | None = None
보기: bool = True
def _calc(body: SangeunCalcBody) -> dict[str, Any]:
@@ -35,7 +37,7 @@ def _calc(body: SangeunCalcBody) -> dict[str, Any]:
if not sd.KEY.match(str(body.키 or "")):
raise store.StoreError(400, "문서 또는 키(SK-######)")
doc = store.read(sd.KIND, str(body.키))["문서"]
return calc.calc_doc(doc, body.입력값)
return calc.calc_doc(doc, body.입력값, body.보기)
class FromLineBody(BaseModel):
+83
View File
@@ -0,0 +1,83 @@
"""M02 단가산출(SK) 보기 넷 = 셈 답 하나(PLAN 750-1 · 요구 588-1) — 화면 셈 길 답 덧칸.
- `글` = 읽는 식(M01 `/text` 와 같은 꼴 · `master_text.lines` + `master_sheet_line.annotate`) ·
SK 고친 줄(`_Over`) · 같은 입력(자동값 + 입력값) · 같은 고름(`choosing`) 위에서 지음.
줄마다 `cid` = SK 줄 cid(`annotate` 와 같은 차례 — 호표 줄 × `_shares` 비목 · 뒤 덧줄).
금액 · 소계 · 계 = SK `줄` · `기준` 그대로(`finish` — 별도계상 뺌 · 켜쌓기율 · 단위 환산 ÷배 ·
못 셈 줄 까닭) · 식 글(`글` · `이름글`)은 바탕 단위 셈 그대로 + 머리 `나눔`(배 ≠ 1).
- `중간` = 시험 계산 중간값(수 · 글만) — 시험 계산 = {lines: `줄`, sums: `기준`, middle: `중간`}.
계산 자리 = 서버 단독(CLAUDE.md 5장 ③) · 화면은 그리기만.
"""
from __future__ import annotations
from decimal import Decimal
from typing import Any
from M02_MasterTemplete import M02_Labor_Doc as labor
mf = labor.m01.cm.mf
import master_sheet_line as msl # noqa: E402 — scripts 길은 `m01` 이 둠
import master_text as mtx # noqa: E402
_ERRORS = (mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError)
def text_of(over: Any, key: str, values: dict, picks: Any, ids: list, eng: list) -> dict:
"""`{글, 중간}` — `eng` = `run_line` 줄(별도계상 빼기 전 · 차례 맞춤용) · 못 지음 = 까닭."""
row = over.logic(key)
auto = {k: v for k, v in labor.m01.mau.values(over, row).items() if v is not None}
given = {**auto, **labor.m01._dec(values or {})}
try:
with mf.mp.choosing(picks or None):
text = msl.annotate(over, key, given, mtx.lines(over, key, given))
mid = mf.run(over, key, given).get("중간") or {}
except _ERRORS as e:
return {"글": {"ok": False, "reason": str(e) or type(e).__name__}, "중간": {}}
if text.get("ok") and "groups" in text and "결과" not in row:
_tag(row, ids, eng, text)
keep = (Decimal, int, float, str, bool)
return {"글": text, "중간": {k: v for k, v in mid.items() if isinstance(v, keep)}}
def _tag(row: dict, ids: list, eng: list, text: dict) -> None:
"""글 줄마다 `cid` — `annotate` 와 같은 차례(호표 줄 × 몫 비목 → 덧줄)."""
slots = {g["비목"]: iter(g["줄"]) for g in text["groups"]}
items, adds = row.get("호표") or [], row.get("덧줄") or []
for n, line in enumerate(eng):
if n < len(items):
parts = [p for p, _ in mtx._shares(line)] if line.get("비목") else []
else:
parts = [adds[n - len(items)]["비목"]] if n - len(items) < len(adds) else []
for part in parts:
one = next(slots.get(part, iter(())), None)
if one is not None and n < len(ids):
one["cid"] = ids[n]
def finish(ans: dict) -> dict:
"""화면 답(`per_unit` 뒤) — 글 금액 · 소계 · 계 = SK 줄 · 기준 · SK 에 없는 줄(별도계상) 뺌."""
text = ans.get("글") or {}
base = ans.get("기준")
if not text.get("ok") or "groups" not in text or not base:
return ans
sk = {x["cid"]: x for x in ans.get("줄") or [] if x.get("cid")}
groups = []
for g in text["groups"]:
kept = []
for one in g["줄"]:
line = sk.get(one.get("cid"))
if line is None and one.get("cid"):
continue # SK 에서 빠진 줄(별도계상)
if line is not None:
parts = line.get("비목") or {}
money = parts.get(g["비목"]) if len(parts) > 1 else line.get("금액")
one = {**one, "금액": money}
if line.get("까닭"):
one["까닭"] = line["까닭"]
kept.append(one)
if kept:
groups.append({**g, "줄": kept, "소계": base.get(g["비목"])})
times = ans.get("화면배")
head = {"나눔": times} if times not in (None, 1) else {}
return {**ans, "글": {**text, "groups": groups, "계": base.get("계"), **head}}
@@ -632,6 +632,7 @@ export function lineDoc(
덮개: patch?.덮개 ?? {},
뺀줄: patch?.뺀줄 ?? [],
더한줄: patch?.더한줄 ?? [],
줄모드: true, // 셈 = 일위대가 그 줄 셈(별도계상 기본 · 750-1 · 서버 `calc_doc`)
};
if (row.참조) return { ...head, 바탕: { 참조: row.참조 } };
return {