Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -57,6 +57,7 @@ def _qty(value: Any) -> float | None:
|
||||
def _ref_mats(whole: Any, read: Any, ref: dict, trail: tuple[str, ...] = ()) -> list[tuple]:
|
||||
"""참조 문서 1 단위 재료 `[(재료 줄, 수량)]` — 층 셈 펼침(`M02_Labor_Ref.Expand`)과 같은 길:
|
||||
승격 = 원 로직 재료 ÷ 배 · 사전조합 = 안 줄 재료 × 손 수량(빈칸 1) · 로직 줄은 알약 배로 나눔 ·
|
||||
사전조합 자재(175-2) = 손 수량(빈칸 = 안 셈 · 품목 키 = 코드 · `자재` 표시 = 연료 거름 안 함) ·
|
||||
순환 · 깊이 넘음 · 없는 문서 = 빈 것."""
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
@@ -94,6 +95,10 @@ def _ref_mats(whole: Any, read: Any, ref: dict, trail: tuple[str, ...] = ()) ->
|
||||
continue
|
||||
hand = _qty((one.get("수량") or {}).get("손"))
|
||||
out += [(m, q * (1.0 if hand is None else hand)) for m, q in sub]
|
||||
for mat in doc.get("자재") or []:
|
||||
if hand := _qty((mat.get("수량") or {}).get("손")):
|
||||
line = {k: mat.get(k) for k in ("이름", "단위")}
|
||||
out.append(({**line, "품목": {"키": mat.get("코드")}, "자재": True}, hand))
|
||||
return out
|
||||
|
||||
|
||||
@@ -129,7 +134,7 @@ def _folder_units(root: str | Path, folder: str, meta: dict[str, dict]) -> dict[
|
||||
|
||||
def logic_mat(mat: dict, qty: float) -> None:
|
||||
name, unit = str(mat.get("이름") or ""), str(mat.get("단위") or "")
|
||||
if _fuel(name, unit):
|
||||
if _fuel(name, unit) and not mat.get("자재"): # 사전조합 사용자 자재는 거르지 않음
|
||||
return
|
||||
goods = mat.get("품목")
|
||||
code = str(goods.get("키") or "") if isinstance(goods, dict) else ""
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
(`M02_Item_Prices.calc` = 142-1 층 셈 꼴)로 돌려 줄 수량을 받고, 블록 꼴로 펼침:
|
||||
|
||||
조합 구조물 조합 사본 하나 = 블록(엑셀 「조합」 꼴) · 줄 = 담은 로직 줄 · 자재 줄
|
||||
사전조합 참조 SC = 겹 대가(굳힌 `참조사본`) · 줄 = 그 안 로직 · 참조 줄(수량 = 손)
|
||||
사전조합 참조 SC = 겹 대가(굳힌 `참조사본`) · 줄 = 그 안 로직 · 참조 줄(수량 = 손) ·
|
||||
자재 줄(175-2 · 수량 = 손 · 빈칸 = 수량 없음)
|
||||
품셈 일위대가화(승격) 줄 · 옛 참조 SP = 로직마다 따로(노무 · 재료 · 중기 · 산근 · % 줄)
|
||||
— 조합이 수량만큼 부름 · 기계 = 기계경비 로직 줄 · 다른 돈 로직 줄 = 하위 산근
|
||||
산근 단가산출 줄(일위대가화 아닌 로직 줄) = 산근 호표 참조(키 = 145-1 `sangeun_key`)
|
||||
@@ -206,7 +207,7 @@ class Builder:
|
||||
"spec_raw": block.get("spec_raw"), "unit": block["unit"], "qty": each} # fmt: skip
|
||||
|
||||
def precombo(self, whole: Any, ref: dict, doc: dict, copies: dict, depth: int = 0) -> str:
|
||||
"""사전조합(SC) = 겹 대가 — 줄 수량 = 손(기본 1) · 로직 줄 = 품셈 호표."""
|
||||
"""사전조합(SC) = 겹 대가 — 줄 수량 = 손(기본 1) · 로직 줄 = 품셈 호표 · 자재 줄(175-2)."""
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
|
||||
inner = [one for one in doc.get("담은로직") or [] if isinstance(one, dict)]
|
||||
@@ -226,6 +227,10 @@ class Builder:
|
||||
continue
|
||||
qty = 1 if hand is None else hand
|
||||
lines.append(self.line_of(whole, one, qty, times, name, copies, depth))
|
||||
lines += [
|
||||
_material(whole, mat, (mat.get("수량") or {}).get("손"))
|
||||
for mat in doc.get("자재") or []
|
||||
]
|
||||
return self.put(
|
||||
{
|
||||
"kind": "사전조합",
|
||||
@@ -262,11 +267,7 @@ class Builder:
|
||||
if line.get("qty") is None and line["kind"] != "blank":
|
||||
line = {**line, "why": "수량 없음 — 면적산출 알약 칸 · 손 수량을 넣음"}
|
||||
lines.append(line)
|
||||
for mat in result.get("자재") or []:
|
||||
row = (whole.get(mat.get("코드")) if mat.get("코드") else None) or {}
|
||||
lines.append({"kind": "material", "key": mat.get("코드"), "name": row.get("이름") or "",
|
||||
"spec": row.get("규격") or "", "unit": row.get("단위") or "",
|
||||
"qty": _f(mat.get("수량"))}) # fmt: skip
|
||||
lines += [_material(whole, mat, mat.get("수량")) for mat in result.get("자재") or []]
|
||||
manifest = read_json(folder / MANIFEST_NAME) or {}
|
||||
origin = manifest.get("원본") or {}
|
||||
return self.put(
|
||||
@@ -282,6 +283,13 @@ class Builder:
|
||||
)
|
||||
|
||||
|
||||
def _material(whole: Any, mat: dict, qty: Any) -> dict[str, Any]:
|
||||
"""자재 줄 — 조합 사용자 자재 · 사전조합 자재(175-2) 한 꼴 · 이름 · 규격 · 단위 = 사본 단가."""
|
||||
row = (whole.get(mat.get("코드")) if mat.get("코드") else None) or {}
|
||||
names = {k: row.get(v) or "" for k, v in (("name", "이름"), ("spec", "규격"), ("unit", "단위"))}
|
||||
return {"kind": "material", "key": mat.get("코드"), **names, "qty": _f(qty)}
|
||||
|
||||
|
||||
def _engine_bases(engine: list[dict], items: list[dict]) -> dict[str, float]:
|
||||
"""엔진 호표 줄 → 덧줄 밑수 후보 — `part:비목` 합 · `lines:labor` 인력 줄 합 · `line:이름`."""
|
||||
out: dict[str, float] = {f"part:{p}": 0.0 for p in PART.values()}
|
||||
|
||||
@@ -162,10 +162,11 @@ def build_source(item: dict[str, Any]) -> dict[str, Any]:
|
||||
materials += [ # 고른 로직 자재(103-1) 품목 줄도 — 복사본 계산이 그 단가를 씀
|
||||
str(key) for one in doc.get("담은로직") or [] for key in (one.get("고름") or {}).values()
|
||||
]
|
||||
materials += refs.frozen_materials((labor or {}).get("참조사본") or {}) # 175-2 사전조합 자재
|
||||
wanted = logic_keys + [code for code in materials if code]
|
||||
files[PRICE_FILE] = prices.to_json(prices.collect(logic_keys, materials)) if wanted else {}
|
||||
if mine := own.bundle(
|
||||
own.codes(doc), root
|
||||
): # 개인 자재(148-3) — 원본 목록이 바뀌어도 사본 그대로
|
||||
[c for c in materials if own.is_own(c)], root
|
||||
): # 개인 자재(148-3 · 사전조합 것도 175-2) — 원본 목록이 바뀌어도 사본 그대로
|
||||
files[PRICE_FILE][own.BUNDLE] = mine
|
||||
return files
|
||||
|
||||
@@ -607,8 +607,13 @@ def server(doc: Any) -> dict[str, Any]:
|
||||
사용자 자재가 로직 자재 품목과 같으면 400(103-1 · 같은 품목 두 번 셈)."""
|
||||
got = clean(doc)
|
||||
out = calc(got)
|
||||
no_twice(got, out)
|
||||
return {**got, "계산값": out}
|
||||
|
||||
|
||||
def no_twice(got: dict, out: dict) -> None:
|
||||
"""사용자 자재가 로직 자재 품목과 같으면 400 — 일위대가 · 사전조합(175-1) 저장 한 벌."""
|
||||
inside = {x["품목"]["키"] for one in out["줄"] for x in one.get("재료") or [] if x.get("품목")}
|
||||
twice = sorted({m["코드"] for m in got["자재"]} & inside)
|
||||
if twice:
|
||||
raise _bad(f"자재 「{', '.join(twice)}」 는 로직 자재에 있음 — 같은 품목 두 번 담음")
|
||||
return {**got, "계산값": out}
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
층 문서를 읽을 때 옛 참조 줄 = 로직 줄 + `승격`(`unpromote`) · 프로젝트 사본은 그대로 셈
|
||||
precombo `{층}/precombo/SC-######.json` 사전조합
|
||||
`{종류:"사전조합", 키, 이름, 단위, 본뜬, 담은로직:[{참조, 수량:{손}, 메모}
|
||||
| {로직, 입력값, 메모, 수량:{손}}]}` — 승격 · 사전조합 · 단가산출 로직(51-11) ·
|
||||
수량은 손(기본 1) · 값 = Σ 안 줄 1 단위 값 × 수량(로직 줄 = 기준 ÷ 배)
|
||||
| {로직, 입력값, 메모, 수량:{손}, 고름?, 자재표시?}], 자재:[일위대가 자재 꼴]}` —
|
||||
승격 · 사전조합 · 단가산출 로직(51-11) · 수량은 손(기본 1) ·
|
||||
자재 수량은 손 · 빈칸 = 안 셈(175) ·
|
||||
값 = Σ 안 줄 1 단위 값 × 수량(로직 줄 = 기준 ÷ 배) + Σ 자재 기준단가 × 손
|
||||
참조 = `{층, 주인, 키}`(마스터 주인 null · 회사 `C` · 개인 `C/U`) — 종류는 키 머리(SP · SC) ·
|
||||
위 층만 가리킴(개인 → 그 개인 · 그 회사 · 마스터 / 회사 → 그 회사 · 마스터 / 마스터 → 마스터) ·
|
||||
같은 참조 두 번 · 순환(422)은 저장 때 막음 · 계산 때도 깊이 상한(`DEPTH`)에서 멈추고 까닭.
|
||||
@@ -142,6 +144,7 @@ def clean_doc(kind: str, doc: Any) -> dict[str, Any]:
|
||||
if not name:
|
||||
raise _bad("사전조합 이름이 빔")
|
||||
out.update(이름=name, 단위=unit or None, 담은로직=_combo_lines(doc, key))
|
||||
out["자재"] = _combo_mats(doc, out["담은로직"])
|
||||
out.update({k: str(doc.get(k) or "").strip() for k in ("구분", "상세구분")})
|
||||
out["본뜬"] = doc["본뜬"] if isinstance(doc.get("본뜬"), dict) else None
|
||||
return out
|
||||
@@ -206,7 +209,7 @@ def _combo_lines(doc: dict, key: str) -> list[dict[str, Any]]:
|
||||
head = {"참조": one["참조"]} if "참조" in one else {"로직": one["로직"]}
|
||||
if "로직" in one:
|
||||
head["입력값"] = one["입력값"]
|
||||
head.update({k: one[k] for k in ("켜쌓기율",) if k in one})
|
||||
head.update({k: one[k] for k in ("켜쌓기율", "고름", "자재표시") if k in one})
|
||||
lines.append({**head, "수량": one["수량"] or {"손": 1}, "메모": one["메모"]})
|
||||
at_ = here()
|
||||
me = {"층": at_[0], "주인": at_[1], "키": key}
|
||||
@@ -219,6 +222,18 @@ def _combo_lines(doc: dict, key: str) -> list[dict[str, Any]]:
|
||||
return lines
|
||||
|
||||
|
||||
def _combo_mats(doc: dict, lines: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""사전조합 자재(175-1) — 일위대가 자재와 같은 검사(`labor.clean` · 개인 자재 · 세 칸) ·
|
||||
로직 자재와 같은 품목 400(`labor.no_twice`)."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
mats = labor.clean({"자재": doc.get("자재") or []})["자재"]
|
||||
if mats:
|
||||
got = {"담은로직": lines, "자재": mats}
|
||||
labor.no_twice(got, labor.calc(got, {}))
|
||||
return mats
|
||||
|
||||
|
||||
def _no_loop(ref: Ref, trail: list[Ref]) -> None:
|
||||
"""저장 때 순환 · 깊이 — 아래로 따라가 처음 문서를 다시 만나면 422 「A → B → A」."""
|
||||
if len(trail) > DEPTH:
|
||||
@@ -446,6 +461,29 @@ class Expand:
|
||||
why = why or f"「{sub.get('이름')}」 {sub.get('까닭')}"
|
||||
continue
|
||||
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:
|
||||
continue
|
||||
got = labor.material_price(self.whole, mat["코드"], mat.get("단위"))
|
||||
price = labor._number(got["기준단가"])
|
||||
zero = dict.fromkeys(labor.COSTS, Decimal(0))
|
||||
base = None if price is None else {**zero, "재료비": price, "계": price}
|
||||
name = mat.get("이름") or mat["코드"]
|
||||
inner.append(
|
||||
{
|
||||
"자재": mat["코드"],
|
||||
"수량": qty,
|
||||
"이름": name,
|
||||
"단위": mat.get("단위"),
|
||||
"기준": base,
|
||||
"까닭": got["까닭"],
|
||||
}
|
||||
)
|
||||
if base is None:
|
||||
why = why or f"「{name}」 {got['까닭']}"
|
||||
continue
|
||||
rows.append({k: v * qty for k, v in base.items()})
|
||||
return {
|
||||
"이름": doc.get("이름"),
|
||||
"단위": doc.get("단위"),
|
||||
@@ -498,6 +536,16 @@ def with_pills(doc: dict[str, Any]) -> dict[str, Any]:
|
||||
return {**doc, "알약": combo_store.combo_pills({"담은로직": lines})["물량"]}
|
||||
|
||||
|
||||
def frozen_materials(copies: dict[str, Any]) -> list[str]:
|
||||
"""굳힌 사전조합의 자재 · 고른 로직 자재 코드(175-2) — 프로젝트 단가 묶음에 넣음."""
|
||||
out = []
|
||||
for doc in copies.values():
|
||||
out += [str(m.get("코드")) for m in doc.get("자재") or [] if isinstance(m, dict)]
|
||||
lines = [one for one in doc.get("담은로직") or [] if isinstance(one, dict)]
|
||||
out += [str(key) for one in lines for key in (one.get("고름") or {}).values()]
|
||||
return out
|
||||
|
||||
|
||||
def frozen_logics(copies: dict[str, Any]) -> list[str]:
|
||||
"""굳힌 단가승격 · 사전조합이 담은 로직 키 — 프로젝트 단가 묶음에 넣음."""
|
||||
return [
|
||||
|
||||
@@ -40,12 +40,13 @@ export interface CalcLine {
|
||||
/** 로직 자재(103-1) — 맨 위 층 재료 줄 · 금액은 `기준` 안(여기서 안 더함) */
|
||||
재료?: LogicMat[];
|
||||
/** 참조 줄 — 가리킨 문서 이름 · 단가승격의 원 로직 키 · 사전조합 안 줄(한 층 · 50-3 ④ ·
|
||||
* 참조 줄 = `참조` · 로직 줄(51-11) = `로직`) */
|
||||
* 참조 줄 = `참조` · 로직 줄(51-11) = `로직` · 자재 줄(175-2) = `자재` 코드) */
|
||||
이름?: string | null;
|
||||
원로직?: string | null;
|
||||
안?: {
|
||||
참조?: LaborRef;
|
||||
로직?: string;
|
||||
자재?: string;
|
||||
수량: number;
|
||||
이름?: string | null;
|
||||
단위?: string | null;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* · 자재목록 = 로직 자재 줄(103-1 · `Labor_LogicMats` · [바꾸기] → `담은로직[].고름` · 입력이 바뀌면 걸린 고름 풂) +
|
||||
* 사용자 자재 줄([삭제]) · 로직 자재와 같은 품목은 사용자 자재로 못 담음
|
||||
* · 오른쪽 「원문」 접는 패널(47-4 · 상세만 · 공용 `ui_template_side_panels`) = 고른 줄 로직의 출처 원문
|
||||
* · 서고 모드(50-3 · `mode`) = 사전조합 — 이름 칸 · 단가산출 로직 표 · 원문만(자재 · 면적 · 다이어그램 없음) ·
|
||||
* · 서고 모드(50-3 · `mode`) = 사전조합 — 이름 칸 · 단가산출 로직 표 · 자재목록(175-1) · 원문(면적 · 다이어그램 없음) ·
|
||||
* 정보 칸 구분 · 상세구분(51-12 · 고름) · 일위대가 상세와 같은 [로직 추가] · 표 · 동작(51-11) · 수량 손 입력(기본 1)
|
||||
* · 로직 줄 구분 드롭다운(134-1 · 단가산출 | 단가승격 · 서고 모드엔 없음)
|
||||
* ========================================================================== */
|
||||
@@ -228,13 +228,13 @@ export function mountLaborDetail(
|
||||
cur.담은로직,
|
||||
calc ?? last,
|
||||
!calc && !fail,
|
||||
// 세 칸(148-4) = 구조물 일위대가만(사전조합 줄은 `_combo_lines` 가 안 남김)
|
||||
// 세 칸(148-4) — 사전조합도 같음(175-1 · `_combo_lines` 가 남김)
|
||||
edit
|
||||
? {
|
||||
open: openRow,
|
||||
swap: swapMat,
|
||||
reset: resetMat,
|
||||
mark: lib ? undefined : markLogicMat,
|
||||
mark: markLogicMat,
|
||||
}
|
||||
: { open: openRow },
|
||||
),
|
||||
@@ -641,7 +641,7 @@ export function mountLaborDetail(
|
||||
]),
|
||||
// 사전조합 = 같은 [로직 추가](51-11)
|
||||
section(tc("Logics_Title"), opt.card ? null : addLogic, logicHost),
|
||||
...(lib ? [] : [section(tc("Labor_Mats"), opt.card ? null : addMat, matHost)]),
|
||||
section(tc("Labor_Mats"), opt.card ? null : addMat, matHost),
|
||||
...(opt.card || lib ? [] : [dgBox]),
|
||||
],
|
||||
}),
|
||||
@@ -668,12 +668,11 @@ export function mountLaborDetail(
|
||||
});
|
||||
|
||||
return {
|
||||
// 서고 모드 = 문서 종류 그대로 · 열 · 자재 없음
|
||||
// 서고 모드 = 문서 종류 그대로 · 열 없음
|
||||
getDoc: () => {
|
||||
if (!lib) return cur;
|
||||
const out: Record<string, unknown> = { ...cur, 종류: "사전조합" };
|
||||
delete out.열;
|
||||
delete out.자재;
|
||||
return out as unknown as LaborDoc;
|
||||
},
|
||||
saveArea: async () => {
|
||||
|
||||
@@ -267,14 +267,14 @@ export interface LogicTableData {
|
||||
}
|
||||
|
||||
/** 고른 줄 하위 행(51-2 · 같은 표 · 같은 칸) — 단가산출 · 단가승격 = 그 로직 재료 · 노무 · 경비 줄 ·
|
||||
* 일위조합 = 세부 항목 한 층(51-13 · 참조 줄 · 로직 줄 · 안을 다시 펼치지 않음 · 금액 = 안 줄 기준 × 안 줄 수량) ·
|
||||
* 일위조합 = 세부 항목 한 층(51-13 · 참조 줄 · 로직 줄 · 자재 줄 175-2 · 안을 다시 펼치지 않음 · 금액 = 안 줄 기준 × 안 줄 수량) ·
|
||||
* 금액 칸 = 위 기준(위 줄 기준과 같은 바탕) / 아래 반영(× 위 줄 수량 ÷ 배) ·
|
||||
* 하위 합 = 위 줄 금액 · 줄이 없으면 빈 한 줄(「…」 · 까닭 · 「—」) */
|
||||
function subRows(line: LaborCalcLine | undefined, wait: boolean, tools: boolean): HTMLElement[] {
|
||||
const times = line?.수량 === null || line?.수량 === undefined ? null : line.수량 / (line.배 || 1);
|
||||
const items = line?.안
|
||||
? line.안.map((x) => {
|
||||
const key = x.참조?.키 ?? x.로직 ?? "";
|
||||
const key = x.참조?.키 ?? x.로직 ?? x.자재 ?? "";
|
||||
const label = x.이름 && x.이름 !== key ? `${key} ${x.이름}` : key; // 로직 줄 이름 없으면 키만
|
||||
return {
|
||||
name: `${label}${x.까닭 ? ` · ${x.까닭}` : ""}`,
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
"""M02 일위대가 사전조합 자재목록(PLAN 175) — 구조물 일위대가와 같은 자재목록.
|
||||
|
||||
175-1 화면 · 저장 — 서고 모드 자재목록 · 로직 자재 세 칸 막힘 풂 · 저장 `_combo_lines` 가
|
||||
고름 · 자재표시 남김 · 문서 `자재`(일위대가 자재 검사 · 로직 자재와 같은 품목 400).
|
||||
175-2 셈 — 담은 자재(손 · 빈칸 = 안 셈)가 사전조합 1 단위 값(`Expand._combo` · 안 줄) · B08 참조 줄
|
||||
재료(`_ref_mats`) · B09 겹 대가 자재 줄 · 프로젝트 사본 단가 묶음(`frozen_materials`)에 들어감.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 임시 층 fixture
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "..", "..", "M02_MasterTemplete")
|
||||
NET = "GC000337" # 낙석방지망 — 재료 줄 3 · 4 · 5(철망 · 결속선 · 에폭시)
|
||||
NET_ITEM = "MT006697"
|
||||
WIRE = "MT000194"
|
||||
MAT = "MT000008" # 자재품목 · 물가정보 83000
|
||||
STONE = "GF000219" # 원/㎡
|
||||
SYS = {"층": "system", "주인": None}
|
||||
|
||||
|
||||
def _save(folder, doc):
|
||||
with store.at(folder):
|
||||
key = refs.create("precombo", {"이름": "자재 조합", **doc})["이름"]
|
||||
return key, json.loads((folder / "precombo" / f"{key}.json").read_bytes())
|
||||
|
||||
|
||||
def test_화면_서고_모드도_자재목록_세_칸_자재_저장(world): # noqa: F811
|
||||
detail = open(os.path.join(SRC, "M02_MasterTemplete_Labor_Detail.ts"), encoding="utf-8").read()
|
||||
assert 'section(tc("Labor_Mats"), opt.card ? null : addMat, matHost),' in detail
|
||||
assert 'lib ? [] : [section(tc("Labor_Mats")' not in detail
|
||||
assert "mark: markLogicMat," in detail and "lib ? undefined" not in detail
|
||||
assert "delete out.자재" not in detail and "delete out.열" in detail
|
||||
|
||||
|
||||
def test_저장_왕복_고름_자재표시_자재_남음(world): # noqa: F811
|
||||
line = {"로직": NET, "고름": {"4": NET_ITEM}, "자재표시": {"3": {"자재구분": "관급"}}}
|
||||
mats = [
|
||||
{"코드": MAT, "수량": {"손": 2}, "운반비": "미포함", "이름": "틀린 이름"},
|
||||
{"코드": WIRE, "수량": None},
|
||||
]
|
||||
_, got = _save(world["system"], {"담은로직": [line], "자재": mats})
|
||||
one = got["담은로직"][0]
|
||||
assert one["고름"] == {"4": NET_ITEM} and one["자재표시"] == {"3": {"자재구분": "관급"}}
|
||||
assert [(m["코드"], m["수량"]) for m in got["자재"]] == [(MAT, {"손": 2}), (WIRE, None)]
|
||||
assert got["자재"][0]["운반비"] == "미포함" and got["자재"][0]["이름"] != "틀린 이름"
|
||||
# 자재 없음 = 빈 목록(옛 문서 꼴 그대로 읽힘)
|
||||
_, empty = _save(world["system"], {"담은로직": [{"로직": NET}]})
|
||||
assert empty["자재"] == []
|
||||
|
||||
|
||||
def test_로직_자재와_같은_품목_400_없는_자재_400(world): # noqa: F811
|
||||
with pytest.raises(store.StoreError) as e:
|
||||
_save(world["system"], {"담은로직": [{"로직": NET}], "자재": [{"코드": WIRE}]})
|
||||
assert e.value.status == 400 and WIRE in str(e.value.detail)
|
||||
with pytest.raises(store.StoreError) as e:
|
||||
_save(world["system"], {"담은로직": [], "자재": [{"코드": "MT999999"}]})
|
||||
assert e.value.status == 400
|
||||
|
||||
|
||||
# ── 175-2 셈 ──────────────────────────────────────────────────────────
|
||||
def _price(code):
|
||||
whole = m01.loaded()[1]
|
||||
return Decimal(str(labor.material_price(whole, code, None)["기준단가"]))
|
||||
|
||||
|
||||
def _world_combo(world): # noqa: F811
|
||||
"""STONE 1 + MAT 손 2 + WIRE 빈칸(안 셈) · NET 고름 4 = NET_ITEM — (키, 손셈 1 단위 값)."""
|
||||
lines = [
|
||||
{"로직": STONE, "수량": {"손": 1}},
|
||||
{"로직": NET, "수량": {"손": 0}, "고름": {"4": NET_ITEM}},
|
||||
]
|
||||
mats = [{"코드": MAT, "수량": {"손": 2}}, {"코드": WIRE, "수량": None}]
|
||||
with store.at(world["system"]):
|
||||
key = refs.create("precombo", {"이름": "자재 조합", "담은로직": lines, "자재": mats})[
|
||||
"이름"
|
||||
]
|
||||
stone = Decimal(str(labor.run_line(m01.loaded()[1], STONE, {})["기준"]["계"]))
|
||||
return key, stone + _price(MAT) * 2
|
||||
|
||||
|
||||
def test_사전조합_값에_자재_금액_빈칸은_안_셈_안_줄에_자재(world): # noqa: F811
|
||||
key, want = _world_combo(world)
|
||||
with store.at(world["system"]):
|
||||
doc = labor.clean(
|
||||
{"열": "rv", "담은로직": [{"참조": {**SYS, "키": key}, "수량": {"손": 3}}]}
|
||||
)
|
||||
one = labor.calc(doc, {})["줄"][0]
|
||||
assert one["기준"]["계"] == pytest.approx(float(want))
|
||||
assert one["반영"]["계"] == pytest.approx(float(want * 3))
|
||||
mats = [x for x in one["안"] if x.get("자재")]
|
||||
assert [(x["자재"], x["수량"]) for x in mats] == [(MAT, 2)] # WIRE 빈칸 = 안 셈
|
||||
assert mats[0]["기준"]["재료비"] == pytest.approx(float(_price(MAT)))
|
||||
# 목록 기준금액(51-4)도 같은 펼침
|
||||
rows = [{"종류": "precombo", "이름": key}]
|
||||
refs.summaries(world["system"], rows)
|
||||
assert rows[0]["기준금액"] == pytest.approx(float(want))
|
||||
|
||||
|
||||
def test_B08_참조_줄_재료에_사전조합_자재_손_곱(world): # noqa: F811
|
||||
from B08_Quantity.B08_Quantity_Router_Material import _ref_mats
|
||||
|
||||
key, _ = _world_combo(world)
|
||||
with store.at(world["system"]):
|
||||
outer = refs.create(
|
||||
"precombo",
|
||||
{"이름": "겹", "담은로직": [{"참조": {**SYS, "키": key}, "수량": {"손": 3}}]},
|
||||
)["이름"]
|
||||
got = _ref_mats(m01.loaded()[1], refs.read_ref, {**SYS, "키": outer})
|
||||
mats = [(m["품목"]["키"], q) for m, q in got if m.get("자재")]
|
||||
assert mats == [(MAT, pytest.approx(6.0))] # 2 × 겹 손 3 · WIRE 빈칸 없음
|
||||
|
||||
|
||||
def test_B09_겹_대가에_자재_줄(world): # noqa: F811
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import Builder
|
||||
|
||||
key, _ = _world_combo(world)
|
||||
ref = {**SYS, "키": key}
|
||||
build = Builder()
|
||||
bid = build.precombo(m01.loaded()[1], ref, refs.read_ref(ref), {})
|
||||
mats = [x for x in build.blocks[bid]["lines"] if x["kind"] == "material"]
|
||||
assert [(x["key"], x["qty"]) for x in mats] == [(MAT, 2.0), (WIRE, None)]
|
||||
assert mats[0]["name"] and mats[0]["unit"]
|
||||
|
||||
|
||||
def test_사본_단가_묶음에_사전조합_자재_고름_사본_값_같음(world): # noqa: F811
|
||||
from M02_MasterTemplete import M02_Item_Prices as prices
|
||||
from M02_MasterTemplete import M02_Item_Source as source
|
||||
|
||||
key, want = _world_combo(world)
|
||||
doc = {"열": "rv", "담은로직": [{"참조": {**SYS, "키": key}, "수량": {"손": 1}}], "자재": []}
|
||||
with store.at(world["system"]):
|
||||
rows = source.labor_doc_rows(labor.clean(doc))
|
||||
mats = refs.frozen_materials(rows["참조사본"])
|
||||
assert {MAT, WIRE, NET_ITEM} <= set(mats)
|
||||
assert "refs.frozen_materials(" in open(source.__file__, encoding="utf-8").read()
|
||||
bundle = prices.to_json(prices.collect(refs.frozen_logics(rows["참조사본"]), mats))
|
||||
(world["system"] / "precombo" / f"{key}.json").unlink() # 원본이 사라져도 굳힌 사본
|
||||
got = prices.calc(rows, bundle, {}, {}, None)["줄"][0]
|
||||
assert got["기준"]["계"] == pytest.approx(float(want), rel=1e-9)
|
||||
@@ -125,7 +125,7 @@ def test_화면_칸_부품_사용자_줄_로직_줄_같은_칸():
|
||||
assert "...markCells(mat, on?.mark" in rows and "...markHeads()," in rows
|
||||
assert "...markCells(" in _src("M02_MasterTemplete_Labor_LogicMats.ts")
|
||||
detail = _src("M02_MasterTemplete_Labor_Detail.ts")
|
||||
assert "mark: lib ? undefined : markLogicMat" in detail
|
||||
assert "mark: markLogicMat," in detail and "lib ? undefined" not in detail # 사전조합도(175-1)
|
||||
assert "mark: markMat" in detail
|
||||
# 서버 허용 값 = 화면 허용 값
|
||||
api = _src("M02_MasterTemplete_Labor_Api.ts")
|
||||
|
||||
Reference in New Issue
Block a user