Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/* 729-3 안 이은 줄 화면 헬퍼 — `Hopyo_Left` · `Hopyo_Check` · `Api_Fetch` · `Labor_Unlinked` · `Hopyo_WizardSk` 도우미를
|
||||
* 트랜스파일해 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_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();
|
||||
|
||||
(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 sk = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_WizardSk.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.이름),
|
||||
cand: sk.candLabel({ 이름: "되메우기", 단위: "㎥", 판정: "후보", 글: "후보" }),
|
||||
cand2: sk.candLabel({
|
||||
이름: "되메우기",
|
||||
단위: "㎥",
|
||||
판정: "다른 점",
|
||||
글: "보통인부 품 다름",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error?.stack ?? error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -14,6 +14,7 @@ const STUB_FILES = [
|
||||
"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 = [];
|
||||
|
||||
@@ -15,6 +15,7 @@ const STUB_FILES = [
|
||||
"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({
|
||||
|
||||
@@ -15,6 +15,7 @@ const STUB_FILES = [
|
||||
"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({
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""일위대가 불러오기 마법사 안 이은 줄 화면(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"] == ["안이음"]
|
||||
assert got["cand"] == "되메우기 [㎥] · 후보"
|
||||
assert got["cand2"] == "되메우기 [㎥] · 다른 점 · 보통인부 품 다름"
|
||||
|
||||
|
||||
def _text(name: str) -> str:
|
||||
return (M02 / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_마법사_끝내기_막음_단가산출_칸_목록_열():
|
||||
wizard = _text("M02_MasterTemplete_Hopyo_Wizard.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 "sangeunStep(scope, file.키)" in wizard and "/sangeun`" not in wizard
|
||||
sk = _text("M02_MasterTemplete_Hopyo_WizardSk.ts")
|
||||
assert "makeSangeun(scope, key, picks)" in sk and "picks[no] = { 로직: s.value }" in sk
|
||||
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")
|
||||
for name in (
|
||||
"M02_MasterTemplete_Hopyo_Left.ts",
|
||||
"M02_MasterTemplete_Hopyo_WizardSk.ts",
|
||||
"M02_MasterTemplete_Labor_Unlinked.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 # 색 = 공통 값
|
||||
@@ -358,7 +358,9 @@ def test_좌측_판_자리와_권한():
|
||||
assert 'accept: ".xls,.xlsx"' in wizard and "input.hidden = true;" in wizard
|
||||
assert "accessFor(layer, null, user).canWrite" in wizard # 결과 층 = 쓸 수 있는 층만
|
||||
assert 'let pick: ManageLayer = "personal";' in wizard # 기본 개인
|
||||
assert "let wantSangeun = false;" in wizard and "/sangeun`" in wizard # 결과 단가산출 체크
|
||||
assert (
|
||||
"let wantSangeun = false;" in wizard and "sangeunStep(scope, file.키)" in wizard
|
||||
) # 결과 단가산출 체크 → 단가산출 칸(729-3)
|
||||
|
||||
|
||||
def test_로직_추가_한_곳과_파일_규칙():
|
||||
|
||||
Reference in New Issue
Block a user