feat(717-4): 일위대가 [로직 추가] 「단가산출」 탭 · B09 재료 창고 단가산출 출처 · 단가산출 더한줄 고르기
- 새 M02_MasterTemplete_Sangeun_Pick — listSangeun = 위 층 SK 목록(마스터 › 회사 › 개인 · 금액 = 층 목록 기준금액) · openSangeunLinePicker = SK 더한줄 고르기(M01 로직 · 일위대가 · 단가산출 · M01 재료 → 715 더한줄 꼴)
- Labor_Pick — lib.sangeun 이면 탭 [기준 목록 | 단가산출] · 단가산출 탭 = 기준 목록 표 꼴(출처 = 단가산출 · 층) · 줄 누름 = 참조 {층, 주인, SK 키}
- withHopyo(일위대가 [로직 추가]) 에 sangeun 켬 · B09 단가연결 창은 그대로(SK 공사 연결 = 719-1 뒤)
- B09 재료 창고 출처 탭 「단가산출」 — SK 목록 · 연결은 알림만
- locale_m2 키 넷 · 시험 test_717_4_sangeun_pick
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Store.ts
|
||||
* B09 설계내역서 오른쪽 재료 창고(PLAN 703-1) — 찾기 하나 + 출처 탭(이 공사 · 내 것 · 회사 · 품셈 · 지난 공사).
|
||||
* B09 설계내역서 오른쪽 재료 창고(PLAN 703-1) — 찾기 하나 + 출처 탭(이 공사 · 내 것 · 회사 · 단가산출 · 품셈 · 지난 공사).
|
||||
* 단가산출(717-4) = 위 층 M02 SK 목록(`Sangeun_Pick` · 마스터 › 회사 › 개인) · 공사 연결은 아직 없음(목록만)
|
||||
*
|
||||
* 두 번 누름 · Enter = 고른 내역 줄(`…_Bill_Level` 고름)에 단가연결 · 끌어 놓기 = 놓은 줄에 단가연결 ·
|
||||
* 우클릭 = [고른 줄에 연결] · 품셈 = [계산서 보기](704-2 `Labor_Sheet` · [담기] = 연결)
|
||||
@@ -31,17 +32,19 @@ import {
|
||||
} from "../M02_MasterTemplete/M02_MasterTemplete_Labor_Api";
|
||||
import { openStandardSheet } from "../M02_MasterTemplete/M02_MasterTemplete_Labor_Sheet";
|
||||
import { ownOwner } from "../M02_MasterTemplete/M02_MasterTemplete_Layer";
|
||||
import { listSangeun } from "../M02_MasterTemplete/M02_MasterTemplete_Sangeun_Pick";
|
||||
import type { Row } from "./B09_Estimation_Calc_Base";
|
||||
import type { TabContext } from "./B09_Estimation_UI_Tabs";
|
||||
import { applyAt, openMenuAt } from "./B09_Estimation_UI_Tabs_Bill_Edit";
|
||||
import { askInputs, importLib, type ApplyRef } from "./B09_Estimation_UI_Tabs_Bill_Link";
|
||||
import { pickedKey } from "./B09_Estimation_UI_Tabs_Bill_Level";
|
||||
|
||||
type Source = "here" | "mine" | "company" | "poomsem" | "past";
|
||||
type Source = "here" | "mine" | "company" | "sangeun" | "poomsem" | "past";
|
||||
const SOURCES: Array<[Source, string]> = [
|
||||
["here", "이 공사"],
|
||||
["mine", "내 것"],
|
||||
["company", "회사"],
|
||||
["sangeun", "단가산출"],
|
||||
["poomsem", "품셈"],
|
||||
["past", "지난 공사"],
|
||||
];
|
||||
@@ -115,6 +118,28 @@ async function layerItems(layer: "personal" | "company"): Promise<Item[]> {
|
||||
];
|
||||
}
|
||||
|
||||
/** 단가산출(717-4) — 위 층 SK(마스터 › 회사 › 개인) · 공사 연결 길 없음 = 알림만 */
|
||||
async function sangeunItems(): Promise<Item[]> {
|
||||
const user = await fetchSessionUser().catch(() => null);
|
||||
const own = (["company", "personal"] as const)
|
||||
.map((층) => ({ 층, 주인: ownOwner(층, user) }))
|
||||
.filter((x) => x.주인);
|
||||
const layerText: Record<string, string> = { system: "마스터", company: "회사", personal: "개인" };
|
||||
return (await listSangeun([{ 층: "system", 주인: null }, ...own])).map((r) => {
|
||||
const name = r.제목 || r.이름;
|
||||
const sub = [r.구분, r.상세구분].filter(Boolean).join(" ");
|
||||
const money = r.기준금액 === null ? "" : `${Math.round(r.기준금액).toLocaleString("ko-KR")}원`;
|
||||
return {
|
||||
tag: `단가산출 · ${layerText[r.ref.층] ?? r.ref.층}`,
|
||||
name,
|
||||
sub: [sub, money].filter(Boolean).join(" · "),
|
||||
unit: r.단위 ?? "",
|
||||
hit: has(`단가산출 ${name} ${sub}`),
|
||||
go: () => showToast("단가산출은 아직 공사에 바로 연결하지 않음 — 목록 보기만", "info"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** 넣을 값 창 — 대표 규격 = 계산서(704-2)가 셈에 쓴 입력 전부(목록 `바꿈` 은 기본과 다른 것만 ·
|
||||
* 기본을 다르게 보는 칸이 빠짐 — 석축헐기 뒷길이 60 · 0) · 못 받으면 `바꿈` */
|
||||
function askStandard(apply: ApplyRef, r: StandardRow): void {
|
||||
@@ -200,6 +225,7 @@ async function pastItems(): Promise<Item[]> {
|
||||
const LOAD: Record<Exclude<Source, "here">, () => Promise<Item[]>> = {
|
||||
mine: () => layerItems("personal"),
|
||||
company: () => layerItems("company"),
|
||||
sangeun: sangeunItems,
|
||||
poomsem: poomsemItems,
|
||||
past: pastItems,
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ export function withHopyo(lib: LibraryPick): LibraryPick {
|
||||
const scope = { layer: lib.layer, owner: lib.owner };
|
||||
return {
|
||||
...lib,
|
||||
sangeun: true, // 717-4 — 일위대가 [로직 추가] 「단가산출」 탭
|
||||
kindFilter: lib.kindFilter ?? [
|
||||
{ value: "logic", text: tc("Kind_Logic") },
|
||||
{ value: "precombo", text: tc("Kind_Combo") },
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
* 704-1 단가산출 줄 = 기준 목록 표(품명 · 규격 · 단위 · 금액 · 출처 · 대표 규격 펼침 · 공종 거름 ·
|
||||
* 실무 낱말 = 서버가 별칭에 덧붙임) · 금액 = 서버 M01 엔진(`/logic-lib/standard`) · 오기 전엔 로직 줄만 ·
|
||||
* 줄 누름 = 계산서 보기(704-2 · `Labor_Sheet`) → [담기] = 지금 담기 동작(`done(로직)`)
|
||||
* 717-4 `lib.sangeun` = 탭 [기준 목록 | 단가산출] · 단가산출 = 위 층 SK(마스터 › 회사 › 개인 · `Sangeun_Pick`)를
|
||||
* 같은 표 꼴(출처 = 단가산출 · 층) · 줄 누름 = `lib.pick` 참조(SK 키)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, createSelectField, el, showToast } from "@ui/ui_template_elements";
|
||||
@@ -37,6 +39,8 @@ import "../M01_MasterData/M01_MasterData_UI_Logic_Style.css"; // 창 겉틀
|
||||
import "./M02_MasterTemplete_Labor.css";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createTabs } from "@ui/ui_template_tabs";
|
||||
import { listSangeun, type SangeunRow } from "./M02_MasterTemplete_Sangeun_Pick";
|
||||
|
||||
export { withHopyo } from "./M02_MasterTemplete_Hopyo_Pick";
|
||||
|
||||
@@ -71,6 +75,8 @@ export interface LibraryPick {
|
||||
more?: () => Promise<PickExtra[]>;
|
||||
/** 종류 고르기 칸(251 · 값 logic · precombo · 덧붙인 줄 kind) */
|
||||
kindFilter?: Array<{ value: string; text: string }>;
|
||||
/** 717-4 「단가산출」 탭(M02 SK · 위 층 모두) — 고르면 `pick` 참조 */
|
||||
sangeun?: boolean;
|
||||
}
|
||||
|
||||
/** 고를 수 있는 층 — 위 층만(마스터 → 마스터 · 회사 → 그 회사 · 마스터 · 개인 → 그 개인 · 그 회사 · 마스터) */
|
||||
@@ -107,6 +113,9 @@ export function openLogicPicker(
|
||||
const detail = createSelectField({ options: opts([]), compact: true, label: tc("Detail") });
|
||||
detail.select.disabled = true;
|
||||
let close = (): void => {};
|
||||
let rowPick: Array<() => void> = []; // 표 줄 누름 — 탭마다 다시 셈
|
||||
let tab: "std" | "sk" = "std";
|
||||
let skRows: SangeunRow[] | null = null; // 717-4 — 오기 전 = null
|
||||
|
||||
/* 한 목록 — 구분(단가산출 · 일위조합) 칸 · 층 단추는 서고 줄(사전조합)의 층 */
|
||||
const layerBar = el("div", { className: "ui-btn-group", attrs: { "data-pick-layers": "" } });
|
||||
@@ -141,15 +150,7 @@ export function openLogicPicker(
|
||||
{ key: "출처", label: tc("Std_Source"), width: 150 },
|
||||
],
|
||||
rows: [],
|
||||
onRowClick: (i) => {
|
||||
const row = listed[i];
|
||||
const logic = all.find((x) => x.키 === row?.키);
|
||||
if (row && logic)
|
||||
openStandardSheet(row, () => {
|
||||
close();
|
||||
done(logic);
|
||||
});
|
||||
},
|
||||
onRowClick: (i) => rowPick[i]?.(),
|
||||
});
|
||||
/** 기준 목록이 오기 전 · 못 받으면 — 로직 하나 = 줄 하나(금액 빈칸) */
|
||||
const plain = (x: LogicSummary): StandardRow => ({
|
||||
@@ -170,6 +171,7 @@ export function openLogicPicker(
|
||||
|
||||
const find = (): void => {
|
||||
const q = finder.search.value.trim().toLowerCase();
|
||||
if (tab === "sk") return findSangeun(q);
|
||||
const rows = std ?? all.map(plain);
|
||||
const found = rows.filter(
|
||||
(x) =>
|
||||
@@ -200,6 +202,14 @@ export function openLogicPicker(
|
||||
? `${shown} / ${total}`
|
||||
: tc("Pick_None");
|
||||
listed = found;
|
||||
rowPick = listed.map((row) => () => {
|
||||
const logic = all.find((x) => x.키 === row.키);
|
||||
if (logic)
|
||||
openStandardSheet(row, () => {
|
||||
close();
|
||||
done(logic);
|
||||
});
|
||||
});
|
||||
table.root.hidden = !!lib?.libOnly;
|
||||
table.setRows(
|
||||
found.map((x) => ({
|
||||
@@ -240,6 +250,32 @@ export function openLogicPicker(
|
||||
})),
|
||||
]);
|
||||
};
|
||||
/** 717-4 단가산출 탭 — 같은 표 꼴 · 찾기 = 이름 · 제목 · 구분 */
|
||||
const findSangeun = (q: string): void => {
|
||||
const rows = (skRows ?? []).filter(
|
||||
(x) => !q || [x.이름, x.제목, x.구분, x.상세구분].join(" ").toLowerCase().includes(q),
|
||||
);
|
||||
finder.hint.textContent = !skRows
|
||||
? tc("Loading")
|
||||
: rows.length
|
||||
? `${rows.length} / ${skRows.length}`
|
||||
: tc("Pick_None");
|
||||
rowPick = rows.map((x) => () => {
|
||||
close();
|
||||
lib?.pick(x.ref, x.제목 || x.이름);
|
||||
});
|
||||
table.root.hidden = false;
|
||||
table.setRows(
|
||||
rows.map((x) => ({
|
||||
품명: x.제목 || x.이름,
|
||||
규격: [x.구분, x.상세구분].filter(Boolean).join(" "),
|
||||
단위: x.단위 ?? "",
|
||||
금액: x.기준금액,
|
||||
출처: `${L("M02_PickSangeun")} · ${tc(LAYER_TEXT[x.ref.층 as keyof typeof LAYER_TEXT])}`,
|
||||
})),
|
||||
);
|
||||
finder.setRows([]);
|
||||
};
|
||||
const loadLib = async (): Promise<void> => {
|
||||
if (!lib || !scope) return;
|
||||
const mine = ++seq;
|
||||
@@ -303,12 +339,46 @@ export function openLogicPicker(
|
||||
finder.search.addEventListener("input", find);
|
||||
|
||||
layerBar.style.display = "none";
|
||||
const hint = createText(tc("Std_Hint"));
|
||||
/** 탭 바꿈 — 단가산출 탭은 찾기 칸만(거름 · 층 단추 · 계산서 안내 숨김) */
|
||||
const tabs = lib?.sangeun
|
||||
? createTabs({
|
||||
tabs: [
|
||||
{ key: "std", label: L("M02_PickStd") },
|
||||
{ key: "sk", label: L("M02_PickSangeun") },
|
||||
],
|
||||
value: tab,
|
||||
kind: "line",
|
||||
onChange: (key) => {
|
||||
tab = key === "sk" ? "sk" : "std";
|
||||
const std = tab === "std";
|
||||
for (const f of [kindSel?.root, work.root, sub.root, detail.root])
|
||||
if (f) f.style.display = std ? "" : "none";
|
||||
layerBar.style.display = std && scopes.length ? "" : "none";
|
||||
hint.hidden = !std;
|
||||
find();
|
||||
},
|
||||
})
|
||||
: null;
|
||||
if (lib)
|
||||
void upper(lib.layer, lib.owner).then((got) => {
|
||||
scopes = got;
|
||||
if (tabs)
|
||||
listSangeun(got).then(
|
||||
(rows) => {
|
||||
skRows = rows;
|
||||
tabs.setBadge("sk", rows.length);
|
||||
find();
|
||||
},
|
||||
(error) => {
|
||||
skRows = [];
|
||||
find();
|
||||
showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error");
|
||||
},
|
||||
);
|
||||
scope = got[got.length - 1] ?? null; // 기본 = 가장 가까운 층
|
||||
if (!got.length) return;
|
||||
layerBar.style.display = "";
|
||||
layerBar.style.display = tab === "std" ? "" : "none";
|
||||
drawLayers();
|
||||
void loadLib();
|
||||
void fetchStructureDef().then(
|
||||
@@ -331,7 +401,7 @@ export function openLogicPicker(
|
||||
body.append(
|
||||
el("div", {
|
||||
className: "m02-std-pick",
|
||||
children: [finder.root, createText(tc("Std_Hint")), table.root],
|
||||
children: [...(tabs ? [tabs.root] : []), finder.root, hint, table.root],
|
||||
}),
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Sangeun_Pick.ts
|
||||
* M02 단가산출(SK) 고르기(PLAN 717-4) — 위 층 SK 목록 한 벌 · 단가산출 더한줄 고르기.
|
||||
* · `listSangeun` = 층 목록 길(`/layers/{층}/templates` · 종류 `sangeun`)을 마스터 › 회사 › 개인 차례로 이음 ·
|
||||
* 금액 = 서버가 붙인 1 단위 `기준금액` · 일위대가 [로직 추가] 「단가산출」 탭 · B09 재료 창고 출처가 씀
|
||||
* · 고른 SK = 참조 `{층, 주인, 키: SK-…}` — 일위대가 줄은 옛 참조 줄 꼴(서버 `M02_Labor_Expand` 가 펼침)
|
||||
* · `openSangeunLinePicker` = 단가산출 더한줄 고르기 — 같은 [로직 추가] 창(M01 로직 · 일위대가 · 단가산출 ·
|
||||
* M01 재료) → SK 문서 `더한줄` 한 줄(PLAN 715 이음 꼴)
|
||||
* ========================================================================== */
|
||||
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import { call, type Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import type { LaborRef } from "./M02_MasterTemplete_Labor_Calc";
|
||||
import { openLogicPicker } from "./M02_MasterTemplete_Labor_Pick";
|
||||
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
||||
import { pickMaterial } from "./M02_Material_Pick";
|
||||
|
||||
export const SANGEUN_KIND = "sangeun";
|
||||
/** 층 차례 — 위에서 아래 */
|
||||
export const LAYER_ORDER = ["system", "company", "personal"] as const;
|
||||
|
||||
/** SK 목록 한 줄 */
|
||||
export interface SangeunRow {
|
||||
ref: LaborRef;
|
||||
이름: string;
|
||||
제목: string | null;
|
||||
단위: string | null;
|
||||
구분: string | null;
|
||||
상세구분: string | null;
|
||||
기준금액: number | null;
|
||||
}
|
||||
|
||||
interface ListRow {
|
||||
종류: string;
|
||||
이름: string;
|
||||
제목?: string | null;
|
||||
단위?: string | null;
|
||||
구분?: string | null;
|
||||
상세구분?: string | null;
|
||||
기준금액?: number | null;
|
||||
}
|
||||
|
||||
/** 위 층 SK — 층 차례(마스터 › 회사 › 개인) · 층 안은 서버 차례 */
|
||||
export async function listSangeun(scopes: Omit<LaborRef, "키">[]): Promise<SangeunRow[]> {
|
||||
const rank = (x: Omit<LaborRef, "키">): number =>
|
||||
LAYER_ORDER.indexOf(x.층 as (typeof LAYER_ORDER)[number]);
|
||||
const sorted = [...scopes].sort((a, b) => rank(a) - rank(b));
|
||||
const lists = await Promise.all(
|
||||
sorted.map(async (at) => {
|
||||
const q = at.주인 && at.층 !== "system" ? `?owner=${encodeURIComponent(at.주인)}` : "";
|
||||
const got = await call<{ 양식: ListRow[] }>(`/layers/${at.층}/templates${q}`);
|
||||
return got.양식
|
||||
.filter((r) => r.종류 === SANGEUN_KIND)
|
||||
.map<SangeunRow>((r) => ({
|
||||
ref: { 층: at.층, 주인: at.층 === "system" ? null : at.주인, 키: r.이름 },
|
||||
이름: r.이름,
|
||||
제목: r.제목 ?? null,
|
||||
단위: r.단위 ?? null,
|
||||
구분: r.구분 ?? null,
|
||||
상세구분: r.상세구분 ?? null,
|
||||
기준금액: r.기준금액 ?? null,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
return lists.flat();
|
||||
}
|
||||
|
||||
/** 단가산출 더한줄 한 줄(PLAN 715 이음 꼴) */
|
||||
export type SangeunLine =
|
||||
| { 종류: "로직"; 로직: string; 입력값: Record<string, never>; 수량: number }
|
||||
| { 참조: LaborRef; 수량: number }
|
||||
| { 종류: "재료"; 요소: string; 이름: string | null; 단위: string | null; 수량: number };
|
||||
|
||||
/** 단가산출 더한줄 고르기 — `scope` = 그 SK 문서 층(고를 수 있는 서고 = 위 층만) */
|
||||
export function openSangeunLinePicker(
|
||||
scope: { layer: Layer; owner: string | null },
|
||||
done: (line: SangeunLine) => void,
|
||||
): void {
|
||||
const element = L("M02_PickElement");
|
||||
openLogicPicker((x) => done({ 종류: "로직", 로직: x.키, 입력값: {}, 수량: 1 }), {
|
||||
...scope,
|
||||
sangeun: true,
|
||||
pick: (ref) => done({ 참조: ref, 수량: 1 }),
|
||||
kindFilter: [
|
||||
{ value: "logic", text: tc("Kind_Logic") },
|
||||
{ value: "precombo", text: L("M02_PrecomboTitle") },
|
||||
{ value: "element", text: element },
|
||||
],
|
||||
extra: [
|
||||
{
|
||||
kind: "element",
|
||||
key: "element:material",
|
||||
tag: element,
|
||||
name: L("M02_PickMaterial"),
|
||||
sub: "",
|
||||
unit: "",
|
||||
onPick: () =>
|
||||
pickMaterial((m) =>
|
||||
done({ 종류: "재료", 요소: m.코드, 이름: m.이름, 단위: m.단위, 수량: 1 }),
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
"""717-4 단가산출(SK) 고르기 — SK 목록 층 차례 · 고른 SK 참조 꼴 · 일위대가 줄 금액."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from resources.tester.test_m02_labor_ref import COMPANY, MINE, world # noqa: F401 — 같은 임시 층
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
M02 = ROOT / "M02_MasterTemplete"
|
||||
PCT = "GC001109" # 원/㎡ · 인력 둘 + 덧줄 %
|
||||
|
||||
|
||||
def _text(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_sk_list_layer_order_and_ref_shape():
|
||||
"""목록 = 마스터 › 회사 › 개인 차례 · 종류 sangeun 만 · 고른 줄 = 참조 {층, 주인, 키}."""
|
||||
pick = _text(M02 / "M02_MasterTemplete_Sangeun_Pick.ts")
|
||||
order = re.search(r"LAYER_ORDER = \[([^\]]+)\]", pick).group(1)
|
||||
assert [x.strip().strip('"') for x in order.split(",")] == ["system", "company", "personal"]
|
||||
assert "[...scopes].sort((a, b) => rank(a) - rank(b))" in pick
|
||||
assert "r.종류 === SANGEUN_KIND" in pick and 'SANGEUN_KIND = "sangeun"' in pick
|
||||
assert 'ref: { 층: at.층, 주인: at.층 === "system" ? null : at.주인, 키: r.이름 }' in pick
|
||||
labor = _text(M02 / "M02_MasterTemplete_Labor_Pick.ts")
|
||||
assert "lib?.pick(x.ref, x.제목 || x.이름);" in labor # SK 탭 줄 누름 = 참조
|
||||
assert 'if (tab === "sk") return findSangeun(q);' in labor
|
||||
assert "sangeun: true," in _text(
|
||||
M02 / "M02_MasterTemplete_Hopyo_Pick.ts"
|
||||
) # 일위대가 [로직 추가]
|
||||
store_ts = _text(ROOT / "B09_Estimation" / "B09_Estimation_UI_Store.ts")
|
||||
assert '["sangeun", "단가산출"],' in store_ts and "sangeun: sangeunItems," in store_ts
|
||||
# 더한줄 고르기 — 715 이음 꼴 셋
|
||||
assert 'done({ 종류: "로직", 로직: x.키, 입력값: {}, 수량: 1 })' in pick
|
||||
assert "done({ 참조: ref, 수량: 1 })" in pick
|
||||
assert 'done({ 종류: "재료", 요소: m.코드, 이름: m.이름, 단위: m.단위, 수량: 1 })' in pick
|
||||
|
||||
|
||||
def _sk(folder, name):
|
||||
with store.at(folder):
|
||||
return refs.create(
|
||||
"sangeun", {"이름": name, "단위": "㎡", "바탕": {"로직": PCT}, "더한줄": []}
|
||||
)["이름"]
|
||||
|
||||
|
||||
def test_list_amount_and_labor_line_holds_upper_sk(world): # noqa: F811
|
||||
"""층 목록 SK 줄에 1 단위 `기준금액` · 개인 일위대가 줄이 회사 SK 를 참조 = SK 1 단위 × 수량."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
sk = _sk(world["company"], "회사 단가산출")
|
||||
with store.at(world["company"]):
|
||||
rows = [r for r in store.list_all() if r["종류"] == "sangeun"]
|
||||
refs.summaries(world["company"], rows)
|
||||
one = calc.calc_doc(store.read("sangeun", sk)["문서"])["1단위"]["계"]
|
||||
assert [r["이름"] for r in rows] == [sk]
|
||||
assert rows[0]["제목"] == "회사 단가산출" and rows[0]["기준금액"] == pytest.approx(one, abs=1)
|
||||
with store.at(world["personal"]):
|
||||
sc = refs.create(
|
||||
"precombo",
|
||||
{"이름": "일위대가", "담은로직": [{"참조": {**COMPANY, "키": sk}, "수량": {"손": 3}}]},
|
||||
)["이름"]
|
||||
saved = store.read("precombo", sc)["문서"]["담은로직"][0]["참조"]
|
||||
got = labor.calc({"담은로직": [{"참조": {**MINE, "키": sc}, "수량": {"손": 1}}]}, {})
|
||||
assert saved == {**COMPANY, "키": sk}
|
||||
assert got["반영합"]["계"] == pytest.approx(one * 3, abs=1)
|
||||
@@ -191,4 +191,8 @@ export const ui_locales_m2 = {
|
||||
M02_HopyoImport: ["일위대가 불러오기", "Import unit-price sheets"],
|
||||
B01_Dashboard_CardPrecombo: ["일위대가", "Unit-cost items"],
|
||||
M02_PrecomboTitle: ["일위대가", "Unit-cost items"],
|
||||
M02_PickStd: ["기준 목록", "Standard list"],
|
||||
M02_PickSangeun: ["단가산출", "Unit-cost calcs"],
|
||||
M02_PickElement: ["M01 요소", "M01 elements"],
|
||||
M02_PickMaterial: ["재료 고르기", "Pick a material"],
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user