feat(M02): 일위대가 단가산출 로직 표 44-6 — 수량(칸) 표기 · 옵션 값 걸친 한 줄 · 전체 합계 강조 · 별칭 열
- 칸 연결 표기 「B6 = 12.5」 → 「12.5(B6)」(수량산출 칸 · 수량 모달 지금 수량 · 자재 줄) - 옵션 값 = 줄 아래 이름 ~ 합계 칸에 걸친 한 줄(말줄임 · 전체 글 title) - 총합계 행 전체 합계 칸 테두리 · 색 - 별칭 열(이름 옆 · 칸 안 글상자 · 담은로직 줄 별칭 저장 · 서버 검사 40자 · 복사 · 가져오기 같이 감) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""M02 일위대가 문서 `labor/<열id>.json` — 정리 · 검사 · 계산 · 알약(PLAN 39-1a · 설계_정함 1장).
|
||||
|
||||
문서 = `{종류, 판, 열, 일위대가번, 단위, 본뜬, 담은로직: [{로직, 메모, 입력값, 수량}],
|
||||
문서 = `{종류, 판, 열, 일위대가번, 단위, 본뜬, 담은로직: [{로직, 별칭, 메모, 입력값, 수량}],
|
||||
자재: [{코드, 이름, 규격, 단위, 수량}], 계산값}` — 이름은 구조물 열 이름(따로 안 둠).
|
||||
수량 = `null`(면적산출 알약 칸 값 · 로직 알약 「이름|단위」 · 자재 알약 「자재|MT키」) | `{손: 수}`
|
||||
(손이 칸보다 앞섬) · 같은 로직 두 번 금지 · 입력값 = 그 로직 입력 이름만(빈 값 = 자동값).
|
||||
@@ -26,6 +26,7 @@ mm = mf.mp.mm
|
||||
COSTS = mf.COST_ITEMS # 노무비 · 재료비 · 경비
|
||||
MATERIAL_HEAD = "자재|"
|
||||
_MAT_COLS = ("코드", "이름", "규격", "단위")
|
||||
ALIAS_MAX = 40 # 별칭(44-6) 글자 수 상한 — 화면 칸과 같음
|
||||
|
||||
|
||||
def _bad(text: str) -> store.StoreError:
|
||||
@@ -83,10 +84,16 @@ def clean(doc: Any) -> dict[str, Any]:
|
||||
given = one.get("입력값") or {}
|
||||
if not isinstance(given, dict):
|
||||
raise _bad(f"{where} · 입력값이 묶음 아님")
|
||||
memo = one.get("메모")
|
||||
memo, alias = one.get("메모"), one.get("별칭")
|
||||
if alias is not None and not isinstance(alias, str):
|
||||
raise _bad(f"{where} · 별칭은 글")
|
||||
alias = (alias or "").strip()
|
||||
if len(alias) > ALIAS_MAX:
|
||||
raise _bad(f"{where} · 별칭은 {ALIAS_MAX}자까지")
|
||||
lines.append(
|
||||
{
|
||||
"로직": code,
|
||||
"별칭": alias or None,
|
||||
"메모": str(memo) if memo else None,
|
||||
"입력값": {k: v for k, v in given.items() if k in names and v not in (None, "")},
|
||||
"수량": _qty(one.get("수량"), where),
|
||||
|
||||
@@ -78,6 +78,59 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 옵션 값 줄(44-6) — 이름 ~ 합계에 걸친 한 줄 말줄임(max-width 0 = 표 폭을 안 늘림) · 위 줄과 한 덩이 */
|
||||
.m02-labrow__opt {
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
padding-top: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-caption);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.m02-labrow__row:has(+ .m02-labrow__optrow) td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.m02-labrow__optrow.is-open {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-labrow__optrow.is-open:hover,
|
||||
.m02-labrow__row.is-open:hover + .m02-labrow__optrow {
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
/* 별칭 칸(44-6) — 칸 안 글상자 · 누르면 바로 고침 */
|
||||
.m02-labrow__alias {
|
||||
min-width: 7rem;
|
||||
}
|
||||
|
||||
.m02-labrow__aliasin {
|
||||
width: 100%;
|
||||
min-width: 6rem;
|
||||
padding: 2px var(--spacing-4);
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.m02-labrow__aliasin:hover,
|
||||
.m02-labrow__aliasin:focus {
|
||||
border-color: var(--color-primary);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/* 총합계 줄 전체 합계 칸(44-6) — 테두리 · 색 */
|
||||
.m02-labrow__sum .m02-labrow__total {
|
||||
border: 2px solid var(--color-primary);
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* 입력 모달 */
|
||||
.m02-labin {
|
||||
width: min(40rem, calc(100vw - 32px));
|
||||
@@ -234,6 +287,17 @@
|
||||
order: 3;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 옵션 값 줄 — 한 줄 통째 */
|
||||
.m02-labrow__optrow .m02-labrow__no,
|
||||
.m02-labrow__sum .m02-labrow__alias {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.m02-labrow .m02-labrow__opt {
|
||||
grid-column: 1 / -1;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 수량산출 칸(40-1) — 위 결과단위 기준 수량 · 아래 지금 수량 · 누르면 손 입력 · 엑셀에서 고르기 */
|
||||
|
||||
@@ -121,6 +121,8 @@ export type LaborQty = { 손: number } | null;
|
||||
/** 담은 로직 한 줄 — 입력값 = 그 로직 입력 이름만(빈 값 = 자동값) */
|
||||
export interface LaborRow {
|
||||
로직: string;
|
||||
/** 사용자가 지은 이름(44-6 · 빈 것 = null) */
|
||||
별칭?: string | null;
|
||||
메모: string | null;
|
||||
입력값: Record<string, string | number>;
|
||||
수량: LaborQty;
|
||||
@@ -153,6 +155,7 @@ export function normalizeLabor(doc: unknown, column: string): LaborDoc {
|
||||
단위: d.단위 ?? null,
|
||||
담은로직: (d.담은로직 ?? []).map((r) => ({
|
||||
로직: r.로직,
|
||||
별칭: r.별칭 ?? null,
|
||||
메모: r.메모 ?? null,
|
||||
입력값: r.입력값 ?? {},
|
||||
수량: r.수량 ?? null,
|
||||
|
||||
@@ -135,7 +135,13 @@ export function mountLaborDetail(
|
||||
{ rows: cur.담은로직, logics, calc, fail, cells: cells() },
|
||||
// 읽기만이면 순서 · 빼기 없음(줄 누름 = 보기 모달은 둠)
|
||||
edit
|
||||
? { open: openRow, qty: openQty, ...(opt.card ? {} : { move }), remove: removeRow }
|
||||
? {
|
||||
open: openRow,
|
||||
qty: openQty,
|
||||
...(opt.card ? {} : { move }),
|
||||
remove: removeRow,
|
||||
alias: setAlias,
|
||||
}
|
||||
: { open: openRow },
|
||||
),
|
||||
);
|
||||
@@ -262,6 +268,11 @@ export function mountLaborDetail(
|
||||
cur.담은로직.splice(i, 1);
|
||||
changed();
|
||||
}
|
||||
/** 별칭(44-6) — 화면 캐시만(저장은 [저장]) · 금액과 무관이라 다시 셈 · 다시 그림 없음 */
|
||||
function setAlias(i: number, value: string | null): void {
|
||||
cur.담은로직[i].별칭 = value;
|
||||
opt.onChanged(dirty());
|
||||
}
|
||||
function removeMat(i: number): void {
|
||||
cur.자재.splice(i, 1);
|
||||
changed();
|
||||
@@ -311,7 +322,7 @@ export function mountLaborDetail(
|
||||
openLogicPicker((x) => {
|
||||
if (cur.담은로직.some((r) => r.로직 === x.키))
|
||||
return showToast(tc("Labor_Already").replace("{value}", `${x.키} ${x.이름}`), "error");
|
||||
cur.담은로직.push({ 로직: x.키, 메모: null, 입력값: {}, 수량: null });
|
||||
cur.담은로직.push({ 로직: x.키, 별칭: null, 메모: null, 입력값: {}, 수량: null });
|
||||
void loadLogics().then(() => changed());
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Labor_Rows.ts
|
||||
* 일위대가 담은 로직 표 · 자재목록 표(39-3) — 상세 · 보기 갈래(면적산출 오른쪽 패널 · 카드)가 같이 씀.
|
||||
* 담은 로직 = 차례 · 로직(키 · 이름 + 작은 글 「지역 서울 · 규격 25mm · 수량 B6 = 12.5㎡」 · 「빈 값 n」 붉게) ·
|
||||
* 결과 단위 · 금액 칸 넷(노무비 · 재료비 · 경비 · 합계 · 칸마다 위 기준 작게 / 아래 반영) · 맨 아래 합계 줄.
|
||||
* 담은 로직 = 차례 · 로직(키 · 이름 + 「빈 값 n」 붉게) · 별칭(44-6) · 수량산출(「12.5(B6)」) ·
|
||||
* 결과 단위 · 금액 칸 넷(노무비 · 재료비 · 경비 · 합계 · 칸마다 위 기준 작게 / 아래 반영) ·
|
||||
* 옵션 값(「지역 서울 · 규격 25mm」)은 줄 아래 이름 ~ 합계에 걸친 한 줄 · 맨 아래 합계 줄(전체 합계 강조).
|
||||
* 자재목록 = 코드 · 이름 · 규격 · 수량산출 · 단위 · 가격 다섯(기준 칸 강조) · 기준단가 / 반영 금액(43-3).
|
||||
* 금액은 calc 답(없으면 「…」).
|
||||
* 줄 누름 · 순서 단추는 `on` 이 있을 때만(보기 갈래 = 없음).
|
||||
@@ -31,11 +32,14 @@ const HEAD: Record<(typeof COSTS)[number], Parameters<typeof tc>[0]> = {
|
||||
계: "Prev_Sum",
|
||||
};
|
||||
|
||||
/** 옵션 값 줄이 걸치는 칸 수 — 이름 · 별칭 · 수량산출 · 결과 단위 · 금액 넷 */
|
||||
const SPAN = 4 + COSTS.length;
|
||||
|
||||
const num = (v: number): string =>
|
||||
Number(v.toPrecision(12)).toLocaleString("ko-KR", { maximumFractionDigits: 6 });
|
||||
|
||||
/** 수량 글 — 「수량 손 3 개소」 · 「수량 B6 = 12.5 ㎡」(칸 = 그 알약을 이은 면적산출 칸) ·
|
||||
* 이은 칸이 비었으면 「수량 A1 = 빈 칸」(수량 없음 · 서버와 같음) · 아니면 빈 글 */
|
||||
/** 수량 글 — 「수량 손 3 개소」 · 「수량 12.5 ㎡(B6)」(칸 = 그 알약을 이은 면적산출 칸 · 44-6) ·
|
||||
* 이은 칸이 비었으면 「수량 빈 칸(A1)」(수량 없음 · 서버와 같음) · 아니면 빈 글 */
|
||||
export function qtyText(
|
||||
line: { 수량?: number | null; 수량출처?: string; 알약?: string | null } | undefined,
|
||||
unit: string,
|
||||
@@ -44,12 +48,12 @@ export function qtyText(
|
||||
if (!line) return "";
|
||||
if (line.수량 === null || line.수량 === undefined) {
|
||||
const at = line.알약 ? cells.get(line.알약) : undefined;
|
||||
return at ? `${tc("Labor_Qty")} ${at} = ${tc("In_QtyEmpty")}` : "";
|
||||
return at ? `${tc("Labor_Qty")} ${tc("In_QtyEmpty")}(${at})` : "";
|
||||
}
|
||||
const v = `${num(line.수량)}${unit ? ` ${unit}` : ""}`;
|
||||
if (line.수량출처 === "손") return `${tc("Labor_Qty")} ${tc("In_QtyHand")} ${v}`;
|
||||
const cell = (line.알약 && cells.get(line.알약)) || tc("In_QtyCell");
|
||||
return `${tc("Labor_Qty")} ${cell} = ${v}`;
|
||||
return `${tc("Labor_Qty")} ${v}(${cell})`;
|
||||
}
|
||||
|
||||
/** 결과단위 기준 수량 — 분모 앞 수 · 분모(「10 ㎡」 · 「1 ㎥」) · 분모 없으면 빈 글 */
|
||||
@@ -60,7 +64,7 @@ export function baseQtyText(
|
||||
return `${num(line.배 || 1)} ${line.분모}`;
|
||||
}
|
||||
|
||||
/** 지금 수량 짧은 글 — 「손 5 ㎥」 · 「B6 = 12.5」 · 「B6 = 빈 칸」 · 없으면 「—」 */
|
||||
/** 지금 수량 짧은 글 — 「손 5 ㎥」 · 「12.5(B6)」 · 「빈 칸(B6)」 · 없으면 「—」 */
|
||||
export function qtyShort(
|
||||
line: { 수량?: number | null; 수량출처?: string; 알약?: string | null } | undefined,
|
||||
unit: string,
|
||||
@@ -68,13 +72,13 @@ export function qtyShort(
|
||||
): string {
|
||||
const at = line?.알약 ? cells.get(line.알약) : undefined;
|
||||
if (!line || line.수량 === null || line.수량 === undefined)
|
||||
return at ? `${at} = ${tc("In_QtyEmpty")}` : "—";
|
||||
return at ? `${tc("In_QtyEmpty")}(${at})` : "—";
|
||||
if (line.수량출처 === "손")
|
||||
return `${tc("Labor_Hand")} ${num(line.수량)}${unit ? ` ${unit}` : ""}`;
|
||||
return `${at ?? tc("In_QtyCell")} = ${num(line.수량)}`;
|
||||
return `${num(line.수량)}(${at ?? tc("In_QtyCell")})`;
|
||||
}
|
||||
|
||||
/** 수량산출 칸(40-1) — 위 결과단위 기준 수량(「10 ㎡」) · 아래 지금 수량(「손 5 ㎥」 · 「B6 = 12.5」 · 「—」) ·
|
||||
/** 수량산출 칸(40-1) — 위 결과단위 기준 수량(「10 ㎡」) · 아래 지금 수량(「손 5 ㎥」 · 「12.5(B6)」 · 「—」) ·
|
||||
* `open` 이 있으면 단추(줄 누름과 따로) */
|
||||
function qtyCell(
|
||||
line: LaborCalcLine | undefined,
|
||||
@@ -155,6 +159,42 @@ export interface RowHandlers {
|
||||
qty?: (i: number) => void;
|
||||
move?: (i: number, d: number) => void;
|
||||
remove?: (i: number) => void;
|
||||
/** 별칭 칸 고침(44-6 · 글자마다 · 빈 글 = null) — 없으면 글만 보임 */
|
||||
alias?: (i: number, value: string | null) => void;
|
||||
}
|
||||
|
||||
/** 별칭 칸(44-6) — 고칠 수 있으면 칸 안 글상자(누르면 바로 고침 · 줄 누름과 따로) · 아니면 글 · 비면 빈 칸 */
|
||||
function aliasCell(
|
||||
value: string | null | undefined,
|
||||
set: ((v: string | null) => void) | null,
|
||||
): HTMLElement {
|
||||
if (!set)
|
||||
return el("td", {
|
||||
className: "m02-labrow__alias",
|
||||
text: value ?? "",
|
||||
attrs: { "data-label": tc("Col_Alias") },
|
||||
});
|
||||
const input = el("input", {
|
||||
className: "m02-labrow__aliasin",
|
||||
attrs: {
|
||||
type: "text",
|
||||
maxlength: "40", // 서버 `ALIAS_MAX` 와 같음
|
||||
title: tc("Alias_Edit"),
|
||||
"aria-label": tc("Col_Alias"),
|
||||
"data-labor-alias": "",
|
||||
},
|
||||
});
|
||||
input.value = value ?? "";
|
||||
input.addEventListener("click", (e) => e.stopPropagation()); // 줄 누름(입력 모달)과 따로
|
||||
input.addEventListener("input", () => set(input.value.trim() || null));
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") input.blur();
|
||||
});
|
||||
return el("td", {
|
||||
className: "m02-labrow__alias",
|
||||
attrs: { "data-label": tc("Col_Alias") },
|
||||
children: [input],
|
||||
});
|
||||
}
|
||||
|
||||
export interface LogicTableData {
|
||||
@@ -175,7 +215,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
const tools = !!(move || remove);
|
||||
const lineOf = (key: string): LaborCalcLine | undefined =>
|
||||
data.calc?.줄.find((l) => l.로직 === key);
|
||||
const trs = data.rows.map((row, i) => {
|
||||
const trs = data.rows.flatMap((row, i) => {
|
||||
const logic = data.logics.get(row.로직);
|
||||
const line = lineOf(row.로직);
|
||||
const unit = logic?.결과단위 ?? "";
|
||||
@@ -201,7 +241,6 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
children: [
|
||||
el("span", { className: "m02-labrow__key", text: row.로직 }),
|
||||
el("strong", { text: logic?.이름 ?? row.로직 }),
|
||||
...(note ? [el("small", { className: "m02-labrow__note", text: note })] : []),
|
||||
...(values.empty
|
||||
? [
|
||||
el("small", {
|
||||
@@ -221,6 +260,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
aliasCell(row.별칭, on?.alias ? (v) => on.alias?.(i, v) : null),
|
||||
qtyCell(line, data.cells, on?.qty ? () => on.qty?.(i) : null),
|
||||
el("td", { className: "m02-labrow__unit", text: unit }),
|
||||
...COSTS.map((c) => moneyCell(line?.기준?.[c], line?.반영?.[c], wait, tc(HEAD[c]))),
|
||||
@@ -240,7 +280,23 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
],
|
||||
});
|
||||
if (on) tr.addEventListener("click", () => on.open(i));
|
||||
return tr;
|
||||
if (!note) return [tr];
|
||||
// 옵션 값 줄(44-6) — 이름 ~ 합계 칸에 걸친 한 줄(말줄임 · 전체 글은 title) · 행 높이 안 커짐
|
||||
const opt = el("tr", {
|
||||
className: on ? "m02-labrow__optrow is-open" : "m02-labrow__optrow",
|
||||
attrs: { "data-labor-opt": row.로직 },
|
||||
children: [
|
||||
el("td", { className: "m02-labrow__no", text: "" }),
|
||||
el("td", {
|
||||
className: "m02-labrow__opt",
|
||||
text: note,
|
||||
attrs: { colspan: String(SPAN), title: note },
|
||||
}),
|
||||
...(tools ? [el("td", { text: "" })] : []),
|
||||
],
|
||||
});
|
||||
if (on) opt.addEventListener("click", () => on.open(i));
|
||||
return [tr, opt];
|
||||
});
|
||||
const sum = (part: Partial<LaborCost> | undefined, c: (typeof COSTS)[number]): number | null =>
|
||||
part?.[c] ?? null;
|
||||
@@ -251,11 +307,19 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
children: [
|
||||
el("td", { className: "m02-labrow__no", text: "" }),
|
||||
el("td", { className: "m02-labrow__name", text: tc("Prev_Sum") }),
|
||||
el("td", { className: "m02-labrow__alias", text: "" }),
|
||||
el("td", { className: "m02-labrow__qty", text: "" }),
|
||||
el("td", { className: "m02-labrow__unit", text: "" }),
|
||||
...COSTS.map((c) =>
|
||||
moneyCell(sum(data.calc?.기준합, c), sum(data.calc?.반영합, c), wait, tc(HEAD[c])),
|
||||
),
|
||||
...COSTS.map((c) => {
|
||||
const td = moneyCell(
|
||||
sum(data.calc?.기준합, c),
|
||||
sum(data.calc?.반영합, c),
|
||||
wait,
|
||||
tc(HEAD[c]),
|
||||
);
|
||||
if (c === "계") td.classList.add("m02-labrow__total"); // 전체 합계 강조(44-6)
|
||||
return td;
|
||||
}),
|
||||
...(tools ? [el("td", { text: "" })] : []),
|
||||
],
|
||||
}),
|
||||
@@ -264,6 +328,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
const heads = [
|
||||
tc("Col_Order"),
|
||||
tc("Col_Name"),
|
||||
tc("Col_Alias"),
|
||||
tc("Labor_QtyCol"),
|
||||
tc("Col_Unit"),
|
||||
...COSTS.map((c) => tc(HEAD[c])),
|
||||
|
||||
@@ -27,6 +27,8 @@ const TEXT = {
|
||||
Logics_Title: ["단가산출 로직", "Unit-price logics"],
|
||||
Col_Order: ["차례", "No."],
|
||||
Col_Name: ["이름", "Name"],
|
||||
Col_Alias: ["별칭", "Alias"],
|
||||
Alias_Edit: ["눌러서 별칭 고침", "Click to edit alias"],
|
||||
Col_Unit: ["결과 단위", "Result unit"],
|
||||
Col_Move: ["순서", "Move"],
|
||||
Up: ["올리기", "Up"],
|
||||
|
||||
@@ -39,7 +39,7 @@ def world(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
|
||||
for cid in ("rv", "c1"):
|
||||
store.create_structure(cid)
|
||||
got = store.read("labor", "rv")
|
||||
logic = [{"로직": TEN, "입력값": {}, "수량": {"손": 2}}]
|
||||
logic = [{"로직": TEN, "별칭": " 돌붙임 ", "입력값": {}, "수량": {"손": 2}}]
|
||||
store.write("labor", "rv", got["판"], {**got["문서"], "단위": "개소", "담은로직": logic})
|
||||
personal = layers.personal_dir(7, 42)
|
||||
items_store.ensure(personal)
|
||||
@@ -71,6 +71,7 @@ def test_같은_층_일위대가_하나_복사_번호_열은_받는_쪽(world):
|
||||
c1 = _labor(system, "c1")
|
||||
assert (c1["열"], c1["일위대가번"]) == ("c1", before["일위대가번"])
|
||||
assert (c1["단위"], c1["담은로직"][0]["로직"]) == ("개소", TEN)
|
||||
assert c1["담은로직"][0]["별칭"] == "돌붙임" # 별칭도 같이(44-6 · 앞뒤 빈칸 뗌)
|
||||
assert c1["계산값"]["반영합"]["계"] > 0 # 서버가 다시 셈
|
||||
|
||||
|
||||
@@ -80,6 +81,7 @@ def test_다른_층에서_내_개인으로_저장_뒤에도_덮어씀(world):
|
||||
for _ in range(2): # 저장된 데이터도 다시 바꿈
|
||||
assert client.post(url).status_code == 200
|
||||
assert _labor(personal, "a")["담은로직"][0]["로직"] == TEN
|
||||
assert _labor(personal, "a")["담은로직"][0]["별칭"] == "돌붙임"
|
||||
area = URL.format("personal", "area", "a") + "?from=rv&from_layer=system"
|
||||
assert client.post(area).status_code == 200
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_정리_모르는_입력_빈_값_버림_자재_칸은_M01_것():
|
||||
)
|
||||
)
|
||||
assert got["담은로직"] == [
|
||||
{"로직": STONE, "메모": None, "입력값": {"돌": "깬돌"}, "수량": None}
|
||||
{"로직": STONE, "별칭": None, "메모": None, "입력값": {"돌": "깬돌"}, "수량": None}
|
||||
]
|
||||
assert got["자재"][0]["이름"] == "치 즐(0.2㎥)" and got["자재"][0]["수량"] == {"손": 2}
|
||||
assert (got["종류"], got["단위"], got["본뜬"]) == ("일위대가", "개소", None)
|
||||
@@ -47,6 +47,8 @@ def test_정리_모르는_입력_빈_값_버림_자재_칸은_M01_것():
|
||||
_doc(담은로직=[{"로직": "GF999999"}]), # 없는 로직
|
||||
_doc(담은로직=[{"로직": STONE, "수량": {"손": "열"}}]), # 수 아님
|
||||
_doc(담은로직=[{"로직": STONE, "수량": 3}]), # 꼴 틀림
|
||||
_doc(담은로직=[{"로직": STONE, "별칭": 3}]), # 별칭 글 아님(44-6)
|
||||
_doc(담은로직=[{"로직": STONE, "별칭": "가" * 41}]), # 별칭 40자 넘음
|
||||
_doc(자재=[{"코드": "LB000001"}]), # 자재품목 아님
|
||||
_doc(자재=[{"코드": MAT}, {"코드": MAT}]), # 같은 자재 두 번
|
||||
"문서 아님",
|
||||
@@ -58,6 +60,13 @@ def test_틀린_문서는_400(bad):
|
||||
assert e.value.status == 400
|
||||
|
||||
|
||||
def test_별칭은_앞뒤_빈칸_떼고_빈_글은_null():
|
||||
got = labor.clean(
|
||||
_doc(담은로직=[{"로직": STONE, "별칭": " 돌붙임 "}, {"로직": TEN, "별칭": " "}])
|
||||
)
|
||||
assert [r["별칭"] for r in got["담은로직"]] == ["돌붙임", None]
|
||||
|
||||
|
||||
def test_빈_일위대가도_됨():
|
||||
got = labor.server(_doc())
|
||||
assert got["계산값"]["기준합"]["계"] == 0 and got["계산값"]["반영합"]["계"] == 0
|
||||
|
||||
@@ -144,7 +144,7 @@ console.log(JSON.stringify([
|
||||
|
||||
|
||||
def test_수량_글은_이은_칸_주소를_보임() -> None:
|
||||
"""`Labor_View.setQty(수량, 칸)` 의 칸 주소로 「수량 B6 = 20 ㎡」 — 없으면 「면적산출 칸」."""
|
||||
"""`Labor_View.setQty(수량, 칸)` 의 칸 주소로 「수량 20 ㎡(B6)」 — 없으면 「면적산출 칸」."""
|
||||
node = shutil.which("node")
|
||||
if not node:
|
||||
pytest.skip("node 없음")
|
||||
@@ -154,11 +154,11 @@ def test_수량_글은_이은_칸_주소를_보임() -> None:
|
||||
assert out.returncode == 0, out.stderr
|
||||
got = json.loads(out.stdout)
|
||||
# 수량산출 열(40-1) — 위 결과단위 기준 수량 · 아래 지금 수량 짧은 글
|
||||
assert got[:7] == ["10 ㎡", "1 ㎥", "", "손 5 ㎥", "B6 = 12.5", "B6 = 빈 칸", "—"]
|
||||
assert got[:7] == ["10 ㎡", "1 ㎥", "", "손 5 ㎥", "12.5(B6)", "빈 칸(B6)", "—"]
|
||||
assert got[7:] == [
|
||||
"수량 B6 = 20 ㎡",
|
||||
"수량 면적산출 칸 = 20 ㎡",
|
||||
"수량 20 ㎡(B6)", # 44-6 — 수량(칸 주소)
|
||||
"수량 20 ㎡(면적산출 칸)",
|
||||
"수량 손 입력 3 ㎡",
|
||||
"수량 B6 = 빈 칸", # 이은 칸이 비었음 = 수량 없음(39-8 ② · 서버 area_values 와 같음)
|
||||
"수량 빈 칸(B6)", # 이은 칸이 비었음 = 수량 없음(39-8 ② · 서버 area_values 와 같음)
|
||||
"",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user