Merge remote-tracking branches 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_4' into main_laptop_1
This commit is contained in:
@@ -314,7 +314,7 @@ def _rounded(value: Any, r: dict) -> Any:
|
||||
|
||||
|
||||
def _lookup(rule: dict, block: Any, conds: dict) -> tuple[Any, str | None]:
|
||||
"""표에서 찾기 — 같음 · 범위(「이름·아래」 < 값 ≤ 「이름·위」 · 빈 끝 열림) · 맞은 줄 하나만."""
|
||||
"""표에서 찾기 — 같음 · 범위(아래 < 값 ≤ 위 · 빈 끝 열림 · 아래 = 위면 같음) · 맞은 줄 하나."""
|
||||
if not isinstance(block, list) or len(block) < 2:
|
||||
return None, "오류"
|
||||
head = [str(h if h is not None else "") for h in block[0]]
|
||||
@@ -336,6 +336,8 @@ def _lookup(rule: dict, block: Any, conds: dict) -> tuple[Any, str | None]:
|
||||
|
||||
def fits(row, lo=lo, hi=hi, x=x) -> bool:
|
||||
low, high = _num_of(row[lo]), _num_of(row[hi])
|
||||
if low is not None and low == high: # 숫자 하나(아래 = 위) = 같음(브레인 정함)
|
||||
return x is not None and x == low
|
||||
return (
|
||||
x is not None
|
||||
and (row[lo] is None or (low is not None and low < x))
|
||||
|
||||
@@ -21,12 +21,10 @@ import {
|
||||
type SubInfo,
|
||||
} from "../../M01_MasterData/M01_MasterData_Api_Fetch";
|
||||
import { cfPlain, cfUserElementAdd, cfUserElements, cfValues } from "./M02_DF_Api";
|
||||
import type { CfBox, CfElementRef, CfGroup, CfLogic } from "./M02_DF_Model";
|
||||
import type { CfBox, CfGroup, CfLogic } from "./M02_DF_Model";
|
||||
import {
|
||||
GROUPS,
|
||||
M01_OF,
|
||||
MACHINE_SEP,
|
||||
USER_PREFIX,
|
||||
addElement,
|
||||
elementOf,
|
||||
machineFields,
|
||||
@@ -243,39 +241,10 @@ function drawChosen(
|
||||
if (node && a.ok && a.value !== null && a.value !== undefined)
|
||||
node.textContent = typeof a.value === "number" ? fmt(a.value) : String(a.value);
|
||||
}),
|
||||
// Api 가 아직 값을 못 주면 M01 줄에서 바로 읽어 보임(값 칸만 — 요소는 그대로)
|
||||
() =>
|
||||
els.forEach(
|
||||
(e) =>
|
||||
void m01Value(group, e).then((v) => {
|
||||
const node = values.get(e.ref);
|
||||
if (node && v !== null) node.textContent = fmt(v);
|
||||
}),
|
||||
),
|
||||
() => undefined, // 값을 못 받아도 요소는 넣을 수 있음 — 값 칸만 「—」
|
||||
);
|
||||
}
|
||||
|
||||
/** 요소 한 개의 값을 M01 줄에서 읽음 — 이름으로 찾아 키가 같은 줄 · 못 찾으면 null */
|
||||
async function m01Value(group: CfGroup, e: CfElementRef): Promise<number | null> {
|
||||
if (e.ref.startsWith(USER_PREFIX)) return null;
|
||||
try {
|
||||
const [key, field] = e.ref.split(MACHINE_SEP);
|
||||
const page = await fetchRows(
|
||||
await fileOf(group),
|
||||
1,
|
||||
30,
|
||||
e.name.split(" · ")[0].split(" ")[0],
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
);
|
||||
const row = (page.rows as M01Row[]).find((r) => r["키"] === key);
|
||||
return row ? valueOf(group, row, field || "가격") : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** M01 줄 찾기 — 구분 · 상세구분 · 이름 · 내가 만든 것도 함께 */
|
||||
async function drawFind(
|
||||
host: HTMLElement,
|
||||
@@ -406,7 +375,6 @@ async function drawFind(
|
||||
list,
|
||||
);
|
||||
void load();
|
||||
void USER_PREFIX;
|
||||
}
|
||||
|
||||
/** 새 요소 — M01 표 「추가」 화면 꼴(이름 · 규격 · 단위 · 값) · 마스터에 안 쓰고 사용자 요소로 */
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/* =============================================================================
|
||||
* M02_DF_InputPanel_Material.ts
|
||||
* 입력 판 — 재료 요소 방식 셋(고정 · 선택 자리 · 조건 찾기) · 자재 찾기(띄어쓰기 AND · 규격까지 · 쪽 넘기기 · 전체 수)
|
||||
* 서버 `/api/m02/df/materials` 를 먼저 부르고 아직 없으면 M01 `/pick?kind=price` 로 같은 모양을 만듦
|
||||
* 서버 `/api/m02/df/materials`(cfMaterials)
|
||||
* 글 [한, 영] 짝은 아래 TEXT · 단추는 공용 작은 크기
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import {
|
||||
createButton,
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
showToast,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { fetchFiles, fetchSubs, type SubInfo } from "../../M01_MasterData/M01_MasterData_Api_Fetch";
|
||||
import { cfValues } from "./M02_DF_Api";
|
||||
import { cfMaterials, cfValues } from "./M02_DF_Api";
|
||||
import type { CfElementRef, CfLogic, CfMaterialMode, CfMaterialRule } from "./M02_DF_Model";
|
||||
import {
|
||||
catchNote,
|
||||
@@ -101,48 +100,9 @@ export interface MatPage {
|
||||
items: MatItem[];
|
||||
}
|
||||
|
||||
async function getJson<T>(path: string): Promise<T> {
|
||||
const res = await fetch(`${API_BASE_URL}${path}`, { credentials: "include" });
|
||||
if (!res.ok) throw new Error(String(res.status));
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
/** 서버가 없으면 M01 값 줄에서 같은 모양으로 — 띄어쓰기 AND(이름 + 규격) · 쪽은 앞에서 잘라 줌 */
|
||||
async function fallback(q: MatQuery): Promise<MatPage> {
|
||||
const r = await getJson<{ items: Record<string, unknown>[] }>(
|
||||
`/m01/pick?${new URLSearchParams({ kind: "price", q: q.q, limit: "200" })}`,
|
||||
);
|
||||
const rows = r.items.filter((i) => !q.sub || i["구분"] === q.sub);
|
||||
const items = rows.map((i): MatItem => ({
|
||||
ref: String(i["ref"]),
|
||||
name: String(i["이름"] ?? ""),
|
||||
spec: String(i["규격"] ?? ""),
|
||||
unit: String(i["단위"] ?? ""),
|
||||
price: typeof i["값"] === "number" ? i["값"] : null,
|
||||
구분: String(i["구분"] ?? ""),
|
||||
}));
|
||||
const from = (q.page - 1) * q.size;
|
||||
return {
|
||||
total: items.length,
|
||||
page: q.page,
|
||||
size: q.size,
|
||||
items: items.slice(from, from + q.size),
|
||||
};
|
||||
}
|
||||
|
||||
/** 서버 자재 찾기 — 띄어쓰기로 나눈 말을 모두 포함 · 쪽 넘기기 · 전체 수 */
|
||||
export async function searchMaterials(q: MatQuery): Promise<MatPage> {
|
||||
const params = new URLSearchParams({
|
||||
q: q.q,
|
||||
page: String(q.page),
|
||||
size: String(q.size),
|
||||
sub: q.sub ?? "",
|
||||
detail: q.detail ?? "",
|
||||
});
|
||||
try {
|
||||
return await getJson<MatPage>(`/m02/df/materials?${params}`);
|
||||
} catch {
|
||||
return fallback(q);
|
||||
}
|
||||
return cfMaterials({ q: q.q, sub: q.sub, detail: q.detail, page: q.page, size: q.size });
|
||||
}
|
||||
|
||||
let subsCache: SubInfo[] | null = null;
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
* M02_DF_UnitPick.ts
|
||||
* 단가산출 흐름도 공용 단위 고르기 — 치면 걸러진 목록 · 고르기만(손글 안 받음) · 「-」 = 없음
|
||||
* 목록 = 단위 표준(산림품셈 1-2-2) 단위 + 부른 쪽이 주는 마스터 결과단위 등 + 원
|
||||
* 표준 표 읽는 길은 `setUnitSource` 로 바꿔 끼움(기본 = 처음 값 `_standard/단위표준.json`) — 다른 판도 이것을 씀
|
||||
* 표준 표 읽는 길은 `setUnitSource` 로 바꿔 끼움(기본 = 서버 /standards) — 다른 판도 이것을 씀
|
||||
* 글 [한, 영] 짝은 아래 TEXT
|
||||
* ========================================================================== */
|
||||
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { cfStandards } from "./M02_DF_Api";
|
||||
import "./M02_DF_UnitPick.css";
|
||||
|
||||
const TEXT = {
|
||||
@@ -36,9 +37,9 @@ interface StandardRow {
|
||||
"단위수량 소수자리"?: number | null;
|
||||
}
|
||||
|
||||
/** 처음 값 — 번들에 든 표준 표(단위표준.json) */
|
||||
const bundled: UnitSource = async () => {
|
||||
const table = (await import("./_standard/단위표준.json")).default as { 줄: StandardRow[] };
|
||||
/** 서버 표준 표(/standards) — 고친 표가 있으면 그것, 없으면 처음 값 */
|
||||
const fromServer: UnitSource = async () => {
|
||||
const table = (await cfStandards()).unit as { 줄: StandardRow[] };
|
||||
const units = table.줄
|
||||
.flatMap((r) => [r["규격 단위"], r["단위수량 단위"]])
|
||||
.filter((u): u is string => !!u);
|
||||
@@ -52,13 +53,13 @@ const bundled: UnitSource = async () => {
|
||||
const places = Object.fromEntries(
|
||||
Object.entries(count).map(([u, c]) => [
|
||||
u,
|
||||
Number(Object.entries(c).sort((a, b) => b[1] - a[1])[0][0]),
|
||||
Number(Object.entries(c).sort((x, y) => y[1] - x[1])[0][0]),
|
||||
]),
|
||||
);
|
||||
return { units, places };
|
||||
};
|
||||
|
||||
let source: UnitSource = bundled;
|
||||
let source: UnitSource = fromServer;
|
||||
let cache: Promise<UnitStandard> | null = null;
|
||||
|
||||
/** 표준 표 읽는 길 바꾸기(서버에서 고친 표를 읽을 때) — 목록을 다시 받음 */
|
||||
@@ -67,15 +68,20 @@ export function setUnitSource(next: UnitSource): void {
|
||||
cache = null;
|
||||
}
|
||||
|
||||
/** 표준 단위 + 원 — 같은 글은 한 번 · 「없음」 은 뺌 */
|
||||
export function loadStandard(): Promise<UnitStandard> {
|
||||
/** 표준 단위 + 원 — 같은 글은 한 번 · 「없음」 은 뺌 · 서버를 못 읽으면 원만(다음에 다시 읽음) */
|
||||
export async function loadStandard(): Promise<UnitStandard> {
|
||||
cache ??= source().then((std) => {
|
||||
const set = new Set([...std.units, "원"]);
|
||||
set.delete("없음");
|
||||
set.delete(NO_UNIT);
|
||||
return { units: [...set], places: std.places };
|
||||
});
|
||||
return cache;
|
||||
try {
|
||||
return await cache;
|
||||
} catch {
|
||||
cache = null;
|
||||
return { units: ["원"], places: {} };
|
||||
}
|
||||
}
|
||||
|
||||
/** 단위를 고르면 표준 표에서 소수자리 제안 — 없으면 null */
|
||||
|
||||
@@ -547,6 +547,24 @@ def test_선_규칙_끝수_재료_조건_설계_때_정할_것(client: TestClien
|
||||
assert _d(
|
||||
_post(client, "/api/m02/df/save", {"logic": lg})["values"]["ports"]["f"]["합"]
|
||||
) == int(Decimal("0.5") + 33000 + 3)
|
||||
lg["boxes"][0]["input"]["variables"][0]["example"] = (
|
||||
"견치돌" # 「높이 4」 줄 = 아래 = 위 → 같음(정함)
|
||||
)
|
||||
lg["boxes"][0]["input"]["variables"][1]["example"] = 4
|
||||
assert _d(
|
||||
_post(client, "/api/m02/df/save", {"logic": lg})["values"]["ports"]["f"]["합"]
|
||||
) == int(Decimal("0.7") + 33000 + 4)
|
||||
lookup = {
|
||||
"kind": "lookup",
|
||||
"col": "석공",
|
||||
"conds": [{"col": "높이", "from": {"box": "v", "name": "높이"}}],
|
||||
}
|
||||
block = [["높이·아래", "높이·위", "석공"], [4, 4, 0.7], [None, 3, 0.5]]
|
||||
assert [cf.dc.apply_rule(lookup, block, {"높이": h})[0] for h in (4, 3.5, 3)] == [
|
||||
0.7,
|
||||
None,
|
||||
0.5,
|
||||
]
|
||||
lg["boxes"][0]["input"]["variables"][2]["example"] = "D999" # 맞는 재료 없음
|
||||
lg["boxes"][0]["input"]["variables"][0]["example"] = "없는돌" # 표에 없는 조건 → 함수 멈춤
|
||||
bad = _post(client, "/api/m02/df/save", {"logic": lg})["values"]
|
||||
|
||||
Reference in New Issue
Block a user