fix(m02): 일위대가 별도계상 자재 셈에서 뺌 · 원문 같은 책 절 모두 · 단위 까닭 시험(222-1)
- 로직 자재 별도계상(빈 칸 = 기본) = 기준에서 빼고 일위대가 상세 줄에서 지움 · 자재목록엔 남김 - 사용자 자재 별도계상 = 합에서 뺌(파이썬 · TS settle 짝 · 거울 시험) - 사전조합 안 줄 · 프로젝트 사본 셈도 같은 길 - 별도계상 바꾸면 다시 셈(로직 자재 = 서버 · 사용자 자재 = 짝 셈) - 원문 출처 = 로직 출처 + 호표 · 입력 출처 같은 책 절(산림품셈 12-1-2 · 12-1-3) - 기존 시험 셋 = 직접계산 표시 · 대조용 apart 끔 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
@@ -20,9 +20,11 @@
|
||||
고름 = `{호표 차례: MT키}`(엔진 `choosing` 그대로) · 금액은 로직 재료비 안이라 합 · 알약에 안 더함(보기만) · 사용자 자재와
|
||||
같은 품목 두 번은 [저장] 때 400.
|
||||
승격(134-1) = 로직 줄 「단가승격」 표시(`true` 만 · 없음 = 단가산출) · 금액 무관 · B09 호표 몫.
|
||||
자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 금액 무관) — 사용자 자재 = 줄 덧칸 ·
|
||||
자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 별도계상만 금액에 닿음) — 사용자 자재 = 줄 덧칸 ·
|
||||
로직 자재 = 로직 줄 `자재표시 {호표 차례: {세 칸}}`(차례가 재료 줄인 것만 · 틀리면 버림) ·
|
||||
빈 칸 = 읽을 때 `MARK_DEFAULT`(`mark_of` · 저장은 비움 · 옛 「일반」 = 버림 → 사급 · 162-1).
|
||||
별도계상(222-1) = 그 자재 금액은 기준 · 합에서 빠지고 로직 `줄` 에서 지움(`drop_apart` · `settle` ·
|
||||
짝 `Labor_Calc.ts`) · `재료` · `자재`(자재목록 · B08 수량)엔 남음.
|
||||
% 덧줄(149-4) = 식 「X * Y / 100」 덧줄에 덧칸 `율`(Y) · `규격`(「노무비의 %」 등 · `pct_extras`) — 표기만 ·
|
||||
단위 · 수량 · 금액 그대로(「식 · 1」) · 화면이 율 · % 로 그림 · M01 시험계산 표도 이 함수.
|
||||
"""
|
||||
@@ -224,6 +226,35 @@ def mark_of(one: Any, key: str) -> str:
|
||||
return marks(one).get(key) or MARK_DEFAULT[key]
|
||||
|
||||
|
||||
def apart(one: Any) -> bool:
|
||||
"""별도계상(222-1) — 세 칸 「별도계상」(빈 칸 = 기본) 자재는 일위대가 셈에서 빠짐(본 프로그램에서
|
||||
따로 계상 · 자재목록은 상세 보기라 따로 안 더함)."""
|
||||
return mark_of(one, "별도계상") == "별도계상"
|
||||
|
||||
|
||||
def drop_apart(got: dict[str, Any], shown: Any) -> dict[str, Any]:
|
||||
"""로직 줄 답(`run_line`)에서 별도계상 로직 자재 빼기(222-1) — 그 재료 줄 금액을 기준에서 빼고
|
||||
`줄`(일위대가 상세)에서 지움 · `재료`(자재목록 · 세 칸 고침)는 그대로.
|
||||
ponytail: 「재료비의 %」 덧줄은 엔진 값 그대로(뺀 재료 몫 안 고침) — 그런 로직이 별도계상을 쓰면 엔진에 넘김."""
|
||||
shown = shown if isinstance(shown, dict) else {}
|
||||
lines = got.get("줄") or []
|
||||
out = {
|
||||
n
|
||||
for m in got.get("재료") or []
|
||||
if (n := m["차례"]) < len(lines) and apart(shown.get(str(n)))
|
||||
}
|
||||
if not out or not got.get("기준"):
|
||||
return got
|
||||
base = {k: _number(v) or Decimal(0) for k, v in got["기준"].items()}
|
||||
for n in out:
|
||||
for k, v in (lines[n].get("비목") or {}).items():
|
||||
cut = _number(v) or Decimal(0)
|
||||
base[k] = base.get(k, Decimal(0)) - cut
|
||||
base["계"] -= cut
|
||||
kept = [x for i, x in enumerate(lines) if i not in out]
|
||||
return {**got, "기준": _json(base), "줄": kept}
|
||||
|
||||
|
||||
def _mat_marks(value: Any, logic: dict) -> dict[str, dict[str, str]] | None:
|
||||
"""로직 자재 세 칸(148-4) `{호표 차례: {세 칸}}` — 맨 위 층 재료 줄 차례 · 허용 값만 ·
|
||||
빈 것 = None."""
|
||||
@@ -386,6 +417,7 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]:
|
||||
have = {str(k): v for k, raw in given.items() if (v := _number(raw)) is not None}
|
||||
hands = {line_id(one): one.get("수량") for one in doc.get("담은로직") or []}
|
||||
mhands = {one.get("코드"): one.get("수량") for one in doc.get("자재") or []}
|
||||
aside = {one.get("코드") for one in doc.get("자재") or [] if apart(one)} # 222-1 합에서 뺌
|
||||
zero = dict.fromkeys((*COSTS, "계"), Decimal(0))
|
||||
lines, mats, bases, reflects = [], [], [], []
|
||||
for line in result.get("줄") or []:
|
||||
@@ -399,9 +431,11 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]:
|
||||
qty, source = _pick(mhands.get(one.get("코드")), one.get("알약"), have)
|
||||
price = _number(one.get("기준단가"))
|
||||
cost = price * qty if price is not None and qty is not None else None
|
||||
mats.append({**one, "수량": qty, "수량출처": source, "반영": cost})
|
||||
if one.get("코드") in aside:
|
||||
continue
|
||||
bases.append({**zero, "재료비": price, "계": price} if price is not None else None)
|
||||
reflects.append({**zero, "재료비": cost, "계": cost} if cost is not None else None)
|
||||
mats.append({**one, "수량": qty, "수량출처": source, "반영": cost})
|
||||
return _json({"줄": lines, "자재": mats, "기준합": _sum(bases), "반영합": _sum(reflects)})
|
||||
|
||||
|
||||
@@ -468,7 +502,7 @@ def calc(
|
||||
"배": pill.get("배") or 1,
|
||||
}
|
||||
got = run_line(whole, code, one.get("입력값"), one.get("켜쌓기율"), one.get("고름"))
|
||||
lines.append({**line, **got})
|
||||
lines.append({**line, **drop_apart(got, one.get("자재표시"))})
|
||||
mats = [
|
||||
{
|
||||
"코드": one["코드"],
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
@@ -16,6 +17,7 @@ from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
|
||||
_SPEC = ("이름", "설명", "단위", "고르기", "범위")
|
||||
_LAYERED = "골쌓기 기준" # 85-2 — 표 주문에 이 글이 있으면 켜쌓기율 대상 로직
|
||||
_NUMBER = re.compile(r"(?<![\d-])\d+(?:-\d+)+(?![\d-])") # 품셈 절 번호(「12-1-3」)
|
||||
|
||||
|
||||
def _num(value: Any) -> Any:
|
||||
@@ -63,6 +65,26 @@ def _labels(whole: Any, spec: dict) -> dict[str, str]:
|
||||
return out
|
||||
|
||||
|
||||
def sources(logic: dict) -> Any:
|
||||
"""원문 출처(222-1) — 로직 `출처` + 호표 · 입력 · 중간 · 덧줄 `출처` 가운데 같은 책(번호 앞 글이
|
||||
같음)의 절 번호를 덧붙임(「산림품셈 12-1-2 · 12-1-3」 · 원문 패널이 절마다 찾음 · M01
|
||||
`make.source`) · 다른 책 절은 안 붙임 · 번호 없는 출처는 그대로."""
|
||||
main = str(logic.get("출처") or "")
|
||||
head = _NUMBER.search(main)
|
||||
if not head:
|
||||
return logic.get("출처")
|
||||
book, seen = main[: head.start()], set(_NUMBER.findall(main))
|
||||
extra = []
|
||||
for part in ("호표", "입력", "중간", "덧줄"):
|
||||
for row in logic.get(part) or []:
|
||||
src = str(row.get("출처") or "") if isinstance(row, dict) else ""
|
||||
hit = _NUMBER.search(src)
|
||||
if hit and src[: hit.start()] == book and hit.group() not in seen:
|
||||
seen.add(hit.group())
|
||||
extra.append(hit.group())
|
||||
return " · ".join([main, *extra])
|
||||
|
||||
|
||||
def _listed(logics: list[dict]) -> list[dict[str, Any]]:
|
||||
"""로직 줄마다 `{로직, 이름, 결과단위, 출처, 켜쌓기, 입력: [{이름, 설명, 단위, 고르기?, 고르기이름?, 범위?, 자동값}]}`."""
|
||||
whole = m01.loaded()[1]
|
||||
@@ -84,7 +106,7 @@ def _listed(logics: list[dict]) -> list[dict[str, Any]]:
|
||||
"로직": logic.get("키"),
|
||||
"이름": logic.get("이름"),
|
||||
"결과단위": logic.get("결과단위"),
|
||||
"출처": logic.get("출처"),
|
||||
"출처": sources(logic),
|
||||
"켜쌓기": logic.get("키") in layers,
|
||||
"입력": specs,
|
||||
}
|
||||
|
||||
@@ -423,6 +423,7 @@ class Expand:
|
||||
got = labor.run_line(
|
||||
self.whole, one["로직"], one.get("입력값"), one.get("켜쌓기율"), one.get("고름")
|
||||
)
|
||||
got = labor.drop_apart(got, one.get("자재표시")) # 별도계상 로직 자재 뺌(222-1)
|
||||
times = labor._number(pill.get("배")) or Decimal(1)
|
||||
base = got.get("기준")
|
||||
return {
|
||||
@@ -463,7 +464,7 @@ class Expand:
|
||||
rows.append({k: v * qty for k, v in sub["기준"].items()})
|
||||
for mat in doc.get("자재") or []: # 사용자 자재(175-2) = 기준단가 × 손 · 빈칸 = 안 셈
|
||||
qty = labor._number((mat.get("수량") or {}).get("손"))
|
||||
if qty is None:
|
||||
if qty is None or labor.apart(mat): # 별도계상 = 안 셈 · 안 줄에도 없음(222-1)
|
||||
continue
|
||||
got = labor.material_price(self.whole, mat["코드"], mat.get("단위"))
|
||||
price = labor._number(got["기준단가"])
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* 거울 시험 `resources/tester/test_m02_labor_calc_mirror.py`.
|
||||
* 기준(결과단위 그대로)은 서버 calc 길이 셈 · 여기는 받은 기준에
|
||||
* 수량(손이 칸보다 앞섬 · 칸 = 알약 키 값) · 반영 = 기준 × 수량 ÷ 배 · 자재 = 재료비만.
|
||||
* 별도계상 자재(222-1 · 빈 칸 = 기본 별도계상) = 반영은 보이되 합에 안 더함(짝 `apart`).
|
||||
* 참조 줄(단가승격 · 사전조합 · 50-3)은 `lineId` = 「참조|층|주인|키」(짝 `M02_Labor_Ref.ref_id`). */
|
||||
|
||||
import type { CalcLine as LogicLine } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
|
||||
@@ -79,7 +80,7 @@ export interface LaborCalc {
|
||||
/** 수량만 보는 문서 조각 — 담은 로직 · 자재의 손 값 */
|
||||
export interface LaborQtyDoc {
|
||||
담은로직?: { 로직?: string; 참조?: LaborRef; 수량?: Qty }[];
|
||||
자재?: { 코드: string; 수량?: Qty }[];
|
||||
자재?: { 코드: string; 수량?: Qty; 별도계상?: string }[];
|
||||
}
|
||||
|
||||
function num(raw: unknown): number | null {
|
||||
@@ -130,6 +131,10 @@ export function settle(
|
||||
}
|
||||
const hands = new Map((doc.담은로직 ?? []).map((one) => [lineId(one), one.수량 ?? null]));
|
||||
const mhands = new Map((doc.자재 ?? []).map((one) => [one.코드, one.수량 ?? null]));
|
||||
// 별도계상(222-1) — 「직접계산」 아니면(빈 · 옛 값 = 기본 별도계상) 합에서 뺌(짝 `M02_Labor_Doc.apart`)
|
||||
const aside = new Set(
|
||||
(doc.자재 ?? []).filter((m) => m.별도계상 !== "직접계산").map((m) => m.코드),
|
||||
);
|
||||
const base = zero();
|
||||
const done = zero();
|
||||
const add = (sum: Money, m: Money | null): void => {
|
||||
@@ -146,8 +151,8 @@ export function settle(
|
||||
const [qty, source] = pick(mhands.get(one.코드), one.알약, have);
|
||||
const price = num(one.기준단가);
|
||||
const cost = price !== null && qty !== null ? price * qty : null;
|
||||
if (price !== null) add(base, { ...zero(), 재료비: price, 계: price });
|
||||
if (cost !== null) add(done, { ...zero(), 재료비: cost, 계: cost });
|
||||
if (price !== null && !aside.has(one.코드)) add(base, { ...zero(), 재료비: price, 계: price });
|
||||
if (cost !== null && !aside.has(one.코드)) add(done, { ...zero(), 재료비: cost, 계: cost });
|
||||
return { ...one, 수량: qty, 수량출처: source, 반영: cost };
|
||||
});
|
||||
return { 줄, 자재, 기준합: base, 반영합: done };
|
||||
|
||||
@@ -383,14 +383,16 @@ export function mountLaborDetail(
|
||||
else delete cur.담은로직[i].승격;
|
||||
opt.onChanged(dirty());
|
||||
}
|
||||
// 자재 세 칸(148-4) — 화면 캐시만 · 금액 무관이라 다시 셈 없음
|
||||
// 자재 세 칸(148-4) — 화면 캐시만 · 별도계상만 금액에 닿음(222-1 · 사용자 자재 = 짝 셈 · 로직 자재 = 서버 기준)
|
||||
function markMat(i: number, key: MatMarkKey, v: string): void {
|
||||
setMark(cur.자재[i], key, v);
|
||||
opt.onChanged(dirty());
|
||||
if (key === "별도계상") changed(true);
|
||||
else opt.onChanged(dirty());
|
||||
}
|
||||
function markLogicMat(i: number, at: number, key: MatMarkKey, v: string): void {
|
||||
setLogicMark(cur.담은로직[i], at, key, v);
|
||||
opt.onChanged(dirty());
|
||||
if (key === "별도계상") changed();
|
||||
else opt.onChanged(dirty());
|
||||
}
|
||||
function removeMat(i: number): void {
|
||||
cur.자재.splice(i, 1);
|
||||
|
||||
Reference in New Issue
Block a user