refactor(785-7): 옛 맞춤 화면 TS 지움 · 옛 [저장] · [모두 고정] 단추 걷음 · 390 줄 판 높이
- 지움(git rm) 15 파일 3,318 줄: Hopyo_Check · CheckRows · Choose · WizardSk · SkApi · SkLines · logic/SkPick · logic/Draft · Left · Lost · Pick · SheetMake · Kids · ToItem · Compare(모두 옛 확인 창 · 마법사 단가산출 · 안 이은 줄 길 몫 · 부르는 곳 0) - 단추 판단: 엑셀 보기 [저장](체크 호표) · [모두 고정으로 들여오기] 화면 단추 걷음 → 만들기 = [옮기기] 하나 · 모두 고정 서버 길(Builder · build_all)은 B09 · 붙여넣기가 써서 남김 · 목록 체크 칸 · 모달 껍데기(openHopyoSheet · raise · lower) 걷음 - Import: 「안 이은 줄」 띠 · [안 이은 줄 고르기] · 남은 줄 셈 걷음 · [끝내기] = 물음(확인하고 안 옮긴 수 = 판정 고름 캐시 moveBody) - Api: 옛 맞춤 타입 · 길(readHopyoOne · guessHopyo · buildHopyo · buildHopyoAll · leftHopyo · listHopyoPicks · hopyoSiblings · buildHopyoItem …) 걷음 · POLL_MS · HOPYO_BUILT 옮겨 옴 · 목록 꼴 = 1차 판정 줄상태(Judge.view) - Labor_Pick: withHopyo(실무 호표 덧줄) → withSangeun(찾아 넣기 창 표시만) · Detail · Table 부름 바꿈 - Ledger: unitField 옮겨 옴 · SheetNav: 옛 확인 캐시 칠 걷음 · UI_Page: [구조물 조합으로] 듣기 걷음 - CSS: 옛 확인 창 · 고르기 창 규칙 34 걷음(481 → 287 줄) · 390 폭 = 줄 판 열리면 위 칸 넓힘 · 줄 판 높이 바닥 16rem 시험 정리(까닭 = 지운 화면 파일만 읽음): - 지움: test_729_3_hopyo_left_screen · test_746_2_hopyo_compare_screen · test_778_5_sk_pick_screen · helper_729_3 · helper_m02_hopyo_check · pick · seam - test_m02_hopyo_screen = 좌측 판 · 끝내기 둘만 · 627 · helper_778_5 = SheetSk 판 바꿈만 · 785_4 · 721 · 717_4 · 720 · 739_1 = 지금 꼴로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
/* 729-3 안 이은 줄 화면 헬퍼 — `Hopyo_Left` · `Hopyo_Check` · `Api_Fetch` · `Labor_Unlinked` 도우미를
|
||||
* 트랜스파일해 Node 로 돌림 · fetch 대역(부른 길 · 몸 모음 · 409 몸) · sessionStorage 대역 ·
|
||||
* stdin = {one, made, left, fixes} · stdout = JSON. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const STUBS = { "@config/config_frontend": { API_BASE_URL: "/api", ROUTES: {} } };
|
||||
const STUB_FILES = [
|
||||
"M02_MasterTemplete_Labor_Pick",
|
||||
"M02_Material_Pick",
|
||||
"M02_MasterTemplete_Find_Pick",
|
||||
"M02_MasterTemplete_Labor_Api",
|
||||
"M02_MasterTemplete_Labor_Input",
|
||||
"M01_MasterData_UI_LogicLab_Source",
|
||||
"M01_MasterData_UI_Logic_Api",
|
||||
"M02_MasterTemplete_Hopyo_CheckRows",
|
||||
"M02_MasterTemplete_Find_Pick",
|
||||
];
|
||||
const stub = new Proxy({}, { get: (_t, k) => (k === "currentLanguageIndex" ? () => 0 : () => {}) });
|
||||
const { loadTs } = makeLoader({
|
||||
root: ROOT,
|
||||
requireFor: (full) => (request) => {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
if (STUB_FILES.some((name) => request.endsWith(name))) return stub;
|
||||
if (request.startsWith(".") && !request.endsWith(".css"))
|
||||
return loadTs(`${path.resolve(path.dirname(full), request)}.ts`);
|
||||
return stub;
|
||||
},
|
||||
});
|
||||
global.sessionStorage = memoryStorage();
|
||||
global.document = { dispatchEvent: () => true }; // 캐시 고침 알림(779-4) 대역
|
||||
|
||||
(async () => {
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const calls = [];
|
||||
global.fetch = async (url, init = {}) => {
|
||||
const body = init.body ? JSON.parse(init.body) : null;
|
||||
calls.push({ url, method: init.method ?? "GET", body });
|
||||
if (url.includes("/hopyo-build"))
|
||||
return { status: 409, ok: false, json: async () => ({ detail: { 까닭: "남음", 남은줄: input.left } }) }; // prettier-ignore
|
||||
if (url.includes("/hopyo-unlinked")) {
|
||||
const done = new Set((body.줄 ?? []).map((x) => x.차례));
|
||||
const left = input.left.filter((x) => !done.has(x.차례));
|
||||
return { status: 200, ok: true, json: async () => ({ 남은줄: left, 수: left.length }) };
|
||||
}
|
||||
const no = Number(url.split("?")[0].split("/").pop());
|
||||
const one = { ...input.one, 호표: { ...input.one.호표, 번: no, 만든: input.made.includes(no) ? { 키: "SC-1", 확인: true } : null } }; // prettier-ignore
|
||||
return { status: 200, ok: true, json: async () => one };
|
||||
};
|
||||
const M02 = path.join(ROOT, "M02_MasterTemplete");
|
||||
const check = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Check.ts"));
|
||||
const left = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Left.ts"));
|
||||
const api = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Api.ts"));
|
||||
const grid = loadTs(path.join(M02, "M02_MasterTemplete_Labor_Unlinked.ts"));
|
||||
const scope = { layer: "personal", owner: null };
|
||||
const at = { scope, target: scope, key: "HP-000001" };
|
||||
|
||||
// 만들기 409 → 남은 줄 · 까닭 글
|
||||
let stale = null;
|
||||
try {
|
||||
await api.buildHopyo(scope, { 층: "personal", 주인: null, 키: "HP-000001", 번: 2 }, []);
|
||||
} catch (error) {
|
||||
stale = { message: error.message, left: api.leftOf(error), name: error.constructor.name };
|
||||
}
|
||||
const before = await left.countLeft(at, [1, 2]);
|
||||
// 줄마다 고름 — 노임 · 기계 · 비율 · 우리에 없음 · 로직
|
||||
const [a, b, c, d, e] = input.left;
|
||||
left.put(at, 2, a.차례, [{ 노임: "LB000002", 이름: "보통인부", 입력값: {}, 배: 1 }]);
|
||||
left.put(at, 2, b.차례, [{ 기계: "EQ000069", 이름: "덤프트럭", 입력값: {}, 배: 1 }]);
|
||||
left.put(at, 2, c.차례, [{ 비율: "노무비", 이름: "노무비", 입력값: {}, 배: 1 }]);
|
||||
left.put(at, 2, d.차례, null);
|
||||
const half = await left.countLeft(at, [1, 2]);
|
||||
left.put(at, 2, e.차례, [{ 로직: "GF000113", 이름: "되메우기", 입력값: {}, 배: 1 }]);
|
||||
const after = await left.countLeft(at, [1, 2]);
|
||||
const body = left.draftLines(at, after.get(2).one);
|
||||
const count = (m) => left.leftCount(m);
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
stale,
|
||||
before: count(before),
|
||||
half: count(half),
|
||||
after: count(after),
|
||||
body,
|
||||
unlinkedCalls: calls.filter((x) => x.url.includes("/hopyo-unlinked")).map((x) => x.url),
|
||||
lastBody: calls.filter((x) => x.url.includes("/hopyo-unlinked")).pop().body,
|
||||
fixes: grid.unlinkedFixes({ 담은로직: input.fixes }).map((x) => x.이름),
|
||||
}),
|
||||
);
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error?.stack ?? error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,12 +1,10 @@
|
||||
/* 778-5 D-5 마법사 단가산출 줄 고름 헬퍼 — `logic/Hopyo_SkPick`(순수) · `Hopyo_SkApi`(몸) 를 트랜스파일해 Node 로 돌림 ·
|
||||
* fetch 대역(부른 길 · 몸 모음) · stdin = {blocks} · stdout = JSON.
|
||||
* 780-2 — stdin `{sheetSk: {...}}` = 엑셀 보기 오른쪽 판 바꾸기(`Hopyo_SheetSk`) · 단가산출 칸 · 시트 막대 · 판은 가짜. */
|
||||
/* 780-2 · 785-6 엑셀 보기 오른쪽 판 바꾸기 헬퍼 — stdin `{sheetSk: {...}}` = `Hopyo_SheetSk` 를 트랜스파일해 Node 로 돌림 ·
|
||||
* 단가산출 칸 · 시트 막대 · 판은 가짜 · stdout = JSON. 옛 마법사 줄 고름(`SkPick` · `SkApi`)은 785-7 에서 지움. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const made = []; // 가짜 단가산출 칸 — 지은 인자 · 강조
|
||||
const fakeEl = () => ({
|
||||
hidden: false,
|
||||
attrs: {},
|
||||
@@ -64,7 +62,7 @@ const STUBS = {
|
||||
},
|
||||
},
|
||||
"./M02_MasterTemplete_Hopyo_Block": { createJudgeBlock: (o) => ({ ...o, destroy() {} }) },
|
||||
"./M02_MasterTemplete_Hopyo_Check": { POLL_MS: 3000 },
|
||||
"./M02_MasterTemplete_Hopyo_Api": { POLL_MS: 3000 },
|
||||
"./M02_MasterTemplete_Hopyo_Rows": { createJudgeMove: () => ({ root: fakeEl(), destroy() {} }) },
|
||||
"@config/config_frontend": { API_BASE_URL: "/api", ROUTES: {} },
|
||||
"@ui/ui_template_elements": {
|
||||
@@ -78,17 +76,6 @@ const STUBS = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
"./M02_MasterTemplete_Hopyo_WizardSk": {
|
||||
sangeunStep: (scope, key, file, onChange, o) => {
|
||||
const step = {
|
||||
args: { scope, key, file, o: { ...o, onRow: !!o.onRow } },
|
||||
marks: [],
|
||||
onRow: o.onRow,
|
||||
};
|
||||
made.push(step);
|
||||
return { root: fakeEl(), mark: (n) => step.marks.push(n) };
|
||||
},
|
||||
},
|
||||
};
|
||||
const stub = new Proxy({}, { get: () => () => {} });
|
||||
const { loadTs } = makeLoader({
|
||||
@@ -104,87 +91,7 @@ global.sessionStorage = { getItem: () => null, setItem: () => {}, removeItem: ()
|
||||
|
||||
(async () => {
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
if (input.sheetSk) return process.stdout.write(JSON.stringify(await sheetSk(input.sheetSk)));
|
||||
const { blocks } = input;
|
||||
const calls = [];
|
||||
global.fetch = async (url, init = {}) => {
|
||||
calls.push({
|
||||
url,
|
||||
method: init.method ?? "GET",
|
||||
body: init.body ? JSON.parse(init.body) : null,
|
||||
});
|
||||
return { status: 200, ok: true, json: async () => ({ 판정: blocks, 만듦: [], 못만듦: [] }) };
|
||||
};
|
||||
const M02 = path.join(ROOT, "M02_MasterTemplete");
|
||||
const sk = loadTs(path.join(M02, "logic", "M02_MasterTemplete_Hopyo_SkPick.ts"));
|
||||
const api = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_SkApi.ts"));
|
||||
const [a, b, c] = blocks; // a = 고를 줄 2 · b = 옮길 수 있음 · c = 못 이음(하위)
|
||||
const out = {};
|
||||
let d = sk.emptyDraft();
|
||||
out.start = blocks.map((x) => ({
|
||||
번: x.번,
|
||||
판정: sk.verdictOf(x, {}),
|
||||
확인켜짐: sk.canConfirm(x, {}),
|
||||
}));
|
||||
out.emptyBody = sk.sureBody(d, blocks); // 아무것도 안 눌렀으면 몸 비움(자동 확인 없음)
|
||||
d = sk.setSure(d, a, true); // 고를 줄 남은 블록 = 안 켜짐
|
||||
out.sureBlocked = d.확인;
|
||||
d = sk.setPick(d, a.번, 1, { 노임: "LB000099" }); // 작업반장 → 다른 노임
|
||||
out.half = {
|
||||
판정: sk.verdictOf(a, sk.picksOf(d, a.번)),
|
||||
켜짐: sk.canConfirm(a, sk.picksOf(d, a.번)),
|
||||
};
|
||||
d = sk.setPick(d, a.번, 2, { 자재: "MT000123" });
|
||||
d = sk.setPick(d, a.번, 3, { 뺌: "우리에 없음" });
|
||||
out.view = a.줄.map((l) => sk.lineView(l, sk.picksOf(d, a.번)[String(l.차례)]));
|
||||
out.full = {
|
||||
판정: sk.verdictOf(a, sk.picksOf(d, a.번)),
|
||||
켜짐: sk.canConfirm(a, sk.picksOf(d, a.번)),
|
||||
};
|
||||
d = sk.setSure(d, a, true);
|
||||
d = sk.setSure(d, b, true);
|
||||
d = sk.setSure(d, c, true); // 못 이음 남음 = 안 켜짐
|
||||
out.sure = d.확인;
|
||||
out.body = sk.sureBody(d, blocks);
|
||||
out.wait = sk.waiting(d, blocks);
|
||||
// 고름 지우면 확인에서 빠짐(tidy)
|
||||
out.tidy = sk.tidy(sk.setPick(d, a.번, 2, null), blocks).확인;
|
||||
// 캐시 글 왕복 · 틀린 글
|
||||
out.round = sk.readDraft(JSON.stringify(d));
|
||||
out.bad = sk.readDraft("{엉터리");
|
||||
out.cand = {
|
||||
L: sk.candPick("L", { 키: "LB000001", 이름: "작업반장" }),
|
||||
X: sk.candPick("X", { 키: "EQ000019", 이름: "굴착기" }),
|
||||
M: sk.candPick("M", { 자재: "MT000001", 이름: "시멘트" }),
|
||||
R: sk.candPick("R", { 기준: "노무비", 비목: "경비" }),
|
||||
Mbad: sk.candPick("M", { 키: "LB000001" }),
|
||||
text: sk.candText({ 이름: "합판", 규격: "12mm", 단위: "장" }),
|
||||
};
|
||||
await api.judgeSangeun({ layer: "personal", owner: null }, "HP-000001");
|
||||
await api.judgeSangeun({ layer: "personal", owner: null }, "HP-000001", out.body);
|
||||
out.calls = calls;
|
||||
// 780-2 일위대가 안 · 밖 · 줄 0 블록 — 거르기 · 판정 · 확인 막음 · 끝내기 글 재료
|
||||
const more = [
|
||||
{ ...a, 부름: [3, 7] },
|
||||
{ ...b, 부름: [] },
|
||||
{ ...b, 번: 9, 부름: [], 줄: [] },
|
||||
{ ...c, 부름: [12], 만든: "SK-000001" },
|
||||
];
|
||||
const e = more[2];
|
||||
let d2 = sk.setSure(sk.emptyDraft(), e, true);
|
||||
out.empty = {
|
||||
판정: sk.verdictOf(e, {}),
|
||||
켜짐: sk.canConfirm(e, {}),
|
||||
확인: d2.확인,
|
||||
몸: sk.sureBody({ 고름: {}, 확인: [9] }, more),
|
||||
};
|
||||
d2 = sk.emptyDraft();
|
||||
out.filter = Object.fromEntries(
|
||||
["all", "in", "out", "open"].map((f) => [f, sk.filterBlocks(more, d2, f).map((x) => x.번)]),
|
||||
);
|
||||
out.old = sk.filterBlocks(blocks, d2, "out").length; // 옛 서버(부름 없음) = 모두 안
|
||||
out.wait2 = sk.waiting(d2, more);
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
process.stdout.write(JSON.stringify(await sheetSk(input.sheetSk)));
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error?.stack ?? error));
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
/* M02 일위대가 실무자료 확인 창(338-3) 헬퍼 — `Hopyo_Check` · `Hopyo_Choose` · `Hopyo_Ledger` · `Hopyo_Sheet`
|
||||
* 도우미와 `Hopyo_Api` 길을 트랜스파일해 Node 로 돌림 · fetch 대역이 부른 길 · 몸을 모음 · sessionStorage 대역 ·
|
||||
* stdin = {lines, rows, ledger} · stdout = {check, choose, ledger, sheet, calls, pick} JSON. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const STUBS = { "@config/config_frontend": { API_BASE_URL: "/api" } };
|
||||
// DOM · 모듈 메타를 쓰는 창은 대역(도우미 · 길만 시험)
|
||||
const STUB_FILES = [
|
||||
"M02_MasterTemplete_Labor_Pick",
|
||||
"M02_Material_Pick",
|
||||
"M02_MasterTemplete_Find_Pick",
|
||||
"M02_MasterTemplete_Labor_Api",
|
||||
"M02_MasterTemplete_Labor_Input",
|
||||
"M01_MasterData_UI_LogicLab_Source",
|
||||
"M02_MasterTemplete_Hopyo_Left", // 남은 줄 창(729-3 · DOM · 찾기 창)
|
||||
"M02_MasterTemplete_Hopyo_CheckRows", // 표 그리기(DOM · Labor_Rows) — 359-3
|
||||
];
|
||||
const opened = [];
|
||||
const stub = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_t, k) =>
|
||||
k === "currentLanguageIndex"
|
||||
? () => 0
|
||||
: k === "openModal"
|
||||
? (o) => opened.push(o.title)
|
||||
: () => {},
|
||||
},
|
||||
);
|
||||
const { loadTs } = makeLoader({
|
||||
root: ROOT,
|
||||
requireFor: (full) => (request) => {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
if (STUB_FILES.some((name) => request.endsWith(name))) return stub;
|
||||
if (request.startsWith(".") && !request.endsWith(".css"))
|
||||
return loadTs(`${path.resolve(path.dirname(full), request)}.ts`);
|
||||
return stub;
|
||||
},
|
||||
});
|
||||
|
||||
const store = new Map();
|
||||
global.sessionStorage = {
|
||||
getItem: (k) => (store.has(k) ? store.get(k) : null),
|
||||
setItem: (k, v) => store.set(k, String(v)),
|
||||
removeItem: (k) => store.delete(k),
|
||||
};
|
||||
global.document = { dispatchEvent: () => true }; // 캐시 고침 알림(779-4) 대역
|
||||
|
||||
let out0 = null;
|
||||
(async () => {
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const calls = [];
|
||||
global.fetch = async (url, init = {}) => {
|
||||
calls.push({
|
||||
url,
|
||||
method: init.method ?? "GET",
|
||||
body: init.body ? JSON.parse(init.body) : null,
|
||||
});
|
||||
const body = url.includes("/hopyo-picks") || url.includes("/hopyo-ledger") ? { 줄: [] } : {};
|
||||
return { status: 200, ok: true, json: async () => body };
|
||||
};
|
||||
const M02 = path.join(ROOT, "M02_MasterTemplete");
|
||||
const check = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Check.ts"));
|
||||
const choose = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Choose.ts"));
|
||||
const ledger = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Ledger.ts"));
|
||||
const sheet = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Sheet.ts"));
|
||||
const api = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Api.ts"));
|
||||
const pick = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Pick.ts"));
|
||||
const draft = loadTs(path.join(M02, "logic", "M02_MasterTemplete_Hopyo_Draft.ts"));
|
||||
if (input.drafts) {
|
||||
// 359-3 — 실물 줄 → 미리 셈 문서 · 표(서버 calc 에 그대로 보냄)
|
||||
const d = check.loadDraft("drafts");
|
||||
out0 = input.drafts.map((l) => {
|
||||
const parts = check.partsOf(l, d);
|
||||
return { 차례: l.차례, table: draft.tableOf(l, parts), doc: draft.draftDoc(l, parts) };
|
||||
});
|
||||
process.stdout.write(JSON.stringify(out0));
|
||||
return;
|
||||
}
|
||||
if (input.real) {
|
||||
// 실물 맞춤 응답(338-4) — 서버 줄을 그대로 먹여 체크 · 몸 · 빠질 줄 · 품 비교 · 글
|
||||
const lines = input.real;
|
||||
const d = check.loadDraft("real");
|
||||
out0 = {
|
||||
body: check.bodyOf(lines, d),
|
||||
leftOut: check.leftOut(lines, d),
|
||||
sure: check.sureLines(lines, d).map((l) => l.차례),
|
||||
lines: lines.map((l) => {
|
||||
const p = check.partsOf(l, d)[0];
|
||||
return {
|
||||
이름: l.이름,
|
||||
상태: l.맞춤.상태,
|
||||
checkable: check.checkable(l, d),
|
||||
checked: check.isChecked(l, d),
|
||||
compare: check.compareText(l.맞춤.품),
|
||||
qty: choose.qtyText(l.맞춤.품?.엑셀),
|
||||
target: choose.targetText(p),
|
||||
inputs: p ? choose.inputsText(p.입력값 ?? {}, p.못읽음 ?? [], p.출처 ?? {}) : "",
|
||||
cands: (l.맞춤.후보 ?? []).map((c) => [
|
||||
choose.targetText(c),
|
||||
c.판정 ?? "",
|
||||
choose.basisTag(c.근거),
|
||||
]),
|
||||
};
|
||||
}),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out0));
|
||||
return;
|
||||
}
|
||||
const { lines } = input;
|
||||
const scope = { layer: "personal", owner: null };
|
||||
const key = check.draftKey(scope, "HP-000001", 3);
|
||||
|
||||
// 서버 제안 그대로 → 사용자가 만짐(체크 끔 · 뺌 · 고른 대상) → 캐시 다시 읽음
|
||||
const fresh = check.loadDraft(key);
|
||||
const suggested = check.bodyOf(lines, fresh);
|
||||
const d = check.loadDraft(key);
|
||||
d.체크[1] = false; // 맞음 줄 체크 끔
|
||||
d.뺌[3] = "우리에 없음";
|
||||
d.체크[3] = true;
|
||||
d.대상[4] = [{ 로직: "GF000211", 이름: "고임돌", 입력값: { 유형: "보통" }, 배: 0.1, 몫: 0.1 }];
|
||||
d.체크[4] = true;
|
||||
check.saveDraft(key, d);
|
||||
const again = check.loadDraft(key);
|
||||
const ask = check.newest();
|
||||
const a = ask(1);
|
||||
const b = ask(1);
|
||||
const c = ask(2);
|
||||
const out = {
|
||||
check: {
|
||||
key,
|
||||
suggested,
|
||||
edited: check.bodyOf(lines, again),
|
||||
leftOut: check.leftOut(lines, again),
|
||||
checkable: lines.map((l) => check.checkable(l, fresh)),
|
||||
sure: check.sureLines(lines, fresh).map((l) => l.차례),
|
||||
compare: check.compareText(lines[1].맞춤.품),
|
||||
compareMany: check.compareText(lines[3].맞춤.품),
|
||||
toTarget: [
|
||||
check.toTarget({ 호표: 46, 이름: "고화매트", 입력값: {}, 배: 2.3 }),
|
||||
check.toTarget({ 자재: "MT1", 이름: "레미콘", 입력값: {}, 배: 1, 몫: null }),
|
||||
check.toTarget({ 전국: true, 이름: "레미콘(전국)-관급", 입력값: {}, 배: 1, 몫: null }),
|
||||
],
|
||||
newest: [a(), b(), c()],
|
||||
dropped: (check.dropDraft(key), store.has(key)),
|
||||
},
|
||||
choose: {
|
||||
hint: [
|
||||
choose.searchHint({
|
||||
이름: "레미콘타설(장비사용)",
|
||||
근거: { 출처: [["건설품셈", "6-1-1"]], 직종: [], 행: [1, 1] },
|
||||
}),
|
||||
choose.searchHint({ 이름: "(소)고임돌 채집 및 운반" }),
|
||||
],
|
||||
picked: choose.pickedTargets([
|
||||
{ c: { 로직: "GC000206" }, 몫: null },
|
||||
{ c: { 로직: "GF000211" }, 몫: 0.1 },
|
||||
]),
|
||||
inputs: choose.inputsText({ 소량감률: 50, 지역: "서울" }, ["측압"], {
|
||||
소량감률: "블록",
|
||||
지역: "자동값",
|
||||
}),
|
||||
word: [choose.builtWord({ 다시씀: false }), choose.builtWord({ 다시씀: true })],
|
||||
qty: choose.qtyText(lines[1].맞춤.품.엑셀),
|
||||
target: choose.targetText({ 호표: 46, 단위: "㎡" }),
|
||||
// 358-1 후보 묶음 — 1위 / 출처 같음 / 이름 닮은 로직 / 숫자만 닮음(약한 후보 끝) · 같은 이름 책
|
||||
groups: (() => {
|
||||
const m = {
|
||||
후보: [
|
||||
{ 로직: "A", 이름: "되메우기", 판정: "맞음", 뜻겹침: true, 책: "산림품셈" },
|
||||
{ 로직: "B", 이름: "야면석 채집", 판정: "후보", 뜻겹침: false },
|
||||
{
|
||||
로직: "C",
|
||||
이름: "되메우기",
|
||||
판정: "후보",
|
||||
뜻겹침: true,
|
||||
출처같음: true,
|
||||
책: "건설품셈",
|
||||
},
|
||||
{ 로직: "D", 이름: "표토펴기", 판정: "다른 점", 뜻겹침: false },
|
||||
],
|
||||
이름후보: [
|
||||
{ 로직: "A", 이름: "되메우기" },
|
||||
{ 로직: "E", 이름: "되메우기(노상재)" },
|
||||
],
|
||||
};
|
||||
const g = choose.candGroups(m);
|
||||
const twins = choose.sameNames(g);
|
||||
return {
|
||||
groups: g.map((x) => [x.제목, x.접힘, x.후보.map((c) => c.로직)]),
|
||||
text: g.flatMap((x) => x.후보).map((c) => choose.candText(c, twins)),
|
||||
material: choose
|
||||
.candGroups({ 후보: [{ 자재: "M1" }, { 자재: "M2" }] }, true)
|
||||
.map((x) => x.제목),
|
||||
// 361-1 지역만 다른 자재 — 1위 전국 · 지역 변형 접힘 · 고르면 그 지역 · 전국 되돌림
|
||||
national: (() => {
|
||||
const all = [
|
||||
{ 전국: true, 이름: "레미콘(전국)-관급", 규격: "25-21-120", 단위: "㎥" },
|
||||
{ 자재: "M1", 이름: "레미콘(서울)-사급", 변형: true },
|
||||
{ 자재: "M2", 이름: "레미콘(관급)-강원도(강릉시)", 변형: true },
|
||||
];
|
||||
const g = choose.candGroups({ 후보: all }, true);
|
||||
const id = (c) => c.자재 ?? (c.전국 ? "전국" : "");
|
||||
return {
|
||||
groups: g.map((x) => [x.제목, x.접힘, x.후보.map(id)]),
|
||||
text: choose.candText(all[0], new Set()),
|
||||
region: choose.pickedTargets([{ c: all[2], 몫: null }]),
|
||||
back: choose.pickedTargets([{ c: all[0], 몫: null }]),
|
||||
hit: choose
|
||||
.candGroups({ 후보: [all[2], all[0], all[1]] }, true)
|
||||
.map((x) => [x.제목, x.후보.map(id)]),
|
||||
};
|
||||
})(),
|
||||
pick: [
|
||||
choose.canPickMaterial({ 갈래: "단가산출", 단가: { 재료비: 10 } }),
|
||||
choose.canPickMaterial({ 갈래: "단가산출", 단가: { 재료비: null } }),
|
||||
choose.canPickMaterial({ 갈래: "별도계상", 단가: {} }),
|
||||
],
|
||||
};
|
||||
})(),
|
||||
},
|
||||
ledger: (() => {
|
||||
const rows = input.ledger;
|
||||
const edits = {
|
||||
"가|b|m3": null, // 지움
|
||||
"나||m3": { 원래: { 품명: "나", 규격: "", 단위: "m3" }, 갈래: "안 씀", 짝: [] },
|
||||
"다||개": ledger.entryOf(rows[2]), // 위 층 → 내 층
|
||||
};
|
||||
const merged = ledger.mergeRows(rows, edits, { layer: "personal", owner: "7/42" });
|
||||
return {
|
||||
key: ledger.ledgerKey({ layer: "company", owner: "7" }),
|
||||
merged: merged.map((r) => [r.열쇠, r.층, r.고침, !!r.고친, r.갈래]),
|
||||
filter: ledger.filterRows(merged, "MT1", "").map((r) => r.열쇠),
|
||||
kind: ledger.filterRows(merged, "", "안 씀").map((r) => r.열쇠),
|
||||
pair: ledger.pairText({ 로직: "GC000198", 입력값: { 소량감률: 50 }, 배: 1, 몫: 0.1 }),
|
||||
entry: ledger.entryOf({ ...rows[0], 갈래: "우리에 없음" }),
|
||||
};
|
||||
})(),
|
||||
draft: (() => {
|
||||
// 359-3 — 미리 셈 문서(우리 수량 = 엑셀 × 배 · 별도계상 · 같은 자재 더함 · 같은 로직 둘째 · 호표 · 배 없음 뺌)
|
||||
const L = { 로직: "GC000198", 입력값: { 소량감률: 50 }, 배: 2 };
|
||||
const M = { 자재: "MT1", 입력값: {}, 배: 1 };
|
||||
return {
|
||||
logic: draft.draftDoc({ 수량: 0.5, 갈래: "단가산출" }, [
|
||||
L,
|
||||
{ ...L, 배: 3 },
|
||||
{ 호표: 46, 입력값: {}, 배: 1 },
|
||||
{ 로직: "GF000101", 입력값: {}, 배: null },
|
||||
]),
|
||||
mat: draft.draftDoc({ 수량: 2, 갈래: "별도계상" }, [M, { ...M, 배: 0.5 }]),
|
||||
direct: draft.draftDoc({ 수량: null, 갈래: "자재" }, [M]).자재[0],
|
||||
none: [
|
||||
draft.draftDoc({ 수량: 1, 갈래: "단가산출" }, []),
|
||||
draft.draftDoc({ 수량: 1, 갈래: "호표" }, [{ 호표: 3, 입력값: {}, 배: 1 }]),
|
||||
draft.draftDoc({ 수량: 1, 갈래: "별도계상" }, [{ 전국: true, 입력값: {}, 배: 1 }]),
|
||||
],
|
||||
table: [
|
||||
draft.tableOf({ 갈래: "단가산출" }, [M]),
|
||||
draft.tableOf({ 갈래: "자재" }, [L]),
|
||||
draft.tableOf({ 갈래: "자재" }, [L, M]),
|
||||
draft.tableOf({ 갈래: "별도계상" }, []),
|
||||
draft.tableOf({ 갈래: "%" }, []),
|
||||
draft.tableOf({ 갈래: "별도계상" }, [{ 전국: true, 입력값: {}, 배: 1 }]),
|
||||
],
|
||||
};
|
||||
})(),
|
||||
sheet: {
|
||||
order: sheet.buildOrder([
|
||||
{ 번: 45, 아래: [46] },
|
||||
{ 번: 46, 아래: [] },
|
||||
{ 번: 3, 아래: [] },
|
||||
{ 번: 7, 아래: [8] },
|
||||
{ 번: 8, 아래: [7] },
|
||||
]),
|
||||
linked: [
|
||||
{ 맞춤: [2, 2] },
|
||||
{ 맞춤: [1, 2] },
|
||||
{ 맞춤: [0, 0] },
|
||||
{ 맞춤: [3, 2] },
|
||||
{ 맞춤: [1, 4], 연결할: 3, 연결된: 3 }, // 358-3 두 칸이 앞섬(% · 노임 줄 밖)
|
||||
{ 맞춤: [3, 3], 연결할: 3, 연결된: 2 },
|
||||
{ 맞춤: [0, 1], 연결할: 0, 연결된: 0 },
|
||||
].map(sheet.allLinked),
|
||||
},
|
||||
};
|
||||
// 길 — 만들기(줄 · 통째) · 호표 하나(만들 층) · 미리 셈 · 장부
|
||||
const from = { 층: "system", 주인: null, 키: "HP-000001", 번: 3 };
|
||||
const target = { layer: "company", owner: "7" };
|
||||
await api.buildHopyo(target, from, suggested);
|
||||
await api.buildHopyo(target, from, [], { 대상: [{ 로직: "GF000225", 배: 1 }] });
|
||||
await api.readHopyoOne({ layer: "system", owner: null }, "HP-000001", 3, target);
|
||||
await api.guessHopyo(scope, "HP-000001", 3, 4, [
|
||||
{ 로직: "GF000211", 입력값: {}, 배: 0.1, 몫: 0.1 },
|
||||
]);
|
||||
await api.readLedger(target);
|
||||
await api.saveLedger(target, { "가|b|m3": null });
|
||||
out.calls = calls.splice(0);
|
||||
// [로직 추가] 실무 호표 — 확인된 것 = 바로 참조 · 아닌 것 = 확인 창(만들기 길 안 부름)
|
||||
const picked = [];
|
||||
const lib = {
|
||||
layer: "company",
|
||||
owner: "7",
|
||||
pick: (ref, name, alias) => picked.push({ ref, name, alias }),
|
||||
};
|
||||
for (const row of input.rows) await pick.pickHopyo(row, lib);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
out.pick = { picked, calls: calls.splice(0), opened };
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error?.stack ?? error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,115 +0,0 @@
|
||||
/* M02 일위대가 실무자료(324-4 · 338-3) 헬퍼 — `M02_MasterTemplete_Hopyo_Pick.ts`(고르기 창 「실무 호표」 덧줄) ·
|
||||
* `M02_MasterTemplete_Hopyo_Choose.ts` 글 도우미 · `M02_MasterTemplete_Hopyo_Sheet.ts` 엑셀 보기 창 도우미(329 · 행 → 호표)를
|
||||
* 트랜스파일해 Node 로 돌림 · fetch 대역이 부른 길 · 몸을 모음 ·
|
||||
* stdin = {rows, built} · stdout = {calls, kinds, rows, picked, project, view, sheet} JSON. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const STUBS = { "@config/config_frontend": { API_BASE_URL: "/api" } };
|
||||
// 고르기 창 · 자재 창 · 입력 창 · 원문 창은 DOM · 모듈 메타를 씀 — 대역(글 도우미만 시험)
|
||||
const STUB_FILES = [
|
||||
"M02_MasterTemplete_Labor_Pick",
|
||||
"M02_Material_Pick",
|
||||
"M02_MasterTemplete_Find_Pick",
|
||||
"M02_MasterTemplete_Labor_Api",
|
||||
"M02_MasterTemplete_Labor_Input",
|
||||
"M01_MasterData_UI_LogicLab_Source",
|
||||
"M02_MasterTemplete_Hopyo_Left", // 남은 줄 창(729-3 · DOM · 찾기 창)
|
||||
];
|
||||
const stub = new Proxy({}, { get: (_t, k) => (k === "currentLanguageIndex" ? () => 0 : () => {}) });
|
||||
const { loadTs } = makeLoader({
|
||||
root: ROOT,
|
||||
requireFor: (full) => (request) => {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
if (STUB_FILES.some((name) => request.endsWith(name))) return stub;
|
||||
if (request.startsWith(".") && !request.endsWith(".css"))
|
||||
return loadTs(`${path.resolve(path.dirname(full), request)}.ts`);
|
||||
return stub;
|
||||
},
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const calls = [];
|
||||
global.fetch = async (url, init = {}) => {
|
||||
calls.push({
|
||||
url,
|
||||
method: init.method ?? "GET",
|
||||
body: init.body ? JSON.parse(init.body) : null,
|
||||
});
|
||||
const body = url.includes("/hopyo-picks") ? { 줄: input.rows } : input.built;
|
||||
return { status: 200, ok: true, json: async () => body };
|
||||
};
|
||||
const M02 = path.join(ROOT, "M02_MasterTemplete");
|
||||
const pick = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Pick.ts"));
|
||||
const picked = [];
|
||||
const lib = {
|
||||
layer: "company",
|
||||
owner: "7",
|
||||
pick: (ref, name, alias) => picked.push({ ref, name, alias }),
|
||||
};
|
||||
const wrapped = pick.withHopyo(lib);
|
||||
const rows = (await wrapped.more()).map(({ onPick: _, ...r }) => r);
|
||||
await pick.pickHopyo(input.rows[0], lib);
|
||||
const project = pick.withHopyo({ ...lib, layer: "project" });
|
||||
const view = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Choose.ts"));
|
||||
const sheet = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Sheet.ts"));
|
||||
const api = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Api.ts"));
|
||||
await api.readHopyoGrid({ layer: "company", owner: "7" }, "HP-000001");
|
||||
const spans = [
|
||||
{ 번: 3, 이름: "임도표지석", 행: [20, 24] },
|
||||
{ 번: 4, 이름: "배수관", 행: [25, 30] },
|
||||
];
|
||||
const out = {
|
||||
calls,
|
||||
kinds: wrapped.kindFilter.map((k) => k.value),
|
||||
rows,
|
||||
picked,
|
||||
project: project.more === undefined && project.kindFilter === undefined,
|
||||
view: {
|
||||
target: [
|
||||
view.targetText({ 로직: "GF000101", 이름: "구조물터파기", 단위: "㎥" }),
|
||||
view.targetText({ 자재: "MT1", 이름: "레미콘", 규격: "25-21-120", 단위: "㎥" }),
|
||||
view.targetText({ 호표: 46, 단위: "㎡" }),
|
||||
],
|
||||
inputs: view.inputsText({ 횟수: "4회사용시", 지역: "서울" }, ["보정작업"]),
|
||||
},
|
||||
basis: (() => {
|
||||
const hit = {
|
||||
조항: ["건설 공통 3-2-1"],
|
||||
직종: ["특별인부"],
|
||||
단위: true,
|
||||
점수: 1.65,
|
||||
까닭: "조항 같음 · 직종 특별인부 · 단위 맞음",
|
||||
};
|
||||
const half = { ...hit, 직종: [] };
|
||||
const trade = { 출처: [["건설품셈", "3-2-1"]], 직종: ["특별인부", "보통인부"], 행: [1, 1] };
|
||||
const plain = { ...trade, 직종: ["보통인부"] };
|
||||
return {
|
||||
ok: [hit, half, { ...hit, 단위: false }, { ...hit, 까닭: "앞 마디 같음" }, undefined].map(
|
||||
(b) => view.basisOk(b, trade),
|
||||
),
|
||||
// 엑셀 직종이 보통인부뿐 · 없음(기계) = 직종 안 봄
|
||||
plain: [view.basisOk(half, plain), view.basisOk(half, { ...trade, 직종: [] })],
|
||||
tag: [hit, half, undefined].map(view.basisTag),
|
||||
source: [
|
||||
view.sourceText({ 출처: [["건설표준품셈", "3-2-1"]], 직종: ["특별인부"], 행: [93, 101] }),
|
||||
view.sourceText({ 출처: [], 직종: [], 행: [7, 7] }),
|
||||
view.sourceText(undefined),
|
||||
],
|
||||
};
|
||||
})(),
|
||||
sheet: {
|
||||
at: [19, 20, 22, 24, 25, 30, 31].map((row) => sheet.hopyoAtRow(spans, row)),
|
||||
span: sheet.spanText([20, 24], sheet.lastColumn({ 칸: { A1: {}, N24: {} }, 열: { O: {} } })),
|
||||
last: [sheet.lastColumn({ 칸: { Z9: {}, AA1: {} } }), sheet.lastColumn({ 칸: {} })],
|
||||
rows: [sheet.rowsText(4345), sheet.rowsText([93, 101])],
|
||||
},
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error?.stack ?? error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
/* M02 일위대가 실무자료 이음매(329 · 330) 헬퍼 — 실제 서버 응답(격자 · 호표 맞춤)을
|
||||
* `M02_MasterTemplete_Hopyo_Sheet.ts`(행 → 호표 · 영역 A1) · `M02_MasterTemplete_Hopyo_Choose.ts`
|
||||
* (근거 꼬리표 · 엑셀 근거 줄 · 근거 맞음)에 먹임 ·
|
||||
* stdin = {grid, lines, all} · stdout = {at, span, rows, lines: [{이름, source, tag, ok}],
|
||||
* count: {top}} JSON — count = 「후보」 줄 1순위 근거 맞음. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const STUBS = { "@config/config_frontend": { API_BASE_URL: "/api" } };
|
||||
const STUB_FILES = [
|
||||
"M02_MasterTemplete_Labor_Pick",
|
||||
"M02_Material_Pick",
|
||||
"M02_MasterTemplete_Find_Pick",
|
||||
"M02_MasterTemplete_Labor_Api",
|
||||
"M02_MasterTemplete_Labor_Input",
|
||||
"M01_MasterData_UI_LogicLab_Source",
|
||||
"M02_MasterTemplete_Hopyo_Left", // 남은 줄 창(729-3 · DOM · 찾기 창)
|
||||
];
|
||||
const stub = new Proxy({}, { get: (_t, k) => (k === "currentLanguageIndex" ? () => 0 : () => {}) });
|
||||
const { loadTs } = makeLoader({
|
||||
root: ROOT,
|
||||
requireFor: (full) => (request) => {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
if (STUB_FILES.some((name) => request.endsWith(name))) return stub;
|
||||
if (request.startsWith(".") && !request.endsWith(".css"))
|
||||
return loadTs(`${path.resolve(path.dirname(full), request)}.ts`);
|
||||
return stub;
|
||||
},
|
||||
});
|
||||
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const M02 = path.join(ROOT, "M02_MasterTemplete");
|
||||
const sheet = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Sheet.ts"));
|
||||
const view = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_Choose.ts"));
|
||||
const { grid } = input;
|
||||
const ilwi = grid.책.시트.find((s) => s.이름 === "일위대가");
|
||||
const three = grid.호표.find((h) => h.번 === 3);
|
||||
const out = {
|
||||
at: [19, 20, 22, 24, 25, 26].map((row) => sheet.hopyoAtRow(grid.호표, row)),
|
||||
span: sheet.spanText(three.행, sheet.lastColumn(ilwi)),
|
||||
rows: sheet.rowsText(grid.단산.find((b) => b.번 === 93).행),
|
||||
lines: input.lines.map((line) => {
|
||||
const top = (line.맞춤.후보 ?? [])[0];
|
||||
return {
|
||||
이름: line.이름,
|
||||
source: view.sourceText(line.근거),
|
||||
tag: view.basisTag(top?.근거),
|
||||
ok: view.basisOk(top?.근거, line.근거),
|
||||
};
|
||||
}),
|
||||
};
|
||||
const open = (input.all ?? []).filter((line) => line.맞춤.상태 === "후보");
|
||||
out.count = {
|
||||
top: open.filter((line) => view.basisOk((line.맞춤.후보 ?? [])[0]?.근거, line.근거)).length,
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
서버 없이 잡음: 탭(역할) 따라 판 바꿈(일위대가표 = 호표 목록 · 단가산출 탭 = 단가산출 목록 · 칸은 처음 한 번만 지음) ·
|
||||
시트 행 → 목록 강조 · 목록 줄 → 지금 탭의 블록 자리 · 만들 층 ≠ 파일 층 = 보기만 · 거르기(전체 · 안 · 밖 · 고를 줄 있음) ·
|
||||
줄 0 블록 = 「줄 없음」 확인 막음 · 마법사 칸과 같은 캐시 열쇠(두 벌 고름 맞춤) · [끝내기] = 막지 않고 물음창 글만 ·
|
||||
[끝내기] = 막지 않고 물음창 글만 ·
|
||||
화면 = 부품 상자만 · 700 줄.
|
||||
"""
|
||||
|
||||
@@ -14,7 +14,6 @@ from pathlib import Path
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
M02 = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
||||
from test_778_5_sk_pick_screen import BLOCKS as sys_blocks # noqa: E402
|
||||
|
||||
|
||||
def _node(payload: dict) -> dict:
|
||||
@@ -71,24 +70,11 @@ def test_다른_층_권한_없음_보기만():
|
||||
assert got["blocks"][0]["readOnly"] is True
|
||||
|
||||
|
||||
def test_거르기_줄0_막음_끝내기_글():
|
||||
got = _node({"blocks": sys_blocks})
|
||||
assert got["empty"] == {"판정": "줄 없음", "켜짐": False, "확인": [], "몸": []}
|
||||
assert got["filter"] == {"all": [2, 5, 9, 7], "in": [2, 7], "out": [5, 9], "open": [2, 9]}
|
||||
assert got["old"] == 0 # 옛 서버(부름 없음) = 모두 일위대가 안
|
||||
assert got["wait2"] == {"블록": 3, "밖": 2, "줄": 3} # 만든 블록 7 뺌 · 줄 0 블록 = 고를 줄 1
|
||||
|
||||
|
||||
def _src(name: str) -> str:
|
||||
return (M02 / f"M02_MasterTemplete_Hopyo_{name}.ts").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_캐시_공유_알림_칠_다시():
|
||||
wiz = _src("WizardSk")
|
||||
assert wiz.count("skDraftKey(scope, key)") == 1 # 두 벌 모두 같은 열쇠(층 · 주인 · 파일 키)
|
||||
assert "SK_SYNC" in wiz and "tell({ got: next })" in wiz # 옮김 = 받은 판정 그대로 다른 벌에
|
||||
assert wiz.count("tell();") == 2 # [확인] · 줄 고름 바뀜
|
||||
assert "new Event(HOPYO_BUILT)" in wiz # 옮김 뒤 열린 엑셀 보기 칠 다시
|
||||
sheet = _src("Sheet")
|
||||
assert "createSheetSk(" in sheet and "sk?.at(s);" in sheet
|
||||
side = _src("SheetSk")
|
||||
@@ -96,17 +82,17 @@ def test_캐시_공유_알림_칠_다시():
|
||||
nav = _src("SheetNav")
|
||||
assert "blockAt(sheet: string, row: number)" in nav and "spotOf(no: number, role: Role)" in nav
|
||||
wizard = _src("Import")
|
||||
assert 'L("M02_SkUnmoved")' in wizard and "finish.disabled = !!n?.줄;" in wizard # 밖 = 안 막음
|
||||
assert 'L("M02_SkUnmoved")' in wizard and "leftCount" not in wizard # 밖 = 안 막음 · 옛 남은 줄 막음 걷음
|
||||
tone = (M02 / "logic" / "M02_MasterTemplete_Hopyo_SheetMap.ts").read_text(encoding="utf-8")
|
||||
assert '글: "일위대가 밖"' in tone
|
||||
|
||||
|
||||
def test_부품_상자만_700줄():
|
||||
for name in ("SheetSk", "Sheet", "SheetMake", "SheetNav", "WizardSk", "Import", "Side"):
|
||||
for name in ("SheetSk", "Sheet", "SheetNav", "Import", "Side"):
|
||||
src = _src(name)
|
||||
assert len(src.splitlines()) <= 700, name
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,6}\b|rgb\(", src), name
|
||||
for name in ("SheetSk", "WizardSk", "Import"):
|
||||
for name in ("SheetSk", "Import"):
|
||||
assert not re.search(r'el\("(button|input|select)"|<button|<input', _src(name)), name
|
||||
css = (M02 / "M02_MasterTemplete_Hopyo.css").read_text(encoding="utf-8")
|
||||
block = css.split(".m02-hopyo-sheet.is-sk", 1)[1].split("/*", 1)[0]
|
||||
|
||||
@@ -33,9 +33,9 @@ def test_sk_list_layer_order_and_ref_shape():
|
||||
# 721-2 — SK · 일위대가 탭은 찾아 넣기 창(Find_Pick) · 줄 누름 = 참조
|
||||
find = _text(M02 / "M02_MasterTemplete_Find_Pick.ts")
|
||||
assert "go: take(() => opt.ref?.(x.ref, x.제목 || x.이름))" in find
|
||||
assert "sangeun: true," in _text(
|
||||
M02 / "M02_MasterTemplete_Hopyo_Pick.ts"
|
||||
) # 일위대가 [로직 추가] → 찾아 넣기 창
|
||||
assert "sangeun: true }" in _text(
|
||||
M02 / "M02_MasterTemplete_Labor_Pick.ts"
|
||||
) # 일위대가 [로직 추가] → 찾아 넣기 창(785-7 `withSangeun`)
|
||||
store_ts = _text(ROOT / "B09_Estimation" / "B09_Estimation_UI_Store.ts")
|
||||
assert '["sangeun", "단가산출"],' in store_ts and "sangeun: sangeunItems," in store_ts
|
||||
# 더한줄 고르기 — 715 이음 꼴 셋(인력 · 재료 = 요소 키)
|
||||
|
||||
@@ -89,7 +89,7 @@ def test_구조물_조합_층_제자리_바꿈():
|
||||
assert "topHost.replaceChildren(" in build and "treeHost.replaceChildren(" in build
|
||||
assert "tree: [treeHost]," in page and "top: topHost," in page
|
||||
assert "() => isAdmin," in page and 'isSystem: user?.role === "SYSTEM_ADMIN",' in page
|
||||
assert "main.root.isConnected ? void afterItem()" in page # 나무를 갈아도 듣기 그대로
|
||||
assert "afterItem" not in page # 785-7 [구조물 조합으로](옛 확인 창 몫) 듣기 걷음
|
||||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||||
assert "const drawing = section(" in side and "practice" not in side # 732-1
|
||||
# 도면 양식 칸 = 누구나 · 단추만 관리자(720-4 덧)
|
||||
|
||||
@@ -44,7 +44,7 @@ def test_721_2_부르는_곳_셋만_새_창():
|
||||
assert "logicOnly" not in _text(M02 / "M02_MasterTemplete_Sangeun_Detail.ts") # 727-2
|
||||
assert "logicOnly" not in find
|
||||
assert "openFindPicker({" in _text(M02 / "M02_MasterTemplete_Sangeun_Pick.ts")
|
||||
assert "sangeun: true," in _text(M02 / "M02_MasterTemplete_Hopyo_Pick.ts") # 일위대가
|
||||
assert "sangeun: true }" in labor_pick # 일위대가(785-7 `withSangeun`)
|
||||
# 옛 창 그대로 — B09 단가 연결은 sangeun 없이 부름(736-1 불러오기 · 별칭 장부는 찾아 넣기 창으로)
|
||||
for f in (
|
||||
ROOT / "B09_Estimation" / "B09_Estimation_UI_Link.ts",
|
||||
@@ -52,7 +52,7 @@ def test_721_2_부르는_곳_셋만_새_창():
|
||||
):
|
||||
text = _text(f)
|
||||
assert "openLogicPicker(" in text and "sangeun:" not in text, f.name
|
||||
for f in ("Hopyo_Choose", "Hopyo_Ledger"):
|
||||
for f in ("Hopyo_Ledger",):
|
||||
text = _text(M02 / f"M02_MasterTemplete_{f}.ts")
|
||||
assert "openFindPicker({" in text and "openLogicPicker" not in text.split("*/", 1)[1], f
|
||||
# 탭 셋 · 찾기 칸 하나 · 거름 고르기 없음 · M01 = 로직 · 인력 · 재료(기계 · 호표 없음)
|
||||
@@ -126,11 +126,7 @@ def test_736_1_항목_추가_찾기_창_하나():
|
||||
find = _text(M02 / "M02_MasterTemplete_Find_Pick.ts")
|
||||
assert 'kind === "재료" && opt.material' in find and "? opt.material" in find
|
||||
assert '"logic" | "labor" | "material" | "machine" | "oil" | "sk" | "pre"' in find # 785-3 oil
|
||||
# 불러오기 엑셀 연결 = 기초(자재 아닌 줄) · 재료(자재 · 재료비 줄) · 별칭 장부 = 기초 · 재료 둘
|
||||
choose = _text(M02 / "M02_MasterTemplete_Hopyo_Choose.ts")
|
||||
assert "다른 로직 검색" not in choose and "다른 자재 검색" not in choose
|
||||
assert 'MATERIAL.has(line.갈래) ? [] : (["logic"] as const)' in choose
|
||||
assert 'canPickMaterial(line) ? (["material"] as const) : []' in choose
|
||||
# 별칭 장부 = 기초 · 재료 둘(옛 확인 창 고르기는 785-7 에서 지움)
|
||||
assert 'kinds: ["logic", "material"],' in _text(M02 / "M02_MasterTemplete_Hopyo_Ledger.ts")
|
||||
|
||||
|
||||
@@ -143,14 +139,6 @@ def test_736_2_빠진_길_둘_신규_자재_기계_탭():
|
||||
assert 'opt.machine && ok("machine")' in find
|
||||
pick = _text(M02 / "M02_Material_Pick.ts")
|
||||
assert "export function openNewMaterial(" in pick and "{ owner }," in pick
|
||||
choose = _text(M02 / "M02_MasterTemplete_Hopyo_Choose.ts")
|
||||
assert 'line.갈래 === "노임"' in choose and '["labor"]' in choose and '["machine"]' in choose
|
||||
assert "element: (e) => opt.done([{ 노임: e.코드, 배: 1 }])," in choose
|
||||
assert "machine: (e) => opt.done([{ 기계: e.코드, 배: 1 }])," in choose
|
||||
left = _text(M02 / "M02_MasterTemplete_Hopyo_Left.ts")
|
||||
assert "kinds: kindsOf(line)" in left and "pickMachine" not in left
|
||||
lost = _text(M02 / "M02_MasterTemplete_Hopyo_Lost.ts")
|
||||
assert '"machine"' in lost and "pickMachine" not in lost
|
||||
unlinked = _text(M02 / "M02_MasterTemplete_Labor_Unlinked.ts")
|
||||
assert 'machine: (e) => done("기계", e.코드)' in unlinked and "pickMachine" not in unlinked
|
||||
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
"""일위대가 불러오기 마법사 안 이은 줄 화면(PLAN 729-3 · 요구서 내역19 1장 8 · 2장 4).
|
||||
|
||||
헬퍼(`helper_729_3_hopyo_left.cjs` · fetch · sessionStorage 대역)로 진짜 TS 를 돌림 —
|
||||
만들기 409 `{까닭, 남은줄}` = StaleError 까닭 글 + 남은 줄 · 남은 수 = 안 만든 호표만(만든 호표는 안 물음) ·
|
||||
줄마다 고름(노임 · 기계 · 비율 · 우리에 없음 · 로직) = 확인 창 캐시 → 만들기 몸 그대로 · 0 될 때까지 ·
|
||||
일위대가표 안 이은 고정 줄(비율 · 이음 있는 줄 뺌) · 고름 대기 후보 글.
|
||||
소스: [끝내기] = 남은 줄 다시 셈 → 있으면 막음(띠 · 표) · 단가산출 칸 · 목록 열 · 화면 요소 = 부품 상자 · 700 줄.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
M02 = Path(HERE).parents[1] / "M02_MasterTemplete"
|
||||
|
||||
|
||||
def _line(no: int, kind: str = "자재", state: str = "후보") -> dict:
|
||||
match = {"열쇠": f"줄{no}", "상태": state, "체크": False, "대상": []}
|
||||
return {"차례": no, "이름": f"줄{no}", "규격": "", "수량": 1, "단위": "㎥", "갈래": kind,
|
||||
"단가": {}, "금액": {}, "비고": "", "맞춤": match} # fmt: skip
|
||||
|
||||
|
||||
def _left(no: int, kind: str) -> dict:
|
||||
return {"차례": no, "이름": f"줄{no}", "규격": "", "단위": "㎥", "수량": 1, "갈래": kind,
|
||||
"까닭": "이을 것 없음", "후보": []} # fmt: skip
|
||||
|
||||
|
||||
LINES = [_line(0, "노임"), _line(1, "기계"), _line(2, "%", "%"), _line(3), _line(4, "단가산출")]
|
||||
LEFT = [_left(0, "노임"), _left(1, "기계"), _left(2, "%"), _left(3, "자재"), _left(4, "단가산출")]
|
||||
FIXES = [
|
||||
{"고정": {"이름": "안이음", "단가": {}, "이음": {"노임": None, "자재": None, "기계": None}}},
|
||||
{
|
||||
"고정": {
|
||||
"이름": "이음",
|
||||
"단가": {},
|
||||
"이음": {"노임": "LB000002", "자재": None, "기계": None},
|
||||
}
|
||||
},
|
||||
{"고정": {"이름": "비율", "단가": {}, "비율": {"기준": "노무비", "비목": "경비"}}},
|
||||
{"로직": "GF000113"},
|
||||
]
|
||||
|
||||
|
||||
def _run() -> dict:
|
||||
payload = {
|
||||
"one": {"키": "HP-000001", "파일": "북부청.xls", "호표": {"이름": "x", "줄": LINES}},
|
||||
"made": [1],
|
||||
"left": LEFT,
|
||||
"fixes": FIXES,
|
||||
}
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_729_3_hopyo_left.cjs")],
|
||||
input=json.dumps(payload, ensure_ascii=False),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=False,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_남은_줄_고름_몸_막힘_판정():
|
||||
got = _run()
|
||||
# 만들기 409 = StaleError · 까닭 글 · 남은 줄 그대로
|
||||
assert got["stale"]["name"] == "StaleError" and got["stale"]["message"] == "남음"
|
||||
assert got["stale"]["left"] == LEFT
|
||||
# 만든 호표(1) 는 안 물음 · 안 만든 호표(2) 만 남은 줄
|
||||
assert got["before"] == {"줄": 5, "호표": 1}
|
||||
assert all(u.endswith("/layers/personal/hopyo-unlinked") for u in got["unlinkedCalls"])
|
||||
assert len(got["unlinkedCalls"]) == 3 # 셈 세 번 × 안 만든 호표 하나
|
||||
assert got["half"] == {"줄": 1, "호표": 1}
|
||||
assert got["after"] == {"줄": 0, "호표": 0}
|
||||
# 고른 것 = 만들기 몸(서버 이음 대상 꼴 · 입력값 없음 · 배 1)
|
||||
body = {x["차례"]: x for x in got["body"]}
|
||||
assert body[0] == {"차례": 0, "대상": [{"노임": "LB000002", "배": 1}]}
|
||||
assert body[1] == {"차례": 1, "대상": [{"기계": "EQ000069", "배": 1}]}
|
||||
assert body[2] == {"차례": 2, "대상": [{"비율": "노무비", "배": 1}]} # % 줄 = 밑수 고른 것만
|
||||
assert body[3] == {"차례": 3, "뺌": "우리에 없음"}
|
||||
assert body[4] == {"차례": 4, "대상": [{"로직": "GF000113", "입력값": {}, "배": 1}]}
|
||||
assert got["lastBody"]["줄"] == got["body"] and got["lastBody"]["번"] == 2
|
||||
# 일위대가표 띠 = 이음 · 비율 없는 고정 줄만
|
||||
assert got["fixes"] == ["안이음"]
|
||||
|
||||
|
||||
def _text(name: str) -> str:
|
||||
return (M02 / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_불러오기_끝내기_막음_단가산출_칸_목록_열():
|
||||
wizard = _text("M02_MasterTemplete_Hopyo_Import.ts")
|
||||
end = wizard.split("async function end()")[1]
|
||||
assert end.index("await recount()") < end.index("showConfirmDialog(") # 끝내기 전 다시 셈
|
||||
assert "if (leftCount(got).줄)" in end and "openLeft(" in end # 남으면 막음 · 표
|
||||
assert "finish.disabled = !!n?.줄;" in wizard # 배지 · 남은 수 · [끝내기] 막음
|
||||
assert (
|
||||
"body?.skCount()" in wizard and "/sangeun`" not in wizard
|
||||
) # 단가산출 칸 = 엑셀 보기 오른쪽 판
|
||||
# 785-4 흠 — [안 이은 줄 고르기] · [끝내기] 모두 엑셀 보기가 읽은 호표 번으로 셈(빈 번 「0」 오표시 없음)
|
||||
pick = wizard.split("async function openPick()")[1].split("async function ensureNos")[0]
|
||||
assert "await ensureNos();" in pick and "openLeft(" in pick
|
||||
assert "nos = got.호표.map((h) => h.번)" in wizard
|
||||
assert "ensureNos" in wizard.split("async function recount()")[1]
|
||||
sk = _text(
|
||||
"M02_MasterTemplete_Hopyo_WizardSk.ts"
|
||||
) # 778-5 D-5 — 줄 고름 · 확인한 것만(옛 기초 고름 걷음)
|
||||
assert "judgeSangeun(scope, key, body)" in sk
|
||||
lost = _text("M02_MasterTemplete_Hopyo_Lost.ts") # 옛 못옮김 창 = 보기만(옛 `바탕` 몸 걷음)
|
||||
assert "makeSangeun" not in lost and "loadLinks" not in lost
|
||||
assert "row.못옮김?.length && row.단산 !== undefined && row.SK" in _text(
|
||||
"M02_MasterTemplete_Hopyo_Left.ts"
|
||||
)
|
||||
left = _text(
|
||||
"M02_MasterTemplete_Hopyo_Left.ts"
|
||||
) # 778-5 D-3 — 기초 고르기 · SK 만들기 걷고 걸음 이동
|
||||
assert "makeSangeun" not in left and 'L("M02_LeftSkGo")' in left and "at.onSk" in left
|
||||
lib = _text("M02_MasterTemplete_Library_List.ts")
|
||||
assert 'L("M02_LibUnlinked"), (r) => (r.안이은줄' in lib
|
||||
assert "unlinkedBar(doc, edit)" in _text("M02_MasterTemplete_Labor_Grid.ts")
|
||||
assert "unlinkedBar(cur, edit &&" in _text("M02_MasterTemplete_Labor_Table.ts") # 일위대가표 띠
|
||||
left = _text("M02_MasterTemplete_Hopyo_Left.ts")
|
||||
# 일반 사용자 길 — 기계 고름은 찾아 넣기 창(736-2)이 `/logic-lib/elements` 로
|
||||
assert "M01_MasterData_UI_Logic_Api" not in left
|
||||
assert "/logic-lib/elements?group=" in _text("M02_MasterTemplete_Find_Pick.ts")
|
||||
for name in (
|
||||
"M02_MasterTemplete_Hopyo_Left.ts",
|
||||
"M02_MasterTemplete_Hopyo_WizardSk.ts",
|
||||
"M02_MasterTemplete_Hopyo_Import.ts",
|
||||
"M02_MasterTemplete_Labor_Unlinked.ts",
|
||||
"M02_MasterTemplete_Hopyo_Lost.ts",
|
||||
):
|
||||
src = _text(name)
|
||||
assert len(src.splitlines()) <= 700, name
|
||||
assert 'el("button"' not in src and 'el("input"' not in src, name # 부품 상자만
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,6}|rgb\(", src), name # 색 = 공통 값
|
||||
@@ -83,18 +83,3 @@ def _lines(client, no):
|
||||
def _auto(client, no):
|
||||
"""장부가 자동으로 정한 줄 수."""
|
||||
return sum(1 for m in _lines(client, no).values() if m["상태"] in FROM_LEDGER)
|
||||
|
||||
|
||||
def test_화면_내_제안_글():
|
||||
"""고르기 창 맨 위 「내 제안」(한 번 눌러 짝 통째) · 장부 화면 = 내 제안 목록 · 승인 칸."""
|
||||
from pathlib import Path
|
||||
|
||||
m02 = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
||||
choose = (m02 / "M02_MasterTemplete_Hopyo_Choose.ts").read_text(encoding="utf-8")
|
||||
assert choose.index("...(mineRow ? [mineRow] : [])") < choose.index("...sections")
|
||||
assert '"data-group": "내 제안"' in choose and "mine.짝.map((p)" in choose
|
||||
rows = (m02 / "M02_MasterTemplete_Hopyo_CheckRows.ts").read_text(encoding="utf-8")
|
||||
assert '"고르기 · 내 제안"' in rows
|
||||
book = (m02 / "M02_MasterTemplete_Hopyo_Ledger.ts").read_text(encoding="utf-8")
|
||||
assert 'MINE_TITLE = "내 제안 목록"' in book and '"승인",' in book
|
||||
assert "createBadge(r.상태, STATE_TONE[r.상태])" in book
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
"""746-2 지킴 — 불러오기 고르기 창 후보 블록 줄 비교 · 요소 후보 화면(요구 585).
|
||||
|
||||
서버 없이 소스로 잡음: 안 이은 줄 창이 요소 후보 단추를 붙임(고름 대기 줄 비교 · 못옮김 잇기는 778-5 D-5 줄 고름으로 걷음) ·
|
||||
안 이은 줄 창의 기초 후보 칸에 요소 후보(`키`)가 섞여 들어가지 않음 · 서버 응답 칸 이름 · 부품 상자만 · 700 줄.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
M02 = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
||||
|
||||
|
||||
def _src(name: str) -> str:
|
||||
return (M02 / f"M02_MasterTemplete_Hopyo_{name}.ts").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_안_이은_줄_창이_요소_후보_단추를_붙임():
|
||||
left = _src("Left")
|
||||
assert "elemChoices(row.후보.filter(isElem)" in left
|
||||
assert "SangeunCand" not in left # 778-5 D-3 — 기초 고르기 걷음
|
||||
|
||||
|
||||
def test_서버_응답_칸_이름_그대로():
|
||||
api = _src("Api")
|
||||
assert "못옮김: SangeunLost[]" in api and "export interface ElemCand" in api
|
||||
assert "SangeunCompare" not in api and "export function compareView" not in _src("Compare")
|
||||
|
||||
|
||||
def test_부품_상자만_날_요소_없음_700줄_안():
|
||||
src = _src("Compare")
|
||||
assert not re.search(r'createElement\("(button|input)"|<button|<input', src)
|
||||
for name in ("Compare", "WizardSk", "Lost", "Left"):
|
||||
assert len(_src(name).splitlines()) <= 700
|
||||
@@ -1,132 +0,0 @@
|
||||
"""778-5 D-5 지킴 — 마법사 단가산출 줄 고름(제안 · 확인 뒤 옮김 · 요구 621 · 622 · 623 · 624).
|
||||
|
||||
서버 없이 잡음: 줄 고름 캐시가 서버 상태를 덮음(「내가 고름」) · [확인] = 고를 줄 0 일 때만 · 몸 = 이음 줄
|
||||
`{확인: [{번, 고름?: {차례: 고름}}]}`(확인한 블록만) · 자동 확인 없음(빈 캐시 = 빈 몸 · 「모두 확인」 단추 없음) ·
|
||||
화면 = 부품 상자만 · 700 줄.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
M02 = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
||||
|
||||
|
||||
def _line(at, kind, state, **more):
|
||||
row = {"차례": at, "행": 100 + at, "묶음": kind, "이름": f"줄{at}", "규격": "", "매개값": {}}
|
||||
row |= {"엑셀금액": 1000.0, "상태": state, "방법": "", "단위": None, "후보": [], "꼬리표": []}
|
||||
return {**row, "글": "", "우리금액": None, "차": None, **more}
|
||||
|
||||
|
||||
def _block(no, lines, made=None):
|
||||
head = {"번": no, "이름": f"블록{no}", "단위": "㎥", "판정": "", "고를줄": 0, "우리금액": None}
|
||||
return head | {"엑셀금액": 5000.0, "근거": None, "만든": made, "줄": lines}
|
||||
|
||||
|
||||
BOSS = ["기본 — 건설업 작업반장"]
|
||||
BLOCKS = [
|
||||
_block(
|
||||
2,
|
||||
[
|
||||
_line(0, "L", "이어짐", 매개값={"품": 0.2}),
|
||||
_line(1, "L", "이어짐", 꼬리표=BOSS, 후보=[{"키": "LB000099"}]),
|
||||
_line(2, "M", "고를 것", 후보=[{"자재": "MT000123"}]),
|
||||
_line(3, "M", "못 이음"),
|
||||
_line(4, "R", "밖"),
|
||||
],
|
||||
),
|
||||
_block(5, [_line(0, "X", "이어짐"), _line(1, "R", "이어짐")]),
|
||||
_block(7, [_line(0, "S", "못 이음")], made=None),
|
||||
]
|
||||
|
||||
|
||||
def _run() -> dict:
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_778_5_sk_pick.cjs")],
|
||||
input=json.dumps({"blocks": BLOCKS}, ensure_ascii=False),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=False,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_판정_확인_켜짐_조건():
|
||||
got = _run()
|
||||
assert got["start"] == [
|
||||
{"번": 2, "판정": "고를 줄 2", "확인켜짐": False},
|
||||
{"번": 5, "판정": "옮길 수 있음", "확인켜짐": True},
|
||||
{"번": 7, "판정": "고를 줄 1", "확인켜짐": False},
|
||||
]
|
||||
assert got["sureBlocked"] == [] # 고를 줄 남은 블록 = [확인] 안 켜짐
|
||||
assert got["half"] == {"판정": "고를 줄 2", "켜짐": False} # 이어진 줄 바꿈은 고를 줄 안 줄임
|
||||
assert got["full"] == {"판정": "옮길 수 있음", "켜짐": True}
|
||||
assert got["sure"] == [2, 5] # 하위 못 이음 블록 7 = 안 켜짐
|
||||
|
||||
|
||||
def test_줄_표_꼴_내가_고름이_덮음():
|
||||
view = _run()["view"]
|
||||
assert view[0] == {"상태": "이어짐", "꼬리표": [], "글": "", "mine": False}
|
||||
assert view[1] == {"상태": "이어짐", "꼬리표": ["내가 고름"], "글": "LB000099", "mine": True}
|
||||
assert view[2]["상태"] == "이어짐" and view[2]["글"] == "MT000123"
|
||||
assert view[3] == {
|
||||
"상태": "뺌",
|
||||
"꼬리표": ["내가 고름"],
|
||||
"글": "뺌 · 우리에 없음",
|
||||
"mine": True,
|
||||
}
|
||||
assert view[4]["상태"] == "밖"
|
||||
|
||||
|
||||
def test_몸_꼴_이음_줄_자동_확인_없음():
|
||||
got = _run()
|
||||
assert got["emptyBody"] == [] # 누른 것 없음 = 몸 비움
|
||||
picks = {"1": {"노임": "LB000099"}, "2": {"자재": "MT000123"}, "3": {"뺌": "우리에 없음"}}
|
||||
assert got["body"] == [{"번": 2, "고름": picks}, {"번": 5}]
|
||||
first, second = got["calls"]
|
||||
assert first["method"] == "POST" and first["body"] == {"확인": []} # 판정만
|
||||
assert first["url"].endswith("/layers/personal/hopyo/HP-000001/sangeun")
|
||||
assert second["body"] == {"확인": got["body"]}
|
||||
assert got["wait"] == {
|
||||
"블록": 3,
|
||||
"밖": 0,
|
||||
"줄": 1,
|
||||
} # 안 옮긴 블록 셋 · 블록 7 하위 줄 · 옛 서버 = 밖 없음
|
||||
assert got["tidy"] == [5] # 고름 지우면 그 블록 확인 빠짐
|
||||
assert got["round"]["확인"] == [2, 5] and got["bad"] == {"고름": {}, "확인": []}
|
||||
|
||||
|
||||
def test_후보_고름_꼴():
|
||||
cand = _run()["cand"]
|
||||
assert cand["L"] == {"노임": "LB000001"} and cand["X"] == {"기계": "EQ000019"}
|
||||
assert cand["M"] == {"자재": "MT000001"} and cand["R"] == {"기준": "노무비", "비목": "경비"}
|
||||
assert cand["Mbad"] is None and cand["text"] == "합판 12mm [장]"
|
||||
|
||||
|
||||
def _src(name: str) -> str:
|
||||
return (M02 / f"M02_MasterTemplete_Hopyo_{name}.ts").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_화면_자동_없음_부품_상자만():
|
||||
wiz = _src("WizardSk")
|
||||
assert "judgeSangeun(scope, key)" in wiz # 열 때 = 판정만
|
||||
assert "judgeSangeun(scope, key, body)" in wiz and "sureBody(load(), got.판정)" in wiz
|
||||
assert "모두" not in wiz.split("*/", 1)[1] and "setSure(" in wiz # 확인은 블록마다만
|
||||
assert 'kind: "chip"' in wiz and "disabled: !ok" in wiz # [확인] 칩 · 고를 줄 0 일 때만
|
||||
assert "단산행: 행" in wiz # 엑셀 보기 뛰기
|
||||
lines = _src("SkLines")
|
||||
assert "lineFind(" in lines and "unitTone(" in lines # 찾기 창 · 엑셀 칠과 한 규칙
|
||||
wizard = _src("Import")
|
||||
assert 'L("M02_SkLeft")' in wizard and "body?.skCount()" in wizard # [끝내기] 막힘 글
|
||||
for name in ("WizardSk", "SkLines", "SkApi", "Lost", "Compare"):
|
||||
src = _src(name)
|
||||
assert len(src.splitlines()) <= 700, name
|
||||
assert not re.search(r'el\("(button|input|select)"|<button|<input', src), name
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,6}\b|rgb\(", src), name
|
||||
logic = (M02 / "logic" / "M02_MasterTemplete_Hopyo_SkPick.ts").read_text(encoding="utf-8")
|
||||
assert not re.search(r"sessionStorage\.|fetch\(|document\.", logic) # 순수
|
||||
@@ -1,5 +1,5 @@
|
||||
"""785-4 불러오기 화면 껍데기 — 가운데 상세 페이지(마법사) 없음 · 왼쪽 [불러오기] → 바로 본 칸 엑셀 + 오른쪽 목록 ·
|
||||
머리 막대 [끝내기] · 흠 셋(안 이은 줄 0 오표시 · 「맞춤 셈 중」 멈춤 · 좁은 판 단가산출 줄 짓눌림) · 부품 상자만 · 700 줄.
|
||||
머리 막대 [끝내기] · 좁은 판 줄 판 자리 · 부품 상자만 · 700 줄(785-7 — 안 이은 줄 띠 · 마법사 단가산출 걸음 · 옛 캐시 걷음 반영).
|
||||
소스 글만 읽음(서버 · 화면 없이 돎)."""
|
||||
|
||||
import re
|
||||
@@ -33,7 +33,7 @@ def test_누름_하나_바로_엑셀():
|
||||
show = side.split("async function show(")[1].split("async function showLedger")[0]
|
||||
assert "buildHopyoImport({" in show and "opt.show(" in show # 올림 뒤 본 칸 바로
|
||||
imp = _src("Import")
|
||||
assert "m.buildHopyoSheet({" in imp and "headExtra: [layerNote, badge, pickBtn, finish]" in imp
|
||||
assert "m.buildHopyoSheet({" in imp and "headExtra: [layerNote, finish]" in imp
|
||||
|
||||
|
||||
def test_결과_층_고름_둘_이상일_때만():
|
||||
@@ -43,28 +43,21 @@ def test_결과_층_고름_둘_이상일_때만():
|
||||
assert 'let pick: ManageLayer = "personal";' in side
|
||||
|
||||
|
||||
def test_흠_셋_고침():
|
||||
imp = _src("Import")
|
||||
# ① 안 이은 줄 0 오표시 — 빈 호표 번으로 안 부름 · 엑셀 보기가 읽은 번을 씀
|
||||
pick = imp.split("async function openPick()")[1].split("async function ensureNos")[0]
|
||||
assert "await ensureNos();" in pick
|
||||
assert "nos = got.호표.map((h) => h.번)" in imp
|
||||
assert "ensureNos" in imp.split("async function recount()")[1]
|
||||
# ② 「맞춤 셈 중」(409) = 3 초마다 저절로 다시 판정
|
||||
sk = _src("WizardSk")
|
||||
assert "error instanceof StaleError" in sk and "void judge()" in sk and "POLL_MS" in sk
|
||||
# ③ 좁은 판 줄 짓눌림 — [줄 n] = 시트 아래 줄 판(넓은 줄 표) · 판 안 펼침 없음
|
||||
assert "skLineTable({" in sk.split("function drawLines()")[1]
|
||||
assert "is-sub" not in sk and "o.narrow" not in sk
|
||||
def test_줄_판_자리_좁은_폭():
|
||||
# 셈 중(블록 풀이) = 3 초마다 다시 읽음 · 줄 판 = 시트 아래 넓은 자리 · 좁은 폭 = 줄 판 높이 바닥
|
||||
assert "POLL_MS" in _src("SheetSk") and "POLL_MS" in _src("Sheet")
|
||||
sheet = _src("Sheet")
|
||||
assert 'attrs: { "data-hopyo-lines": "" }' in sheet and "openLines," in sheet
|
||||
assert "openLines: (make: (host: HTMLElement)" in _src("SheetSk") # 785-6 줄 판 자리 한 손잡이
|
||||
css = (M02 / "M02_MasterTemplete_Hopyo.css").read_text(encoding="utf-8")
|
||||
narrow = css.split("/* 좁은 폭(785-7 · 390)")[1].split("/*")[0]
|
||||
assert ".m02-hopyo-sheet__lines:not([hidden])" in narrow and "min-height: 16rem;" in narrow
|
||||
|
||||
|
||||
def test_끝내기_머리_막대_끝냄_뒤_빈_본_칸():
|
||||
imp = _src("Import")
|
||||
end = imp.split("async function end()")[1]
|
||||
assert end.index("await recount()") < end.index("showConfirmDialog(")
|
||||
assert "unmoved()" in end.split("showConfirmDialog(")[0] # 확인하고 안 옮긴 수 = 물음 글만(막지 않음)
|
||||
assert "deleteHopyo(scope, file.키)" in end and "a.onEnded();" in end
|
||||
side = _src("Side")
|
||||
assert "onEnded: () => {" in side and "void refresh();" in side
|
||||
@@ -73,19 +66,19 @@ def test_끝내기_머리_막대_끝냄_뒤_빈_본_칸():
|
||||
|
||||
|
||||
def test_부품_상자만_700줄_늘리지_않음():
|
||||
for name in ("Import", "Side", "Sheet", "SheetMake", "SheetSk", "WizardSk"):
|
||||
for name in ("Import", "Side", "Sheet", "SheetSk"):
|
||||
src = _src(name)
|
||||
assert len(src.splitlines()) <= 700, name
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,6}\b|rgb\(", src), name # 색 = 공통 값
|
||||
for name in ("Import", "SheetMake", "SheetSk", "WizardSk"):
|
||||
for name in ("Import", "SheetSk"):
|
||||
assert not re.search(r'el\("(button|input|select)"|<button|<input', _src(name)), name
|
||||
assert len(_src("Check").splitlines()) <= 662 and len(_src("Api").splitlines()) <= 632
|
||||
assert len(_src("Api").splitlines()) <= 632
|
||||
css = (M02 / "M02_MasterTemplete_Hopyo.css").read_text(encoding="utf-8")
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,6}\b|rgb\(", css) and ".m02-hopyo-import" in css
|
||||
|
||||
|
||||
def test_해시_캐시_열쇠_그대로():
|
||||
check = _src("Check")
|
||||
assert "m02-hopyo-check:" in check # 확인 창 캐시 열쇠
|
||||
assert "m02-hopyo-sk:" in _src("WizardSk") # 단가산출 캐시 열쇠
|
||||
picks = (M02 / "logic" / "M02_MasterTemplete_Hopyo_Picks.ts").read_text(encoding="utf-8")
|
||||
assert "m02-hopyo-judge:" in picks # 판정 고름 캐시 열쇠 하나(옛 확인 · 단가산출 캐시 걷음)
|
||||
assert "draftKey(layer, null, file.키)" in _src("Import")
|
||||
assert "location.hash" not in _src("Import") + _src("Side") # 새 해시 열쇠 없음
|
||||
|
||||
@@ -1,348 +1,17 @@
|
||||
"""일위대가 실무자료 화면(PLAN 324-4 · 338-3) — 좌측 판 · 본 화면 · 확인 창 · 고르기 창 ·
|
||||
별칭 장부 · [로직 추가] 「실무 호표」 줄 · 엑셀 보기 창.
|
||||
"""일위대가 실무자료 화면(PLAN 324-4 · 718-1 · 785-4) — 좌측 판 자리 · 권한 · 불러오기 본 칸 · [끝내기] · 떠남.
|
||||
|
||||
- 확인 창 도우미(Node · fetch · sessionStorage 대역 · `helper_m02_hopyo_check.cjs`):
|
||||
체크 → 몸(체크한 줄만 · 뺌 · 고른 대상 · 몫 · 배 · 호표) · 빠질 줄 까닭 · 캐시 키 ·
|
||||
최신 응답만 · 품 비교 · 만들기 · 통째 · 만들 층 · 미리 셈 · 장부 길 · 장부 화면 합침 ·
|
||||
하위 호표 먼저 · 확인된 것 = 바로 참조
|
||||
- 고르기 창 덧줄(`helper_m02_hopyo_pick.cjs`): 이 층 · 위 층 호표 줄 · 종류 고르기 ·
|
||||
프로젝트 층엔 없음 · 글 도우미 · 근거 꼬리표 · 엑셀 보기 창 행 → 호표
|
||||
- 소스: 옛 즉시 확정 · 바로 만들기 없음(saveHopyoMemory · [확정] · [같음 모두 확정] ·
|
||||
[근거 맞음 모두 확정] · [사전조합 만들기]) · `buildHopyo(` 는 확인 창 · 엑셀 보기 창 펼침에서만 ·
|
||||
좌측 판 자리 · 700 줄 · 색 = 공용 토큰
|
||||
옛 확인 창 · 고르기 창 · [로직 추가] 「실무 호표」 · 엑셀 보기 모달 시험은 785-7 에서 지움(판정 화면 = `test_642_6_screen`).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
M02 = Path(HERE).parents[1] / "M02_MasterTemplete"
|
||||
NEW = (
|
||||
"Hopyo_Api",
|
||||
"Hopyo_Pick",
|
||||
"Hopyo_Side",
|
||||
"Hopyo_Sheet",
|
||||
"Hopyo_Check",
|
||||
"Hopyo_CheckRows",
|
||||
"Hopyo_Choose",
|
||||
"Hopyo_Ledger",
|
||||
"Hopyo_Import",
|
||||
"Hopyo_SheetMake",
|
||||
)
|
||||
ROW = {
|
||||
"층": "system",
|
||||
"주인": None,
|
||||
"키": "HP-000001",
|
||||
"파일": "북부청.xls",
|
||||
"번": 3,
|
||||
"이름": "임도표지석",
|
||||
"규격": "910×600×150",
|
||||
"단위": "개소",
|
||||
"맞춤": [2, 4],
|
||||
}
|
||||
SURE = {"로직": "GC000198", "이름": "장비사용타설", "입력값": {"소량감률": 50}, "배": 1, "몫": None}
|
||||
QTY = {
|
||||
"엑셀": {"인력": {"콘크리트공": 0.1091}, "장비": [{"갈래": "굴착기", "시간": 0.29}]},
|
||||
"우리": {"인력": {"콘크리트공": 0.1091}, "장비": [{"갈래": "굴착기", "시간": 0.31}]},
|
||||
"같음": {"콘크리트공": True, "굴착기": False},
|
||||
}
|
||||
|
||||
QTY_MANY = { # 같은 장비 여러 줄(229-4 ②) — 합쳐 한 줄로 견줌
|
||||
"엑셀": {"장비": [{"갈래": "굴착기", "시간": t} for t in (0.0334, 0.0371, 0.0889)]},
|
||||
"우리": {"장비": [{"갈래": "굴착기", "시간": 0.0889}]},
|
||||
"같음": {"굴착기": False},
|
||||
}
|
||||
|
||||
|
||||
def _text(name: str) -> str:
|
||||
return (M02 / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _node(helper: str, payload: dict) -> dict:
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, helper)],
|
||||
input=json.dumps(payload, ensure_ascii=False),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=False,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def _line(no, state, check, parts, **extra):
|
||||
match = {"열쇠": f"줄{no}||m3", "상태": state, "체크": check, "대상": parts, **extra}
|
||||
return {"차례": no, "이름": f"줄{no}", "규격": "", "수량": 1, "단위": "㎥", "갈래": "단가산출",
|
||||
"맞춤": match} # fmt: skip
|
||||
|
||||
|
||||
def _check() -> dict:
|
||||
lines = [
|
||||
_line(0, "후보", False, [{"자재": "MT004258", "입력값": {}, "배": 1}]),
|
||||
_line(1, "맞음", True, [SURE], 품=QTY),
|
||||
_line(2, "별칭", True, [{"로직": "GF000101", "입력값": {}, "배": 1}]),
|
||||
_line(3, "다른 점", False, [{"로직": "GF000103", "입력값": {}, "배": 1}], 품=QTY_MANY),
|
||||
_line(4, "후보", False, []),
|
||||
_line(5, "%", False, []),
|
||||
_line(6, "호표", True, [{"호표": 46, "입력값": {}, "배": 2.3}]),
|
||||
_line(7, "노임", False, [{"로직": "GF000300", "입력값": {}, "배": None}]),
|
||||
]
|
||||
ledger = [
|
||||
{"열쇠": "가|b|m3", "원래": {"품명": "가", "규격": "b", "단위": "m3"}, "갈래": "짝",
|
||||
"짝": [{"로직": "GC000198", "입력값": {}, "배": 1, "몫": None}], "층": "personal",
|
||||
"주인": "7/42", "고침": True},
|
||||
{"열쇠": "나||m3", "원래": {"품명": "나", "규격": "", "단위": "m3"}, "갈래": "짝",
|
||||
"짝": [{"로직": "GF000101", "입력값": {}, "배": 1, "몫": None}], "층": "personal",
|
||||
"주인": "7/42", "고침": True},
|
||||
{"열쇠": "다||개", "원래": {"품명": "다", "규격": "", "단위": "개"}, "갈래": "짝",
|
||||
"짝": [{"자재": "MT1", "입력값": {}, "배": 1, "몫": None}], "층": "system",
|
||||
"주인": None, "고침": False},
|
||||
] # fmt: skip
|
||||
rows = [
|
||||
{**ROW, "확인": {"층": "company", "주인": "7", "키": "SC-000009"}},
|
||||
{**ROW, "번": 4, "이름": "배수관", "확인": None},
|
||||
]
|
||||
return _node("helper_m02_hopyo_check.cjs", {"lines": lines, "ledger": ledger, "rows": rows})
|
||||
|
||||
|
||||
def test_확인_창_체크한_줄만_몸():
|
||||
got = _check()["check"]
|
||||
assert got["key"] == "m02-hopyo-check:personal::HP-000001:3"
|
||||
assert got["suggested"] == [ # 서버 체크 제안 그대로 · 배 없는 줄 · % 줄 · 후보는 안 감
|
||||
{"차례": 1, "대상": [{"로직": "GC000198", "입력값": {"소량감률": 50}, "배": 1}]},
|
||||
{"차례": 2, "대상": [{"로직": "GF000101", "입력값": {}, "배": 1}]},
|
||||
{"차례": 6, "대상": [{"호표": 46, "배": 2.3}]},
|
||||
]
|
||||
assert got["edited"] == [ # 캐시 — 맞음 줄 체크 끔 · 뺌 · 고른 대상(몫 · 배)
|
||||
{"차례": 2, "대상": [{"로직": "GF000101", "입력값": {}, "배": 1}]},
|
||||
{"차례": 3, "뺌": "우리에 없음"},
|
||||
{
|
||||
"차례": 4,
|
||||
"대상": [{"로직": "GF000211", "입력값": {"유형": "보통"}, "배": 0.1, "몫": 0.1}],
|
||||
},
|
||||
{"차례": 6, "대상": [{"호표": 46, "배": 2.3}]},
|
||||
]
|
||||
assert got["leftOut"] == {"확인 안 함": 3, "뺌": 1, "% 줄": 1}
|
||||
assert got["checkable"] == [True, True, True, True, False, False, True, False]
|
||||
assert got["sure"] == [1, 2] # [맞음 · 별칭 일괄 체크]
|
||||
assert got["compare"] == "콘크리트공 0.1091 → 0.1091 ✓ · 굴착기 0.29 → 0.31 ≠"
|
||||
assert got["compareMany"] == "굴착기 0.1594 → 0.0889 ≠" # 같은 장비 여러 줄 = 합쳐 한 줄
|
||||
assert got["toTarget"] == [
|
||||
{"호표": 46, "배": 2.3},
|
||||
{"자재": "MT1", "배": 1},
|
||||
{"전국": True, "배": 1}, # 361-1 — 키 없음 · 이름은 서버가
|
||||
]
|
||||
assert got["newest"] == [False, True, True] # 같은 줄 늦은 응답 버림 · 다른 줄은 따로
|
||||
assert got["dropped"] is False # 만들기 성공 = 캐시 지움
|
||||
|
||||
|
||||
def test_고르기_창_도우미():
|
||||
got = _check()["choose"]
|
||||
assert got["hint"] == ["레미콘타설", "고임돌"] # 품명 첫 뜻 낱말(358-1 · 조항 번호 아님)
|
||||
assert got["groups"]["groups"] == [
|
||||
["1위", False, ["A"]],
|
||||
["출처 같음", False, ["C"]],
|
||||
["이름 닮은 로직", False, ["E"]],
|
||||
["숫자만 닮음", True, ["D", "B"]], # 뜻 낱말 안 겹치는 「후보」 = 끝
|
||||
]
|
||||
assert got["groups"]["text"][:2] == ["되메우기 · A · 산림", "되메우기 · C · 건설"]
|
||||
assert got["groups"]["material"] == ["1위", "그 밖 후보"]
|
||||
nat = got["groups"]["national"] # 361-1
|
||||
assert nat["groups"] == [["1위", False, ["전국"]], ["지역 변형", True, ["M1", "M2"]]]
|
||||
assert nat["text"] == "레미콘(전국)-관급 25-21-120 [㎥] · 키 없음"
|
||||
assert nat["region"] == [{"자재": "M2", "입력값": {}, "배": 1, "몫": None}]
|
||||
assert nat["back"] == [{"전국": True, "입력값": {}, "배": 1, "몫": None}]
|
||||
assert nat["hit"] == [ # 줄 글 지역 = 1위 · 전국 = 되돌릴 자리 · 남은 변형 접힘
|
||||
["1위", ["M2"]],
|
||||
["전국", ["전국"]],
|
||||
["지역 변형", ["M1"]],
|
||||
]
|
||||
assert got["groups"]["pick"] == [True, False, True] # 재료비 있는 단가산출 줄도 자재 검색
|
||||
assert got["picked"] == [
|
||||
{"로직": "GC000206", "입력값": {}, "배": 1, "몫": None},
|
||||
{"로직": "GF000211", "입력값": {}, "배": 0.1, "몫": 0.1},
|
||||
]
|
||||
assert got["inputs"] == "소량감률=50(블록), 지역=서울 · ⚠ 못 가름: 측압"
|
||||
assert got["word"] == ["만듦", "있던 것을 확인한 내용으로 덮음"]
|
||||
assert got["qty"] == "콘크리트공 0.1091 · 굴착기 0.29hr"
|
||||
assert got["target"] == "제46호표 [㎡]"
|
||||
|
||||
|
||||
def test_별칭_장부_화면_합침():
|
||||
got = _check()["ledger"]
|
||||
assert got["key"] == "m02-hopyo-ledger:company:7"
|
||||
assert got["merged"] == [
|
||||
["나||m3", "personal", True, True, "안 씀"], # 고친 줄
|
||||
["다||개", "personal", True, True, "짝"], # 위 층 → 내 층으로 고쳐 씀
|
||||
["다||개", "system", False, False, "짝"], # 위 층 = 그대로 읽기만
|
||||
] # 지운 줄(가) 없음
|
||||
assert got["filter"] == ["다||개", "다||개"] and got["kind"] == ["나||m3"]
|
||||
assert got["pair"] == "GC000198 × 1 · 몫 0.1 · 소량감률=50"
|
||||
assert got["entry"]["짝"] == [] and "층" not in got["entry"]
|
||||
|
||||
|
||||
def test_길_만들기_통째_만들_층_미리_셈_장부():
|
||||
calls = _check()["calls"]
|
||||
build = "/api/m02/layers/company/hopyo-build?owner=7"
|
||||
head = {"층": "system", "주인": None, "키": "HP-000001", "번": 3}
|
||||
assert calls[0]["url"] == build and calls[0]["body"]["줄"][0]["차례"] == 1
|
||||
assert {k: calls[0]["body"][k] for k in head} == head and "새로" not in calls[0]["body"]
|
||||
assert calls[1]["body"] == {**head, "통째": {"대상": [{"로직": "GF000225", "배": 1}]}}
|
||||
assert calls[2] == {
|
||||
"url": "/api/m02/layers/system/hopyo/HP-000001/3?to=company&to_owner=7",
|
||||
"method": "GET",
|
||||
"body": None,
|
||||
}
|
||||
assert calls[3]["url"] == "/api/m02/layers/personal/hopyo/HP-000001/3/guess"
|
||||
assert calls[3]["body"]["대상"][0]["몫"] == 0.1
|
||||
assert calls[4]["url"] == "/api/m02/layers/company/hopyo-ledger?owner=7"
|
||||
assert calls[5]["method"] == "PUT" and calls[5]["body"] == {"줄": {"가|b|m3": None}}
|
||||
|
||||
|
||||
def test_로직_추가_확인된_것은_바로_참조_아니면_확인_창():
|
||||
got = _check()["pick"]
|
||||
assert got["picked"] == [
|
||||
{
|
||||
"ref": {"층": "company", "주인": "7", "키": "SC-000009"},
|
||||
"name": "임도표지석",
|
||||
"alias": "임도표지석",
|
||||
}
|
||||
]
|
||||
assert got["calls"] == [] # 확인 없이 만들지 않음
|
||||
assert got["opened"] == ["확인 — 북부청.xls 제4호표 배수관"]
|
||||
|
||||
|
||||
def test_엑셀_보기_창_하위_호표_먼저():
|
||||
got = _check()["sheet"]
|
||||
assert got["order"] == [46, 45, 3, 7, 8] # 순환은 남은 차례
|
||||
# 다 연결만 녹색 · 0/0 색 없음(359-2) · 358-3 연결할 · 연결된 두 칸이 있으면 그것
|
||||
assert got["linked"] == [True, False, False, False, True, False, False]
|
||||
|
||||
|
||||
def _pick() -> dict:
|
||||
row = {**ROW, "확인": {"층": "company", "주인": "7", "키": "SC-000009"}}
|
||||
return _node("helper_m02_hopyo_pick.cjs", {"rows": [row], "built": {}})
|
||||
|
||||
|
||||
def test_고르기_창_실무_호표_줄():
|
||||
got = _pick()
|
||||
assert got["calls"][0] == {
|
||||
"url": "/api/m02/layers/company/hopyo-picks?owner=7",
|
||||
"method": "GET",
|
||||
"body": None,
|
||||
}
|
||||
assert got["kinds"] == ["logic", "precombo", "hopyo"]
|
||||
assert got["rows"] == [
|
||||
{
|
||||
"kind": "hopyo",
|
||||
"key": "system||HP-000001|3",
|
||||
"tag": "실무 호표",
|
||||
"name": "임도표지석 910×600×150",
|
||||
"sub": "북부청.xls · 제3호표 · 마스터",
|
||||
"unit": "개소 · 확인 SC-000009",
|
||||
},
|
||||
{
|
||||
"kind": "hopyo",
|
||||
"key": "system||HP-000001|엑셀",
|
||||
"tag": "실무 호표",
|
||||
"name": "엑셀로 보기 · 여러 호표",
|
||||
"sub": "북부청.xls · 마스터",
|
||||
"unit": "호표 1",
|
||||
},
|
||||
]
|
||||
assert got["picked"][0]["ref"]["키"] == "SC-000009"
|
||||
assert got["project"] is True # 프로젝트 층 = 덧줄 없음
|
||||
|
||||
|
||||
def test_글_도우미와_근거_꼬리표():
|
||||
got = _pick()
|
||||
view = got["view"]
|
||||
assert view["target"] == ["구조물터파기 [㎥]", "레미콘 25-21-120 [㎥]", "제46호표 [㎡]"]
|
||||
assert view["inputs"] == "횟수=4회사용시, 지역=서울 · ⚠ 못 가름: 보정작업"
|
||||
basis = got["basis"]
|
||||
assert basis["ok"] == [True, False, False, False, False] # 조항 같음 · 직종 · 단위 모두
|
||||
assert basis["plain"] == [True, True] # 줄 직종이 보통인부뿐 · 없음 = 직종 안 봄
|
||||
assert basis["tag"] == [
|
||||
"조항 건설 공통 3-2-1 · 직종 특별인부 · 단위 같음",
|
||||
"조항 건설 공통 3-2-1 · 단위 같음",
|
||||
"",
|
||||
]
|
||||
assert basis["source"] == [
|
||||
"출처 건설표준품셈 3-2-1 · 직종 특별인부 · 단가산출 93–101행",
|
||||
"단가산출 7행",
|
||||
"",
|
||||
]
|
||||
|
||||
|
||||
def test_엑셀_보기_창_행과_호표():
|
||||
got = _pick()
|
||||
assert got["calls"][1] == {
|
||||
"url": "/api/m02/layers/company/hopyo-grid/HP-000001?owner=7",
|
||||
"method": "GET",
|
||||
"body": None,
|
||||
}
|
||||
sheet = got["sheet"]
|
||||
assert sheet["at"] == [None, 3, 3, 3, 4, 4, None] # 22행 → 제3호표 · 범위 밖 = 없음
|
||||
assert sheet["span"] == "B20:O24" # 제3호표 영역 = B열 ~ 끝 열(열 폭 정보 O)
|
||||
assert sheet["last"] == ["AA", "A"]
|
||||
assert sheet["rows"] == ["B4345:F4345", "B93:F101"] # 단가산출 행 B~F 열
|
||||
|
||||
|
||||
# ── 소스 ──────────────────────────────────────────────────────────────
|
||||
def test_옛_즉시_확정과_바로_만들기_없음():
|
||||
texts = {name: _text(f"M02_MasterTemplete_{name}.ts") for name in NEW}
|
||||
every = "\n".join(texts.values())
|
||||
for gone in ("saveHopyoMemory", "hopyo-memory", "HopyoMemory", "memoryOf", "basisPick"):
|
||||
assert gone not in every, gone
|
||||
for label in (
|
||||
"같음 모두 확정",
|
||||
"근거 맞음 모두 확정",
|
||||
'"확정"',
|
||||
'"사전조합 만들기"',
|
||||
'"불러오기" 옛 단추', # 718-1 — 「불러오기」 = 칸 이름 · 마법사 단추라 낱말은 뺌
|
||||
):
|
||||
assert label not in every, label
|
||||
users = sorted(name for name, text in texts.items() if "buildHopyo(" in text)
|
||||
assert users == [
|
||||
"Hopyo_Check",
|
||||
"Hopyo_SheetMake",
|
||||
] # 확인 창 · 엑셀 보기 [저장] 물음 뒤(785-4 만들기 셋 = SheetMake)
|
||||
check = texts["Hopyo_Check"]
|
||||
assert '`${L("M02_PrecomboTitle")} 생성`' in check and "맞음 · 별칭 일괄 체크" in check
|
||||
assert "확인하고" not in check # 359-4 명사형
|
||||
assert "showLoadingOverlay()" in check and "dropDraft(k)" in check
|
||||
assert "m02-hopyo-check:" in check and "POLL_MS" in check
|
||||
sheet = texts["Hopyo_SheetMake"]
|
||||
# 359-2 — 펼침 표 · [확인 창 열기] · [이대로 n개 만들기] 없음 · [저장] = 물음 한 번 뒤 체크 줄로 만들기
|
||||
for gone in ("확인 창 열기", "이대로", "review(", "확인하고 만들기"):
|
||||
assert gone not in sheet, gone
|
||||
save = sheet.split("async function save(")[1].split("async function makeAll(")[0]
|
||||
assert 'showConfirmDialog(ask, "저장")' in save and "hasDraft(" in save
|
||||
assert "건너뜀 — 체크 줄 없음" in save and "await makeAll(ready, skipped);" in save
|
||||
assert (
|
||||
"buildOrder(" in sheet and "unmadeKids(" in sheet and "leftAt(" in sheet
|
||||
) # 하위 먼저 · 함께(777-1)
|
||||
sheet = texts["Hopyo_Sheet"] # 목록 · [수정] = 엑셀 보기 몸
|
||||
edit = sheet.split("const editButton =")[1].split("function paintList(")[0]
|
||||
assert (
|
||||
'"수정"' in edit and "e.stopPropagation();" in edit and "createJudgeRows({" in edit
|
||||
) # 785-6 줄 판
|
||||
row = sheet.split("function paintList(")[1]
|
||||
assert row.index("m02-hopyo-sheet__no") < row.index("editButton(h)") < row.index("__name")
|
||||
assert 'linked.classList.toggle("is-linked", allLinked(h));' in row
|
||||
pick = texts["Hopyo_Pick"]
|
||||
assert "if (row.확인)" in pick and 'import("./M02_MasterTemplete_Hopyo_Check")' in pick
|
||||
assert not (M02 / "M02_MasterTemplete_Hopyo_View.ts").exists() # 본 칸 표 없음(359-1)
|
||||
ledger = texts["Hopyo_Ledger"]
|
||||
assert "m02-hopyo-ledger:" in ledger and "saveLedger(" in ledger
|
||||
for gone in ("발주처", "회사로 올리기", "쓰인 횟수"):
|
||||
assert gone not in ledger.split("*/", 1)[1] # 머리 글 「안 둠」 밖
|
||||
|
||||
|
||||
def test_좌측_판_자리와_권한():
|
||||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||||
assert 'import { buildHopyoSection } from "./M02_MasterTemplete_Hopyo_Side";' in side
|
||||
@@ -375,182 +44,6 @@ def test_좌측_판_자리와_권한():
|
||||
assert gone not in imp + hopyo_side, gone # 중간 걸음 · 단가산출 체크 없음
|
||||
|
||||
|
||||
def test_로직_추가_한_곳과_파일_규칙():
|
||||
detail = _text("M02_MasterTemplete_Labor_Detail.ts")
|
||||
assert len(detail.splitlines()) <= 700 # 700 줄 넘지 않음(717-3 나눔으로 줄어듦)
|
||||
assert "withHopyo(library)," in detail
|
||||
pick = _text("M02_MasterTemplete_Labor_Pick.ts")
|
||||
assert "more?: () => Promise<PickExtra[]>;" in pick and "...later" in pick
|
||||
assert "if (pre?.query) finder.search.value = pre.query;" in pick # 찾기 글 미리 넣음
|
||||
for name in NEW:
|
||||
text = _text(f"M02_MasterTemplete_{name}.ts")
|
||||
assert len(text.splitlines()) <= 700, name
|
||||
css = _text("M02_MasterTemplete_Hopyo.css")
|
||||
assert "var(--color-" in css and not re.search(r"#[0-9a-fA-F]{3,6}\b", css)
|
||||
for state in ("맞음", "별칭", "다른점", "후보"):
|
||||
assert f".m02-hopyo__state.is-{state}" in css
|
||||
assert '"./M02_MasterTemplete_Hopyo.css"' in _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||||
|
||||
|
||||
def test_엑셀_보기_창_여는_자리와_늦게_불러옴():
|
||||
lazy = 'import("./M02_MasterTemplete_Hopyo_Sheet")'
|
||||
side = _text("M02_MasterTemplete_Hopyo_Import.ts")
|
||||
assert "return show(pick, got, true);" in _text(
|
||||
"M02_MasterTemplete_Hopyo_Side.ts"
|
||||
) # 올린 직후 바로 본 칸
|
||||
assert lazy in side and "m.buildHopyoSheet({" in side # 본 칸 몸 = 늦게 불러옴
|
||||
check = _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||||
assert lazy in check and 'label: "엑셀 보기"' in check and "호표: opt.no" in check
|
||||
assert "엑셀에서 보기" not in check
|
||||
assert "단산행: 행" in check
|
||||
pick = _text("M02_MasterTemplete_Hopyo_Pick.ts")
|
||||
assert lazy in pick
|
||||
static = r'^import (?!type )[^(;]*"\./M02_MasterTemplete_Hopyo_Sheet"'
|
||||
for text in (side, pick, check):
|
||||
assert not re.search(static, text, re.M) # 정적 import 없음(타입만)
|
||||
sheet = _text("M02_MasterTemplete_Hopyo_Sheet.ts")
|
||||
assert 'import("../A00_Common/spreadsheet/spreadsheet")' in sheet
|
||||
assert not re.search(
|
||||
r'^import \{[^}]*\} from "\.\./A00_Common/spreadsheet/spreadsheet"', sheet, re.M
|
||||
)
|
||||
assert "readOnly: true" in sheet and "wide: true" in sheet
|
||||
|
||||
|
||||
def test_실물_응답_대역_확인_창():
|
||||
"""338-4 — 가 창 실물 응답(`fixtures/m02_hopyo_view_sample.json`)을 확인 창 · 고르기 창 도우미에
|
||||
그대로 먹임 — 체크 = 서버 제안 · 몸 꼴 · 품 비교 · 옵션 출처 · 후보 판정 꼬리표."""
|
||||
sample = json.loads((Path(HERE) / "fixtures" / "m02_hopyo_view_sample.json").read_bytes())
|
||||
lines = sample["호표"]["호표"]["줄"]
|
||||
got = _node("helper_m02_hopyo_check.cjs", {"real": lines})
|
||||
by = {x["이름"]: x for x in got["lines"]}
|
||||
remicon = by["레미콘타설(장비사용)"]
|
||||
assert remicon["상태"] == "맞음" and remicon["checked"] and remicon["checkable"]
|
||||
assert (
|
||||
remicon["compare"]
|
||||
== "콘크리트공 0.1091 → 0.1091 ✓ · 보통인부 0.03636 → 0.03636 ✓ · 굴착기 0.29 → 0.2909 ✓"
|
||||
)
|
||||
assert (
|
||||
remicon["inputs"]
|
||||
== "타설방법=장비사용타설(규격), 구조물=철근구조물(규격), 소량감률=50(블록)"
|
||||
)
|
||||
assert remicon["qty"] == "콘크리트공 0.1091 · 보통인부 0.03636 · 굴착기 0.29hr"
|
||||
assert remicon["cands"][0][1] == "맞음" and remicon["cands"][0][2].startswith("조항 건설품셈")
|
||||
rebar = by["철근가공조립(현장)"]
|
||||
assert rebar["상태"] == "다른 점" and not rebar["checked"] and rebar["checkable"]
|
||||
assert [x["차례"] for x in got["body"]] == [x["차례"] for x in lines if x["맞춤"]["체크"]]
|
||||
assert all({"로직", "입력값", "배"} <= set(t) for x in got["body"] for t in x["대상"])
|
||||
assert got["sure"] == [x["차례"] for x in lines if x["맞춤"]["상태"] == "맞음"]
|
||||
|
||||
|
||||
def test_확인_창_손질_339():
|
||||
"""339 덧고침 — ① 줄 표 꼴(한글 낱말 줄바꿈 · 옵션 · 품 비교 한 줄씩 · 단추 세로) · 장부 품명 · 규격 폭
|
||||
② 만들기 뒤 좌측 사전조합 목록 다시 ③ 엑셀 보기 창 목록 셈 끝까지 다시 읽음 · 펼침 표 숫자 판정 칸."""
|
||||
css = _text("M02_MasterTemplete_Hopyo.css")
|
||||
assert "word-break: keep-all;" in css and "white-space: pre-line;" in css
|
||||
assert ".m02-hopyo__lines .m02-hopyo__acts > *" in css
|
||||
assert "nth-child(9)" not in css # 359-3 — 칸 차례 대신 칸 이름
|
||||
assert ".m02-hopyo-ledger__table td:nth-child(1)" in css
|
||||
assert "m02-hopyo-ledger__table" in _text("M02_MasterTemplete_Hopyo_Ledger.ts")
|
||||
rows = _text("M02_MasterTemplete_Hopyo_CheckRows.ts")
|
||||
assert r'text: compare.split(" · ").join("\n")' in rows # 품 비교 = 항목마다 한 줄
|
||||
assert "m02-hopyo__opts" in rows # 옵션 = 항목마다 한 줄
|
||||
check = _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||||
make = check.split("async function make(")[1].split("function builtBlock(")[0]
|
||||
assert make.index("document.dispatchEvent(new Event(HOPYO_BUILT));") < make.index("opt.onBuilt")
|
||||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||||
assert "document.addEventListener(HOPYO_BUILT, onBuilt);" in side
|
||||
onb = side.split("const onBuilt")[1].split(";")[0]
|
||||
assert "precombo.refresh()" in onb and "sangeun.refresh()" in onb # 717-2a — 둘 다 새로고침
|
||||
sheet = _text("M02_MasterTemplete_Hopyo_Sheet.ts")
|
||||
assert "poll(file);" in sheet and "poll(next);" in sheet and "POLL_MS" in sheet
|
||||
assert "if (ok) document.dispatchEvent(new Event(HOPYO_BUILT));" in _text(
|
||||
"M02_MasterTemplete_Hopyo_SheetMake.ts"
|
||||
)
|
||||
|
||||
|
||||
def test_엑셀_보기_머리_막대_셈_끝까지_다시_읽음_359():
|
||||
"""339 덧고침 2 → 359-1 · 785-4 — 옛 본 칸 표 몫(시트 · 호표 수 · 검산 · 「제안 n / 줄 m」 · 「셈 중」)은
|
||||
엑셀 보기 몸 머리(부르는 쪽 덧붙임 `headExtra` — 층 · 안 이은 줄 · [끝내기]) · 셈 중이면 3 초마다 다시 읽음(머리 · 목록 함께)."""
|
||||
sheet = _text("M02_MasterTemplete_Hopyo_Sheet.ts")
|
||||
head = sheet.split("function paintHead(")[1].split("/** 목록 강조")[0]
|
||||
for word in ("시트 ", "호표 ", "검산 어긋남", "검산 맞음", "제안 ", "숫자 판정 셈 중"):
|
||||
assert word in head, word
|
||||
assert "...(opt.headExtra ?? [])" in head and "opt.onRead?.(file);" in head
|
||||
assert "dropButton" not in sheet and "onDeleted" not in sheet # [삭제] 걷음 = [끝내기] 한 길
|
||||
poll = sheet.split("function poll(")[1].split("async function load(")[0]
|
||||
assert "paintHead(next);" in poll and "paintList();" in poll and "poll(next);" in poll
|
||||
|
||||
|
||||
def test_확인_창_표_일위대가_상세_꼴_359():
|
||||
"""359-3 — 로직 표 · 자재 표(일위대가 상세 꼴) + 품 비교 · 단추 · 드롭다운 글(저장 값 그대로) ·
|
||||
금액 = 줄마다 서버 calc 미리 셈(`logic/` 문서 꼴 · Build 와 같은 수량 규칙)."""
|
||||
got = _check()["draft"]
|
||||
assert got["logic"] == {
|
||||
"종류": "일위대가",
|
||||
"열": "",
|
||||
"단위": None,
|
||||
"담은로직": [ # 같은 로직 둘째 · 호표 · 배 없음 = 뺌
|
||||
{"로직": "GC000198", "별칭": None, "메모": None, "입력값": {"소량감률": 50},
|
||||
"수량": {"손": 1}},
|
||||
],
|
||||
"자재": [],
|
||||
} # fmt: skip
|
||||
assert got["mat"]["자재"] == [ # 같은 자재 = 2 × 1 + 2 × 0.5
|
||||
{"코드": "MT1", "이름": None, "규격": None, "단위": None, "수량": {"손": 3},
|
||||
"별도계상": "별도계상"},
|
||||
] # fmt: skip
|
||||
assert got["direct"]["별도계상"] == "직접계산" and got["direct"]["수량"] == {"손": 0}
|
||||
assert got["none"] == [None, None, None] # 전국 자재(361-1) = 키 없어 미리 셈 없음
|
||||
assert got["table"] == ["mat", "logic", "logic", "mat", "logic", "mat"]
|
||||
rows = _text("M02_MasterTemplete_Hopyo_CheckRows.ts")
|
||||
assert '["", "추가"],' in rows and '["우리에 없음", "로직없음"],' in rows
|
||||
assert '["안 씀", "사용안함"],' in rows and "뺌 — " not in rows
|
||||
for word in ('"고르기"', '"원문 보기"', '"엑셀 보기"', '"옵션"', '"품 비교"'):
|
||||
assert word in rows, word
|
||||
assert 'import { moneyCell, PRICE_SLOTS } from "./M02_MasterTemplete_Labor_Rows";' in rows
|
||||
assert "...markCells(p?.세칸 ?? { 별도계상:" in rows # 361-2 서버 세 칸 먼저
|
||||
assert "...markHeads()," in rows
|
||||
for head in ("Col_Order", "Sub", "Col_Name", "Col_Alias", "Labor_QtyCol", "Col_Unit"):
|
||||
assert f'tc("{head}")' in rows, head
|
||||
check = _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||||
assert "calcLabor(target.layer, DRAFT_COLUMN, doc, {}, target.owner)" in check
|
||||
assert "checkTables(h.줄, ctx())" in check and "draftDoc(line, partsOf(line, d))" in check
|
||||
assert "function lineRow(" not in check and "function partCell(" not in check
|
||||
draft = _text("logic/M02_MasterTemplete_Hopyo_Draft.ts")
|
||||
assert "M02_Hopyo_Build" in draft # 정본 짝 표시
|
||||
code = draft.split("*/", 1)[1] # 머리 글 밖
|
||||
for gone in ("document", "fetch(", "sessionStorage", "@ui/"):
|
||||
assert gone not in code, gone
|
||||
|
||||
|
||||
def test_명사형_단추와_별칭_장부_풀이_359():
|
||||
"""359-4 — 별칭 장부 뜻 한 줄(좌측 단추 title · 화면 머리) · M02 실무자료 단추 = 명사형 ·
|
||||
갈래 글 = 확인 창 드롭다운과 같음(저장 값 그대로)."""
|
||||
ledger = _text("M02_MasterTemplete_Hopyo_Ledger.ts")
|
||||
hint = "엑셀 품명 ↔ 우리 로직 · 자재 짝을 기억해 두었다가 같은 품명이 나오면 먼저 제안"
|
||||
assert hint in ledger and "`${LEDGER_HINT}" in ledger
|
||||
side = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||||
assert "title: LEDGER_HINT" in side
|
||||
assert 'label: master ? "삭제" : "거두기"' in ledger # 739-1 내 제안 = 거두기
|
||||
for label in ('"변경 취소"', '"항목 추가"', '"내 층 복사"'):
|
||||
assert f"label: {label}" in ledger, label
|
||||
assert '"우리에 없음": "로직없음"' in ledger and '"안 씀": "사용안함"' in ledger
|
||||
mine = ("Hopyo_Side", "Hopyo_Sheet", "Hopyo_Check", "Hopyo_CheckRows", "Hopyo_Ledger")
|
||||
mine += ("Hopyo_Import", "Hopyo_SheetMake")
|
||||
every = "\n".join(_text(f"M02_MasterTemplete_{n}.ts") for n in mine)
|
||||
for gone in (
|
||||
"고친 것 버림",
|
||||
"로직 더함",
|
||||
"내 층으로 고쳐 씀",
|
||||
"확인하고 사전조합 만들기",
|
||||
"엑셀 올리기",
|
||||
"엑셀에서 보기",
|
||||
"모두 체크 (",
|
||||
'label: "지우기"',
|
||||
):
|
||||
assert gone not in every, gone
|
||||
|
||||
|
||||
def test_372_2_저장은_개인_층에_삭제는_화면_층_권한():
|
||||
"""718-1 — 만들 층 = 올린 층 · [끝내기] 로 올린 파일 지움 → 빈 본 칸(785-4: 엑셀 보기 [삭제] 걷음) ·
|
||||
743-1 — 떠남(화면 바뀜 · 페이지 닫음) = 쥔 파일 지움."""
|
||||
@@ -562,77 +55,3 @@ def test_372_2_저장은_개인_층에_삭제는_화면_층_권한():
|
||||
assert 'addEventListener("pagehide", leave)' in imp and " leave,\n" in imp
|
||||
panel = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||||
assert panel.count("open?.imp?.leave()") == 2 and panel.count("open.imp?.leave()") == 1
|
||||
|
||||
|
||||
def test_779_세_보기_칠_뛰기_창_다시_씀():
|
||||
"""779-4 — 탭 = 서버 역할 id(이름으로 안 찾음) · 칠 = 공용 격자 `칠` · 범례 부품 · 색은 공통 값 ·
|
||||
확인 창 캐시 고침 = 다시 칠 알림 · 779-6 — 같은 파일 창은 다시 씀(앞으로 · 제자리) · 격자 다시 안 받음."""
|
||||
sheet = _text("M02_MasterTemplete_Hopyo_Sheet.ts")
|
||||
nav = _text("M02_MasterTemplete_Hopyo_SheetNav.ts")
|
||||
check = _text("M02_MasterTemplete_Hopyo_Check.ts")
|
||||
assert "Nav.nameTabs(book, grid)" in sheet and "칠: nav?.fill" in sheet
|
||||
assert "nav?.at(s);" in sheet and "nav?.marks(briefs);" in sheet
|
||||
assert "[detail, ...DANSAN]" in sheet # 단산행 = 역할 id 먼저(STmate 「단가산출근거」)
|
||||
assert "if (opened.has(id)) return opened.get(id)!(opt);" in sheet
|
||||
assert "Nav.gridOf(id, opt.scope, opt.key)" in sheet and "readHopyoGrid" not in sheet
|
||||
assert "above = Nav.raise(left);" in sheet and "Nav.lower(left, above.splice(0))" in sheet
|
||||
assert "createLegend([" in nav and 'kind: "line"' in nav
|
||||
assert re.findall(r'color: "([^"]+)"', nav) == [
|
||||
f"var(--color-{x})"
|
||||
for x in ("success", "warning", "info", "danger", "text-muted", "accent")
|
||||
]
|
||||
assert 'HOPYO_DRAFT = "m02-hopyo-draft"' in nav
|
||||
save = check.split("export function saveDraft(")[1].split("export function dropDraft(")[0]
|
||||
assert 'document.dispatchEvent(new Event("m02-hopyo-draft"));' in save
|
||||
for name in (
|
||||
"M02_MasterTemplete_Hopyo_SheetNav.ts",
|
||||
"logic/M02_MasterTemplete_Hopyo_SheetMap.ts",
|
||||
):
|
||||
text = _text(name)
|
||||
assert (
|
||||
'el("button"' not in text
|
||||
and 'el("input"' not in text
|
||||
and "#" not in text.split("*/", 1)[1]
|
||||
)
|
||||
pure = _text("logic/M02_MasterTemplete_Hopyo_SheetMap.ts").split("*/", 1)[1]
|
||||
for word in ("document", "fetch", "sessionStorage", "import "):
|
||||
assert word not in pure, word
|
||||
assert (
|
||||
len(check.splitlines()) <= 663
|
||||
and len(_text("M02_MasterTemplete_Hopyo_Api.ts").splitlines()) <= 676
|
||||
)
|
||||
|
||||
|
||||
def test_단위_환산_칸_보이기_784_2():
|
||||
"""784-2 — 배 1 · 단위 같음 = 칸 숨김 · 몫 줄 = 몫만 · 빈칸 · 추정 · 단위 다름 = 입력 칸 + 두 단위 글 재료 ·
|
||||
화면(CheckRows · Ledger)은 「배」 대신 「단위 환산」 · 「엑셀 1 ㎡ = 우리 [ ] 본」 꼴."""
|
||||
cases = [
|
||||
{"part": {"배": 1, "환산": {"배": 1, "갈래": "같음"}}, "from": "㎡", "to": "㎡"},
|
||||
{"part": {"배": 1, "환산": {"배": 1, "갈래": "두 단위"}}, "from": "공/㎥", "to": "공㎥"},
|
||||
{"part": {"배": 0.1, "몫": 0.1, "환산": {"배": 1, "갈래": "같음"}}, "from": "㎥", "to": "㎥"},
|
||||
{"part": {"배": None, "환산": {"배": None, "갈래": "입력"}}, "from": "㎡", "to": "본"},
|
||||
{"part": {"배": 1, "환산": {"배": None, "갈래": "입력"}}, "from": "m", "to": "본"},
|
||||
{"part": {"배": 0.2, "환산": {"배": 0.2, "갈래": "추정"}}, "from": "㎡", "to": "㎥"},
|
||||
{"part": {"배": 100, "환산": {"배": 100, "갈래": "단위표"}}, "from": "m", "to": "cm"},
|
||||
{"part": {"배": 0.01, "몫": 0.1, "환산": {"배": 0.1, "갈래": "단위표"}}, "from": "a", "to": "b"},
|
||||
{"part": {"배": 1}, "from": "m2", "to": "㎡"},
|
||||
{"part": {"배": 1}, "from": "m", "to": "본"},
|
||||
{"part": {"배": 1, "몫": 1, "환산": {"배": 1, "갈래": "같음"}}, "from": "㎡", "to": "㎡"},
|
||||
] # fmt: skip
|
||||
got = _node("helper_m02_hopyo_unit.cjs", {"cases": cases})
|
||||
assert [g["mode"] for g in got] == [
|
||||
"same", "same", "quota", "input", "input", "input", "input", "input", "same", "input", "same",
|
||||
] # fmt: skip
|
||||
assert (got[3]["from"], got[3]["to"]) == ("㎡", "본") # 「엑셀 1 ㎡ = 우리 [ ] 본」
|
||||
assert got[5]["estimated"] and got[5]["thick"] == 0.2 # 「두께 0.2m 로 추정」
|
||||
assert not got[3]["estimated"] and got[3]["thick"] is None
|
||||
rows = _text("M02_MasterTemplete_Hopyo_CheckRows.ts")
|
||||
assert "unitView(p, line.단위, p.단위)" in rows and "M02_HopyoUnitSame" in rows
|
||||
assert "배 — 우리 수량" not in rows
|
||||
ledger = _text("M02_MasterTemplete_Hopyo_Ledger.ts")
|
||||
assert "unitField(times, { from: r.원래.단위" in ledger and 'title: "배"' not in ledger
|
||||
pure = _text("logic/M02_MasterTemplete_Hopyo_Unit.ts").split("*/", 1)[1]
|
||||
for word in ("document", "fetch", "sessionStorage", "import "):
|
||||
assert word not in pure, word
|
||||
for name in ("CheckRows", "Ledger"):
|
||||
assert len(_text(f"M02_MasterTemplete_Hopyo_{name}.ts").splitlines()) <= 700
|
||||
|
||||
Reference in New Issue
Block a user