Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_5' into sub_laptop_1
This commit is contained in:
@@ -183,6 +183,8 @@ def compare_sheet(
|
||||
if applied is None:
|
||||
gaps.append(gap("compare", m["key"], m["name"], "적용 단가 없음(출처 없음)"))
|
||||
supply = next((v for v in (mine.get("supply"), m.get("supply")) if v in SUPPLY), None)
|
||||
if mine.get("supply") == "사급": # 841-1 사용자가 기본 표시(M02 자재표시)를 끔
|
||||
supply = None
|
||||
rows.append(
|
||||
{
|
||||
**_head(m, no),
|
||||
|
||||
@@ -158,7 +158,9 @@ function compareSheet(materials: Row[], user: Row, gaps: Gap[]): Row[] {
|
||||
else [applied, appliedFrom] = [low, lowFrom];
|
||||
if (applied === null) gaps.push(gap("compare", m.key, m.name, "적용 단가 없음(출처 없음)"));
|
||||
const supply =
|
||||
[mine.supply, m.supply].find((v) => (SUPPLY as readonly string[]).includes(v)) ?? null;
|
||||
mine.supply === "사급" // 841-1 사용자가 기본 표시(M02 자재표시)를 끔
|
||||
? null
|
||||
: ([mine.supply, m.supply].find((v) => (SUPPLY as readonly string[]).includes(v)) ?? null);
|
||||
return {
|
||||
...head(m, i + 1),
|
||||
unit: m.unit || "",
|
||||
|
||||
@@ -132,6 +132,7 @@ def _item_row(item: dict[str, Any], prices: dict[str, Any] | None) -> dict[str,
|
||||
def _material_items(inputs: dict[str, Any], compare: list[dict[str, Any]]) -> list[dict]:
|
||||
"""대비표에서 「별도」 · 「관급」 표시한 수량 있는 자재 → 4 자재대 · 관급자재대 항목."""
|
||||
qty_of = {m["key"]: m.get("qty") for m in inputs.get("materials") or [] if "qty" in m}
|
||||
qty_of.update(inputs.get("aside_qty") or {}) # 841-1 대가 · 산근 밖 재료 = 그 역전개 수량
|
||||
out = []
|
||||
for row in compare:
|
||||
if row["supply"] is None or row["key"] not in qty_of:
|
||||
|
||||
@@ -95,6 +95,7 @@ function itemRow(item: Row, prices: Row | null): Row {
|
||||
function materialItems(inputs: Row, compare: Row[]): Row[] {
|
||||
const qtyOf: Row = {};
|
||||
for (const m of inputs.materials || []) if ("qty" in m) qtyOf[m.key] = m.qty;
|
||||
Object.assign(qtyOf, inputs.aside_qty || {}); // 841-1 대가 · 산근 밖 재료 = 그 역전개 수량
|
||||
const out: Row[] = [];
|
||||
for (const row of compare) {
|
||||
if (row.supply === null || !(row.key in qtyOf)) continue;
|
||||
|
||||
@@ -103,9 +103,6 @@ def _prices(line: dict[str, Any], books: dict[str, Any], sums: dict) -> dict[str
|
||||
): # 841-1 = M02 엔진 단가(`own`) · B09 단가 책 안 읽음
|
||||
own = line.get("own")
|
||||
out = {p: num(own.get(p)) if p in own else 0.0 for p in PARTS} if own else _none()
|
||||
row = (books["material"].get(key) or {}) if kind == "material" else {}
|
||||
if row.get("supply") in SUPPLY: # 관급 · 별도계상 자재 = 대가 안 0원(자재대 · 관급 단락)
|
||||
out["material"] = 0.0
|
||||
elif kind in ("daega", "sangeun"):
|
||||
row = {"daega": sums, "sangeun": books["sangeun"]}[kind].get(key) or {}
|
||||
for part in PARTS:
|
||||
@@ -117,6 +114,11 @@ def _prices(line: dict[str, Any], books: dict[str, Any], sums: dict) -> dict[str
|
||||
return out
|
||||
|
||||
|
||||
def _apart(line: dict[str, Any], books: dict[str, Any]) -> bool:
|
||||
"""대가 밖 재료(841-1) — 대비표 표시(사용자 › M02 자재표시 기본) 관급 · 별도 = 자재대 · 관급 단락."""
|
||||
return (books["material"].get(line.get("key")) or {}).get("supply") in SUPPLY
|
||||
|
||||
|
||||
def _none() -> dict[str, float | None]:
|
||||
return dict.fromkeys(PARTS, None)
|
||||
|
||||
@@ -137,8 +139,9 @@ def _base(spec: str, lines: list[dict[str, Any]], src: list) -> float | None:
|
||||
`line:이름` 그 줄 · `bundle:labor|material|machine` 그 종류 줄(+ 그 이음 고정 줄) 합 ·
|
||||
`rows:0,2@labor` 블록 그 차례 줄(`src` cid · `@비목` = 그 몫) 합(778-4 · 짝 `.ts` `pctBase`)."""
|
||||
kind, _, name = spec.partition(":")
|
||||
if kind == "part":
|
||||
return total([ln["amounts"][name] for ln in lines if not ln.get("out")])
|
||||
if kind == "part": # 대가 밖 재료(841-1 `aside`)도 밑수에 — M02 엔진 「재료비의 %」 꼴
|
||||
aside = "aside" if name == "material" else ""
|
||||
return total([ln.get(aside) or ln["amounts"][name] for ln in lines if not ln.get("out")])
|
||||
if kind == "rows": # `3@labor` = 그 줄 그 비목 몫
|
||||
out: list[float | None] = []
|
||||
for one in dict.fromkeys(name.split(",")):
|
||||
@@ -195,6 +198,8 @@ def _block(
|
||||
part: None if p is None or qty is None else cut(p * qty, line.get("place"))
|
||||
for part, p in prices.items()
|
||||
}
|
||||
if line["kind"] == "material" and _apart(line, books): # 관급 · 별도계상 = 대가 안 0원
|
||||
extra["aside"], prices["material"], amounts["material"] = amounts["material"], 0.0, 0.0
|
||||
ref = {"ref": {"key": line["key"]}} if line.get("key") else {} # 집계표(145-5) 이음
|
||||
ask = {"why": line["ask"]} if qty is None and line.get("ask") else {} # 723-4
|
||||
lines.append(
|
||||
|
||||
@@ -109,9 +109,6 @@ function linePrices(line: Row, books: Row, sums: Record<string, Row>): Parts {
|
||||
const own: Row | undefined = line.own;
|
||||
out = blanks();
|
||||
if (own) for (const part of PARTS) out[part] = part in own ? num(own[part]) : 0;
|
||||
const row: Row = kind === "material" ? books.material[key] || {} : {};
|
||||
// 관급 · 별도계상 자재 = 대가 안 0원(자재대 · 관급 단락)
|
||||
if ((SUPPLY as readonly string[]).includes(row.supply)) out.material = 0;
|
||||
} else if (kind === "daega" || kind === "sangeun") {
|
||||
const src: Row = ({ daega: sums, sangeun: books.sangeun } as Row)[kind];
|
||||
const row: Row = src[key] || {};
|
||||
@@ -140,7 +137,12 @@ function pctBase(spec: string, lines: Row[], src: Row[]): Num {
|
||||
const kind = spec.slice(0, at);
|
||||
const name = spec.slice(at + 1);
|
||||
const whole = (ln: Row): Num => total(PARTS.map((p) => ln.amounts[p]));
|
||||
if (kind === "part") return total(lines.filter((ln) => !ln.out).map((ln) => ln.amounts[name]));
|
||||
// 대가 밖 재료(841-1 `aside`)도 밑수에 — M02 엔진 「재료비의 %」 꼴
|
||||
const aside = name === "material" ? "aside" : "";
|
||||
if (kind === "part")
|
||||
return total(
|
||||
lines.filter((ln) => !ln.out).map((ln) => (aside && ln[aside]) || ln.amounts[name]),
|
||||
);
|
||||
if (kind === "rows") {
|
||||
// `3@labor` = 그 줄 그 비목 몫
|
||||
const out: Num[] = [];
|
||||
@@ -185,6 +187,13 @@ function block(b: Row, books: Row, sums: Record<string, Row>, gaps: Gap[], own:
|
||||
const p = prices[part];
|
||||
amounts[part] = p === null || qty === null ? null : cut(p * qty, line.place);
|
||||
}
|
||||
// 관급 · 별도계상 = 대가 안 0원(841-1 대비표 표시 = 사용자 › M02 자재표시)
|
||||
const supply = (books.material[line.key] || {}).supply;
|
||||
if (line.kind === "material" && (SUPPLY as readonly string[]).includes(supply)) {
|
||||
extra.aside = amounts.material;
|
||||
prices.material = 0;
|
||||
amounts.material = 0;
|
||||
}
|
||||
const ref = line.key ? { ref: { key: line.key } } : {}; // 집계표(145-5) 이음
|
||||
const ask: Row = qty === null && line.ask ? { why: line.ask } : {}; // 723-4
|
||||
lines.push({ ...line, ...ref, ...ask, qty, prices, amounts, ...extra });
|
||||
|
||||
@@ -38,6 +38,7 @@ from B09_Estimation import B09_Estimation_Labor_Rule as rule
|
||||
from B09_Estimation import B09_Estimation_Sangeun as sg
|
||||
from B09_Estimation import B09_Estimation_Sangeun_Text as st
|
||||
from B09_Estimation.B09_Estimation_Bill_Items import LAYER_WORD
|
||||
from B09_Estimation.B09_Estimation_Supply import supply_mark
|
||||
from M02_MasterTemplete import M02_Item_Prices as item_prices
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor_doc
|
||||
from M02_MasterTemplete import M02_Labor_Fixed as fixed
|
||||
@@ -150,6 +151,10 @@ class Builder:
|
||||
for line in lines:
|
||||
if line["kind"] == "machine":
|
||||
line["spec"] = (eq.get(line["key"]) or {}).get("규격") or ""
|
||||
marks = one.get("자재표시") or {} # 841-1 M02 자재표시(호표 차례) → 대비표 기본 표시
|
||||
for n, line in enumerate(lines):
|
||||
if line["kind"] == "material":
|
||||
line["mark"] = supply_mark(marks.get(str(n)))
|
||||
law = rule.mark(logic, lines, engine) # 새 의미 로직 = 줄 자리 · 합밖 · 번지 · 끝전(792-4)
|
||||
unit = str(logic.get("결과단위") or "").replace("원/", "")
|
||||
spec = " · ".join(f"{k} {v}" for k, v in sorted(given.items()))
|
||||
@@ -198,7 +203,8 @@ class Builder:
|
||||
return None
|
||||
except (sd.DocError, mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError):
|
||||
return None
|
||||
one = {"로직": key, "입력값": got["입력값"], "켜쌓기율": got["켜쌓기율"]}
|
||||
one = {"로직": key, "입력값": got["입력값"], "켜쌓기율": got["켜쌓기율"],
|
||||
"자재표시": got.get("자재표시")} # fmt: skip
|
||||
where = {c: n for n, c in enumerate(got["정체"])} # 율 기준 줄 목록(778-4) → 블록 차례
|
||||
block = self._poomsem(calc._Over(whole, key, row), {**one, "고름": got["고름"]})
|
||||
if block is None:
|
||||
@@ -436,7 +442,11 @@ class Builder:
|
||||
if line.get("qty") is None and line["kind"] != "blank":
|
||||
line = {**line, "why": "수량 없음 — 면적산출 알약 칸 · 손 수량을 넣음"}
|
||||
lines.append(line)
|
||||
lines += [_material(whole, mat, mat.get("수량")) for mat in result.get("자재") or []]
|
||||
marks = {m.get("코드"): m for m in doc.get("자재") or [] if isinstance(m, dict)}
|
||||
lines += [
|
||||
_material(whole, {**(marks.get(mat.get("코드")) or {}), **mat}, mat.get("수량"))
|
||||
for mat in result.get("자재") or []
|
||||
]
|
||||
manifest = read_json(folder / MANIFEST_NAME) or {}
|
||||
origin = manifest.get("원본") or {}
|
||||
return self.put(
|
||||
@@ -576,7 +586,8 @@ def _material(whole: Any, mat: dict, qty: Any) -> dict[str, Any]:
|
||||
if price is None and mat.get("코드"):
|
||||
price = labor_doc.material_price(whole, mat["코드"], mat.get("단위"))["기준단가"]
|
||||
own = None if _f(price) is None else {"material": _f(price)}
|
||||
return {"kind": "material", "key": mat.get("코드"), **names, "qty": _f(qty), "own": own}
|
||||
return {"kind": "material", "key": mat.get("코드"), **names, "qty": _f(qty), "own": own,
|
||||
"mark": supply_mark(mat)} # fmt: skip
|
||||
|
||||
|
||||
def _engine_bases(engine: list[dict], items: list[dict]) -> dict[str, float]:
|
||||
|
||||
@@ -93,7 +93,13 @@ from B09_Estimation.B09_Estimation_Rates import rate_candidates, rate_table
|
||||
from B09_Estimation.B09_Estimation_Router_Import import router as import_router
|
||||
from B09_Estimation.B09_Estimation_Router_Link import router as link_router
|
||||
from B09_Estimation.B09_Estimation_Router_Quotes import router as quotes_router
|
||||
from B09_Estimation.B09_Estimation_Summary import compute_summary, hopyo_lines, used_materials
|
||||
from B09_Estimation.B09_Estimation_Summary import (
|
||||
compute_summary,
|
||||
hopyo_lines,
|
||||
totals,
|
||||
used_materials,
|
||||
)
|
||||
from B09_Estimation.B09_Estimation_Supply import supply_defaults
|
||||
from common_util.common_util_auth import verify_session
|
||||
from common_util.common_util_project_settings import reset_section
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
@@ -159,8 +165,9 @@ def compute_sheets(
|
||||
|
||||
화면 짝 = `B09_Estimation_UI_Shell.computeSheets`(산근만 서버 답 그대로) ·
|
||||
memo = 두 번 셈의 앞 산근 풀이(`Sangeun.collect`)."""
|
||||
supply = supply_defaults(inputs, user)
|
||||
base = compute_base(inputs, user)
|
||||
sg = sangeun.build(root, user, memo) # 145-1 — M02 엔진 값으로 풀이(841-1 · + 166-2 손 줄 로직)
|
||||
sg = sangeun.build(root, user, memo, supply) # 145-1 M02 엔진 값으로 풀이(841-1 · 166-2 손 줄)
|
||||
sg = sg_cover.cover_sangeun(sg, user.get("sg_cover")) # 719-3 이 공사 산근 고친 칸
|
||||
by_key = {row["key"]: row for row in sg["sg_list"]}
|
||||
labor = compute_labor(
|
||||
@@ -189,6 +196,7 @@ def compute_twice(root: Path, inputs: dict[str, Any], user: dict[str, Any]) -> t
|
||||
keep = used_materials(first, inputs, user)
|
||||
inputs["materials"] = [m for m in inputs.get("materials") or [] if m["key"] in keep]
|
||||
inputs["material_qty"] = {row["key"]: row["qty"] for row in summary["sum_material"]}
|
||||
inputs["aside_qty"] = totals(first, hopyo_lines(first))[0].get("aside") or {} # 841-1 자재대
|
||||
sheets = compute_sheets(root, inputs, user, memo)
|
||||
return sheets, compute_summary(sheets, hopyo_lines(sheets))
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ from typing import Any
|
||||
|
||||
from B08_Quantity.B08_Quantity_Haul_Cycle import fill_given
|
||||
from B09_Estimation import B09_Estimation_Sangeun_Sum as ss
|
||||
from B09_Estimation.B09_Estimation_Calc_Base import gap, num, rounddown, total
|
||||
from B09_Estimation.B09_Estimation_Calc_Base import SUPPLY, gap, num, rounddown, total
|
||||
from B09_Estimation.B09_Estimation_Store import ref_rows
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
@@ -311,7 +311,8 @@ def block_totals(lines: list[dict[str, Any]]) -> dict[str, float | None]:
|
||||
class Solver:
|
||||
"""한 번 셈 — 산근 블록을 첫 등장 차례로 모음(하위 산근 포함)."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, supply: Any = None) -> None:
|
||||
self.supply = supply or (lambda key: None) # 자재 키 → 관급 · 별도 · None(841-1)
|
||||
self.prices = _Prices()
|
||||
self.eng: list[dict[str, Any]] = [] # 지금 푸는 로직의 엔진 줄(호표 차례 · 841-1)
|
||||
self.blocks: dict[str, dict[str, Any]] = {}
|
||||
@@ -349,6 +350,7 @@ class Solver:
|
||||
lines.append(self._line(whole, item, n, env, times, rate, depth))
|
||||
ss.places(row, lines) # 줄 자리(792-3)
|
||||
self._extras(whole, row, env, lines)
|
||||
self._aside(lines)
|
||||
_annotate(whole, row, env, lines)
|
||||
except (mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError) as e:
|
||||
return self._stop(code, name, str(e) or type(e).__name__)
|
||||
@@ -409,6 +411,16 @@ class Solver:
|
||||
at += n
|
||||
return text[:start], text[-1]
|
||||
|
||||
def _aside(self, lines: list[dict[str, Any]]) -> None:
|
||||
"""관급 · 별도계상 재료 = 산근 안 0원(841-1 · % 덧줄 밑수는 엔진 값 그대로 · 자재대 · 관급 단락)."""
|
||||
for ln in lines:
|
||||
key = (ln.get("ref") or {}).get("key")
|
||||
if ln["kind"] != "material" or self.supply(key) not in SUPPLY:
|
||||
continue
|
||||
part = next(iter(ln["price"]), "material")
|
||||
ln["aside"], ln[part], ln["price"] = ln[part], 0.0, {part: 0.0}
|
||||
self.prices.material[key] = 0.0
|
||||
|
||||
def _each(self, n: int, part: str, q: float) -> float | None:
|
||||
"""엔진 줄 n 비목 단가 = 엔진 금액 ÷ 엔진 수량 · 안 쓰는 줄(수량 0) = 0 · 못 셈 = None."""
|
||||
x = self.eng[n] if n < len(self.eng) else {}
|
||||
@@ -608,13 +620,15 @@ def collect(
|
||||
entries: list[tuple[str, Path]],
|
||||
hands: list[tuple[str, str, dict[str, Any]]] | None = None,
|
||||
memo: dict[str, Any] | None = None,
|
||||
supply: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""`entries` = [(구조물 id, 사본 폴더)] → 블록 · 목록 · 「연결 없음」 ·
|
||||
supply = 자재 키 → 관급 · 별도(841-1 · 대비표 표시) ·
|
||||
`hands` = 손 줄 로직(166-2 · 사본 뒤 번호 · 마스터 로직) ·
|
||||
memo = 같은 사본 · 손 줄의 앞 풀이(두 번 셈) — 앞 풀이가 본 단가가 같으면 그 풀이 사본."""
|
||||
if memo and "sg" in memo: # 841-1 풀이 = 사본 · 엔진만(바탕 표 안 읽음) → 앞 풀이 그대로
|
||||
return copy.deepcopy(memo["sg"])
|
||||
solver = Solver()
|
||||
solver = Solver(supply)
|
||||
for owner, folder in entries:
|
||||
whole = item_prices.load_prices(read_json(folder / PRICE_FILE) or {})
|
||||
for src in rows_of(folder, whole):
|
||||
@@ -647,6 +661,7 @@ def build(
|
||||
project_root: str | Path,
|
||||
user: dict[str, Any] | None = None,
|
||||
memo: dict[str, Any] | None = None,
|
||||
supply: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""연결된 사본 전부(구조물 id 차례 → 설계내역서 서고 사본 · 702-3)의 산근 +
|
||||
설계내역서 손 줄 로직(166-2 · 뒤 번호) — 차례 = `Labor_Source.daega_inputs` ·
|
||||
@@ -662,4 +677,4 @@ def build(
|
||||
entries += [
|
||||
(f"서고 {(m.get('원본') or {}).get('name') or f.name}", f) for f, m in copies(project_root)
|
||||
]
|
||||
return collect(entries, hand_sources(user), memo)
|
||||
return collect(entries, hand_sources(user), memo, supply)
|
||||
|
||||
@@ -328,7 +328,7 @@ def clean_user(raw: Any) -> dict[str, Any]:
|
||||
out: dict[str, Any] = {}
|
||||
if "applied" in cell:
|
||||
out["applied"] = _num(cell["applied"])
|
||||
if cell.get("supply") in SUPPLY:
|
||||
if cell.get("supply") in (*SUPPLY, "사급"): # 사급 = 기본 표시 끔(841-1)
|
||||
out["supply"] = cell["supply"]
|
||||
if "quote" in cell:
|
||||
out["quote"] = _num(cell["quote"])
|
||||
|
||||
@@ -41,7 +41,7 @@ def expand(
|
||||
kind = line.get("kind")
|
||||
if kind == "hopyo":
|
||||
expand(lines_of, str(line.get("key")), qty * q, out, depth + 1)
|
||||
elif kind in (*KINDS, "fixed"):
|
||||
elif kind in (*KINDS, "fixed", "aside"):
|
||||
bucket = out.setdefault(kind, {})
|
||||
bucket[str(line.get("key"))] = bucket.get(str(line.get("key")), 0.0) + qty * q
|
||||
|
||||
@@ -154,6 +154,8 @@ def block_lines(blocks: list[dict[str, Any]] | None) -> dict[str, Any]:
|
||||
lines += _fixed_lines(block, line)
|
||||
continue
|
||||
kind = LINE_KIND.get(line.get("kind"))
|
||||
if kind == "material" and "aside" in line: # 841-1 대가 · 산근 밖 재료 = 자재대 몫
|
||||
kind = "aside"
|
||||
key = (line.get("ref") or {}).get("key")
|
||||
if kind and key and num(line.get("qty")) is not None:
|
||||
lines.append({"kind": kind, "key": str(key), "qty": line["qty"]})
|
||||
@@ -191,7 +193,8 @@ def used_materials(sheets: dict[str, Any], inputs: dict[str, Any], user: dict[st
|
||||
}
|
||||
marks = user.get("materials") or {}
|
||||
for m in inputs.get("materials") or []:
|
||||
if "qty" in m or m.get("supply") or (marks.get(m["key"]) or {}).get("supply"):
|
||||
own = m.get("supply") and m.get("origin") != "M01" # 841-1 M02 기본 표시는 남길 까닭 아님
|
||||
if "qty" in m or own or (marks.get(m["key"]) or {}).get("supply"):
|
||||
keep.add(m["key"])
|
||||
return keep
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""B09 자재 관급 · 별도 표시(PLAN 841-1) — M02 자재표시를 대비표 기본 표시로 · 서버 단독 · 순수 함수.
|
||||
|
||||
M02 일위대가 · 단가산출 셈은 재료 줄을 자재표시(자재구분 관급 · 별도계상 — 빈 칸 = 별도계상)로
|
||||
합에서 뺌(`M02_Labor_Doc.apart`) → B09 도 같은 표시로 대가 · 산근 안 0 원 · 4 자재대 · 관급자재대로
|
||||
(`B09_Estimation_Calc_Labor._apart` · `Sangeun.Solver._aside` · `Calc_Bill` 자재대 수량 = `aside_qty`).
|
||||
대비표 표시 = 사용자 › 이 기본 · 사용자 「사급」 = 기본을 끔.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor_doc
|
||||
|
||||
|
||||
def supply_mark(one: Any) -> str | None:
|
||||
"""M02 자재표시(841-1) → 대비표 기본 표시 — 관급 · 별도(별도계상 · M02 기본) · None(직접계산)."""
|
||||
if labor_doc.mark_of(one, "자재구분") == "관급":
|
||||
return "관급"
|
||||
return "별도" if labor_doc.apart(one) else None
|
||||
|
||||
|
||||
def material_marks(blocks: list[dict[str, Any]]) -> dict[str, str | None]:
|
||||
"""대가 블록 재료 줄 M02 표시를 자재 키마다 하나로(841-1) — 관급 하나라도 = 관급 · 별도 하나라도 =
|
||||
별도 · 모두 직접계산 = None. ponytail: 키 하나 = 표시 하나(줄마다 다르면 별도 쪽 · 자재대에 한 번)."""
|
||||
out: dict[str, str | None] = {}
|
||||
for block in blocks:
|
||||
for line in block.get("lines") or []:
|
||||
if line.get("kind") != "material" or not line.get("key") or "mark" not in line:
|
||||
continue
|
||||
key, was = str(line["key"]), out.get(str(line["key"]))
|
||||
out[key] = "관급" if "관급" in (was, line["mark"]) else was or line["mark"]
|
||||
return out
|
||||
|
||||
|
||||
def supply_defaults(inputs: dict[str, Any], user: dict[str, Any]) -> Any:
|
||||
"""841-1 대비표 기본 표시 = M02 자재표시(대가 재료 줄 · 없으면 M02 기본 별도계상) — 로직 자재
|
||||
(B08 줄 밖 · 유가 밖) 입력 줄에 `supply` 를 얹음 → 자재 키 → 관급 · 별도(사용자 표시 › 기본)."""
|
||||
marks = material_marks((inputs.get("daega") or {}).get("blocks") or [])
|
||||
for row in inputs.get("materials") or []:
|
||||
if row.get("origin") == "M01" and "supply" not in row and marks.get(row["key"], "별도"):
|
||||
row["supply"] = marks.get(row["key"], "별도")
|
||||
mine = user.get("materials") or {}
|
||||
default = {row["key"]: row.get("supply") for row in inputs.get("materials") or []}
|
||||
|
||||
def supply(key: Any) -> str | None:
|
||||
got = (mine.get(key) or {}).get("supply")
|
||||
return None if got == "사급" else got or default.get(key, marks.get(key, "별도"))
|
||||
|
||||
return supply
|
||||
@@ -115,7 +115,8 @@ export const buildBasis: TabBuild = ({ sheets }): TabTable => {
|
||||
};
|
||||
};
|
||||
|
||||
const SUPPLY_NEXT: Record<string, string | null> = { "": "관급", 관급: "별도", 별도: null };
|
||||
// 841-1 기본 표시(M02 자재표시)가 있어도 끌 수 있게 — 별도 다음 = 사급(표시 끔)
|
||||
const SUPPLY_NEXT: Record<string, string | null> = { "": "관급", 관급: "별도", 별도: "사급" };
|
||||
|
||||
/** 적용 단가 고르기 항목(183-3) — 값 있는 출처(견적 포함 · 0 뺌) 「출처 단가 · 최저」 + 「최저(자동)」 ·
|
||||
* `pick(s)` = 그 출처 · null = 자동 */
|
||||
|
||||
Reference in New Issue
Block a user