feat(m02): 「손 입력」 → 「수기 입력」 · [면적산출에서 잇기] · goArea 걷어 냄(147-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
This commit is contained in:
@@ -63,7 +63,7 @@ export const CARD_HELP: Record<"structure" | "labor" | "area", CardHelp> = {
|
||||
["수정", "일위대가 상세를 엶 — 로직 추가 · 순서 바꾸기 · 다이어그램은 여기"],
|
||||
clear("일위대가"),
|
||||
["로직 줄 누름", "넣을 값 창 — 그 로직의 넣을 값 고침"],
|
||||
["수량산출 칸", "손 입력 · 면적산출 칸 고르기"],
|
||||
["수량산출 칸", "수기 입력 · 면적산출 칸 고르기"],
|
||||
["별칭 칸", "줄에 따로 이름 붙임"],
|
||||
["줄 끝 단추", "그 줄을 뺌"],
|
||||
["자재 추가", "자재를 골라 자재목록에 넣음"],
|
||||
@@ -72,7 +72,7 @@ export const CARD_HELP: Record<"structure" | "labor" | "area", CardHelp> = {
|
||||
steps: [
|
||||
"왼쪽 표에서 구조물 항목을 고름",
|
||||
"로직 줄을 눌러 넣을 값을 고침",
|
||||
"수량산출 칸에서 손 입력 또는 면적산출 칸을 고름",
|
||||
"수량산출 칸에서 수기 입력 또는 면적산출 칸을 고름",
|
||||
"[저장] — 로직을 더하거나 순서를 바꿀 때는 [수정]",
|
||||
],
|
||||
},
|
||||
|
||||
@@ -319,7 +319,6 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
}
|
||||
: {}),
|
||||
onChanged: (dirty) => laborCard.saver.mark(dirty && laborWritable()),
|
||||
goArea: null, // 면적 칸 잇기는 수량 모달 안(43-1) — 카드에서 화면을 옮기지 않음
|
||||
card: true,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ import {
|
||||
diagramTitle,
|
||||
renderComboDiagram,
|
||||
} from "./M02_MasterTemplete_Labor_Diagram";
|
||||
import { openLaborInput, type GoArea } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { openLaborInput } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { openQtyModal } from "./M02_MasterTemplete_Labor_QtyModal";
|
||||
import { openRefEdit } from "./M02_MasterTemplete_Labor_RefEdit";
|
||||
import { afterRowDrag } from "./M02_MasterTemplete_Labor_RowDrag";
|
||||
@@ -79,8 +79,6 @@ export interface LaborDetailOptions {
|
||||
readOnly: boolean;
|
||||
/** 저장 안 한 고침이 생기거나 사라짐 */
|
||||
onChanged: (dirty: boolean) => void;
|
||||
/** 「면적산출에서 잇기」 — 그 열 면적산출 상세로(옮겨 가면 참 · 취소 = 거짓 · 없으면 단추 없음) */
|
||||
goArea: GoArea | null;
|
||||
/** 구조물 조합 카드 갈래(43-4) — 단가산출 로직 표 + 자재목록만 · 위로 · 아래로 · 로직 추가 ·
|
||||
* 자재 추가 · 정보(단위 · 본뜬) · 다이어그램 없음 · 옵션 · 삭제 · 줄 · 수량 모달은 상세와 같음 */
|
||||
card?: boolean;
|
||||
@@ -318,7 +316,6 @@ export function mountLaborDetail(
|
||||
unit,
|
||||
readOnly: !edit,
|
||||
sheet: { doc: area?.doc ?? null, layer: opt.layer, column: opt.column, pill },
|
||||
goArea: opt.goArea,
|
||||
...(lib ? { handOnly: true } : {}),
|
||||
done: (r) => {
|
||||
if (r.kind === "keep") return;
|
||||
|
||||
@@ -16,9 +16,6 @@ import "./M02_MasterTemplete_Labor.css";
|
||||
|
||||
const fill = (text: string, value: string): string => text.replace("{value}", value);
|
||||
|
||||
/** 면적산출 상세로 감 — 옮겨 갔으면 참(물음에서 취소 = 거짓) */
|
||||
export type GoArea = () => Promise<boolean>;
|
||||
|
||||
export interface LaborInputResult {
|
||||
입력값: LaborValues;
|
||||
메모: string | null;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Labor_QtyModal.ts
|
||||
* 일위대가 수량산출 모달(43-1) — 수량산출 칸 누름(로직 줄 · 자재 줄 같음) = 모달 하나.
|
||||
* · 위 = 지금 수량(손 · 칸 · —) · 손 입력 칸 · [연결 풀기]
|
||||
* · 위 = 지금 수량(수기 · 칸 · —) · 수기 입력 칸 · [연결 풀기]
|
||||
* · 아래 = 그 열 면적산출 시트를 모달 안에 바로(읽기만 · 실제 값 · 이은 칸 색 · `mountAreaSheet`) ·
|
||||
* 칸 누름 = 그 칸을 고름(함께 잇기 · 바꾸기 물음 그대로)
|
||||
* · 머리 [확인] = 손 값이 있으면 손 · 아니면 고른 칸으로 잇기 · 풀기 · 머리 [닫기] = 그대로
|
||||
* · 머리 [확인] = 수기 값이 있으면 수기 · 아니면 고른 칸으로 잇기 · 풀기 · 머리 [닫기] = 그대로
|
||||
* 잇기 · 풀기는 화면 문서만(저장은 일위대가 [저장] 때 면적 문서와 함께 · `Labor_Detail`).
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -13,7 +13,6 @@ import { openModal } from "@ui/ui_template_modal";
|
||||
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import type { Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { mountAreaSheet, type AreaCellPick } from "./M02_MasterTemplete_AreaCellPick";
|
||||
import type { GoArea } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
||||
import "./M02_Labor_Values.css";
|
||||
import "./M02_MasterTemplete_Labor.css";
|
||||
@@ -29,20 +28,18 @@ export type QtyResult =
|
||||
export interface QtyModalOptions {
|
||||
/** 「GF000219 돌쌓기 메쌓기(인력)」 */
|
||||
title: string;
|
||||
/** 지금 수량 글(「손 5 ㎥」 · 「B6 = 12.5」 · 「—」) */
|
||||
/** 지금 수량 글(「수기 5 ㎥」 · 「B6 = 12.5」 · 「—」) */
|
||||
now: string;
|
||||
/** 지금 칸에 이어져 있나 — [연결 풀기] */
|
||||
linked: boolean;
|
||||
/** 지금 손 값(없으면 null) */
|
||||
hand: number | null;
|
||||
/** 손 입력 단위(분모 · 자재 단위) */
|
||||
/** 수기 입력 단위(분모 · 자재 단위) */
|
||||
unit: string;
|
||||
readOnly: boolean;
|
||||
/** 그 열 면적산출 — `pill` 이 없으면(계산 전) 시트 대신 안내 */
|
||||
sheet: { doc: Workbook | null; layer: Layer; column: string; pill: string | null };
|
||||
/** [면적산출 상세로] — 없으면 단추 없음 */
|
||||
goArea: GoArea | null;
|
||||
/** 손 입력만(서고 사전조합 줄 · 51-6) — 면적산출 시트 없음 */
|
||||
/** 수기 입력만(서고 사전조합 줄 · 51-6) — 면적산출 시트 없음 */
|
||||
handOnly?: boolean;
|
||||
done: (result: QtyResult) => void;
|
||||
}
|
||||
@@ -92,16 +89,6 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
},
|
||||
})
|
||||
: null;
|
||||
const go = opt.goArea
|
||||
? createButton({
|
||||
label: tc("In_QtyGoArea"),
|
||||
variant: "ghost",
|
||||
onClick: () =>
|
||||
void opt.goArea?.().then((moved) => {
|
||||
if (moved) finish();
|
||||
}),
|
||||
})
|
||||
: null;
|
||||
const host = el("div", {
|
||||
className: "m02-cellpick__sheet",
|
||||
attrs: { "data-labor-qty-sheet": "" },
|
||||
@@ -153,7 +140,6 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
],
|
||||
}),
|
||||
...(unlink ? [unlink] : []),
|
||||
...(go ? [go] : []),
|
||||
],
|
||||
}),
|
||||
state,
|
||||
|
||||
@@ -66,7 +66,6 @@ export async function openRefEdit(opt: RefEditOptions): Promise<void> {
|
||||
column: opt.ref.키,
|
||||
readOnly: opt.readOnly,
|
||||
onChanged: (d) => (dirty = d && !opt.readOnly),
|
||||
goArea: null,
|
||||
mode: "precombo",
|
||||
owner: opt.ref.주인,
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ const BOOK = /^(건설|산림)품셈/;
|
||||
const num = (v: number): string =>
|
||||
Number(v.toPrecision(12)).toLocaleString("ko-KR", { maximumFractionDigits: 6 });
|
||||
|
||||
/** 수량 글 — 「수량 손 3 개소」 · 「수량 12.5 ㎡(B6)」(칸 = 그 알약을 이은 면적산출 칸 · 44-6) ·
|
||||
/** 수량 글 — 「수량 수기 입력 3 개소」 · 「수량 12.5 ㎡(B6)」(칸 = 그 알약을 이은 면적산출 칸 · 44-6) ·
|
||||
* 이은 칸이 비었으면 「수량 빈 칸(A1)」(수량 없음 · 서버와 같음) · 아니면 빈 글 */
|
||||
export function qtyText(
|
||||
line: { 수량?: number | null; 수량출처?: string; 알약?: string | null } | undefined,
|
||||
@@ -103,7 +103,7 @@ export function baseQtyText(
|
||||
return `${num(line.배 || 1)} ${line.분모}`;
|
||||
}
|
||||
|
||||
/** 지금 수량 짧은 글 — 「손 5 ㎥」 · 「12.5(B6)」 · 「빈 칸(B6)」 · 없으면 「—」 */
|
||||
/** 지금 수량 짧은 글 — 「수기 5 ㎥」 · 「12.5(B6)」 · 「빈 칸(B6)」 · 없으면 「—」 */
|
||||
export function qtyShort(
|
||||
line: { 수량?: number | null; 수량출처?: string; 알약?: string | null } | undefined,
|
||||
unit: string,
|
||||
@@ -117,7 +117,7 @@ export function qtyShort(
|
||||
return `${num(line.수량)}(${at ?? tc("In_QtyCell")})`;
|
||||
}
|
||||
|
||||
/** 수량산출 칸(40-1) — 위 결과단위 기준 수량(「10 ㎡」) · 아래 지금 수량(「손 5 ㎥」 · 「12.5(B6)」 · 「—」) ·
|
||||
/** 수량산출 칸(40-1) — 위 결과단위 기준 수량(「10 ㎡」) · 아래 지금 수량(「수기 5 ㎥」 · 「12.5(B6)」 · 「—」) ·
|
||||
* `open` 이 있으면 단추(줄 누름과 따로) */
|
||||
function qtyCell(
|
||||
line: LaborCalcLine | undefined,
|
||||
@@ -204,7 +204,7 @@ export interface RowHandlers {
|
||||
option?: (i: number) => void;
|
||||
/** 참조 줄 단추(51-14) — 일위조합 [수정] = 서고 원본 모달 · 없으면 단추 없음 */
|
||||
ref?: (i: number) => void;
|
||||
/** 수량산출 칸 누름(손 입력 · 엑셀에서 고르기) — 없으면 줄 누름과 같음 */
|
||||
/** 수량산출 칸 누름(수기 입력 · 엑셀에서 고르기) — 없으면 줄 누름과 같음 */
|
||||
qty?: (i: number) => void;
|
||||
/** 줄 순서(85-3) — from 줄을 to 자리로(▲▼ = 옆 자리 · 줄 끌기 102-2 = 놓은 줄 자리) */
|
||||
move?: (from: number, to: number) => void;
|
||||
|
||||
@@ -70,8 +70,8 @@ const TEXT = {
|
||||
Labor_Qty: ["수량", "Quantity"],
|
||||
Labor_QtyCol: ["수량산출", "Quantity"],
|
||||
Labor_Item: ["항목", "Item"],
|
||||
Labor_Hand: ["손", "Manual"],
|
||||
Labor_QtyPick: ["누르면 손 입력 · 엑셀에서 고르기", "Manual or pick a cell"],
|
||||
Labor_Hand: ["수기", "Manual"],
|
||||
Labor_QtyPick: ["누르면 수기 입력 · 엑셀에서 고르기", "Manual or pick a cell"],
|
||||
Labor_PickWait: ["계산이 끝난 뒤 고름", "Wait for the calculation"],
|
||||
Labor_NoArea: ["이 열 면적산출을 못 읽음", "Area sheet not loaded"],
|
||||
Labor_BadCell: ["칸 주소가 틀림", "Invalid cell"],
|
||||
@@ -131,9 +131,8 @@ const TEXT = {
|
||||
Labor_Layer: ["켜쌓기 {n}%", "Layered {n}%"],
|
||||
In_QtyCell: ["면적산출 칸", "Area sheet cell"],
|
||||
In_QtyEmpty: ["빈 칸", "empty cell"],
|
||||
In_QtyHand: ["손 입력", "Manual"],
|
||||
In_QtyGoArea: ["면적산출에서 잇기", "Link in area sheet"],
|
||||
In_QtyNeed: ["손 입력 수량을 수로 적음", "Enter a number"],
|
||||
In_QtyHand: ["수기 입력", "Manual"],
|
||||
In_QtyNeed: ["수기 입력 수량을 수로 적음", "Enter a number"],
|
||||
In_Bad: ["범위 밖 값이 있음 — 고친 뒤 확인", "Out-of-range values — fix first"],
|
||||
In_Ok: ["확인", "OK"],
|
||||
In_Close: ["닫기", "Close"],
|
||||
|
||||
@@ -423,13 +423,6 @@ export function createMain(
|
||||
column: at.name,
|
||||
readOnly,
|
||||
onChanged: (d) => (dirty = d && !readOnly),
|
||||
goArea: mode
|
||||
? null
|
||||
: async () => {
|
||||
if (!(await confirmLeave())) return false; // 취소 — 입력 모달 그대로
|
||||
void open({ ...at, kind: "area" });
|
||||
return true;
|
||||
},
|
||||
...(mode ? { mode, owner: at.owner ?? null } : {}),
|
||||
});
|
||||
// [저장] = 엑셀에서 고른 칸 연결(면적 문서) 먼저 · 그다음 일위대가(서버가 새 칸 값으로 계산값)
|
||||
|
||||
@@ -184,11 +184,11 @@ def test_수량_글은_이은_칸_주소를_보임() -> None:
|
||||
assert out.returncode == 0, out.stderr
|
||||
got = json.loads(out.stdout)
|
||||
# 수량산출 열(40-1) — 위 결과단위 기준 수량 · 아래 지금 수량 짧은 글
|
||||
assert got[:7] == ["10 ㎡", "1 ㎥", "", "손 5 ㎥", "12.5(B6)", "빈 칸(B6)", "—"]
|
||||
assert got[:7] == ["10 ㎡", "1 ㎥", "", "수기 5 ㎥", "12.5(B6)", "빈 칸(B6)", "—"]
|
||||
assert got[7:] == [
|
||||
"수량 20 ㎡(B6)", # 44-6 — 수량(칸 주소)
|
||||
"수량 20 ㎡(면적산출 칸)",
|
||||
"수량 손 입력 3 ㎡",
|
||||
"수량 수기 입력 3 ㎡",
|
||||
"수량 빈 칸(B6)", # 이은 칸이 비었음 = 수량 없음(39-8 ② · 서버 area_values 와 같음)
|
||||
"",
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
· 147-1 곁값 `actions`(머리 [닫기] 바로 왼쪽 · 넘긴 창만) · `wide`(96vw × 94vh · 몸만 스크롤)
|
||||
· 147-2 연결창 단추 넷 → 머리 · 큰 창 · 글 묻는 창 [확인] 머리 · [취소] 없음
|
||||
· 147-3 M02 입력 · 수량산출 · 참조 창 — 아래 단추 줄 없음 · [확인] 머리 · 읽기만이면 없음
|
||||
· 147-4 「손 입력」 → 「수기 입력」 · 짧은 글 「손」 → 「수기」 · [면적산출에서 잇기] · goArea 없음
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -80,3 +81,15 @@ def test_147_3_m02_창_셋_머리_확인():
|
||||
assert "if (!opt.readOnly)" in ts and "head.append(" in ts, name
|
||||
assert "In_Cancel" not in _text(f"{m02}Labor_Text.ts")
|
||||
assert ".m02-labin__foot" not in _text(f"{m02}Labor.css")
|
||||
|
||||
|
||||
def test_147_4_수기_입력_잇기_없음():
|
||||
m02 = ROOT / "M02_MasterTemplete"
|
||||
text = _text("M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts")
|
||||
assert 'In_QtyHand: ["수기 입력", "Manual"],' in text
|
||||
assert 'Labor_Hand: ["수기", "Manual"],' in text
|
||||
assert "In_QtyGoArea" not in text
|
||||
assert "손 입력" not in _text("M02_MasterTemplete/M02_MasterTemplete_CardHelp.ts")
|
||||
for path in m02.glob("*.ts"):
|
||||
src = path.read_text(encoding="utf-8")
|
||||
assert "goArea" not in src and "GoArea" not in src, path.name
|
||||
|
||||
Reference in New Issue
Block a user