Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8

This commit is contained in:
2026-10-31 16:53:45 +09:00
5 changed files with 70 additions and 22 deletions
+22 -4
View File
@@ -113,6 +113,23 @@ def check_cols(cols: Any) -> dict[str, int]:
return out
def fit(cols: dict[str, int], rows: list[list[str]]) -> dict[str, int]:
"""기억한 열 짝이 다른 꼴에 붙을 때 — 칸 전체를 -1 · 0 · +1 밀어 보고 품명 글 · 수량 수가 가장 많이
맞는 쪽(엑셀꼴 「호표」 칸 있음 · STmate 꼴 없음 · 같으면 그대로)."""
def score(d: int) -> int:
got = 0
for r in rows:
name, qty = _cell(r, cols["품명"] + d), _cell(r, cols["수량"] + d)
got += bool(name and hx._num(name) is None) + (hx._num(qty) is not None)
return got
best = max((0, 1, -1), key=lambda d: (score(d), d == 0))
if best == 0 or min(cols.values()) + best < 0:
return cols
return {k: v + best for k, v in cols.items()}
def _money_cols(cols: dict[str, int]) -> dict[str, int]:
out = {"노무비": cols.get("노"), "재료비": cols.get("재"), "경비": cols.get("경")}
if cols.get("금액") is not None:
@@ -148,7 +165,7 @@ def read_text(text: str, cols: Any = None) -> dict[str, Any]:
below = [_flat(v) for v in rows[start]] if start < len(rows) else []
start += "단가" in below or "금액" in below
elif cols is not None:
used = check_cols(cols)
used = fit(check_cols(cols), rows)
else:
used, asked = guess(rows), True
money = _money_cols(used)
@@ -206,9 +223,10 @@ def read_text(text: str, cols: Any = None) -> dict[str, Any]:
qty = hx._num(_cell(row, used["수량"]))
if not unit and not any(v for v in (*price.values(), *amount.values())):
continue # 근거 줄 「건설표준품셈 | 6-7-2 | 0」
if not hopyos: # 첫 줄 수량 없음 = 호표 머리(「제N호표」 칸 없이 복사)
open_hopyo(0, row if qty is None else None)
if qty is None:
if not hopyos: # 첫 줄 수량 없음 + 금액 있음 = 엑셀꼴 호표 머리(「제N호표」 칸 없이 복사)
head_row = qty is None and any(amount.values())
open_hopyo(0, row if head_row else None)
if head_row:
continue
remark = _cell(row, note)
ref = hx._REF.match(_flat(remark))
@@ -97,7 +97,8 @@ export async function openRelink(src: RelinkSource): Promise<void> {
const load = async (): Promise<void> => {
rows = await src.rows();
pick = rows.map((r) => (r.후보.length && !linked(r) ? 0 : r.후보.length));
on = rows.map((r) => !!r.후보.length && !linked(r));
// 저절로 고름 = 후보 하나 · 붙인 단가 있음(별도계상 0 원 · 후보 여럿은 사람이 고름)
on = rows.map((r) => r.후보.length === 1 && !linked(r) && r.붙인[COST[r.후보[0].비목]] > 0);
};
const choice = (i: number): RelinkRow["후보"][number] | "keep" | "free" => {
const r = rows[i];
@@ -146,19 +147,19 @@ export async function openRelink(src: RelinkSource): Promise<void> {
});
const table = createDataTable({
columns: [
{ key: "name", label: "품명 · 규격", width: 220 },
{ key: "unit", label: "단위", width: 48 },
{ key: "qty", label: "수량", kind: "number", decimals: 4, trim: true, width: 70 },
{ key: "pasted", label: "붙인 단가", kind: "number", decimals: 1, width: 90 },
{ key: "now", label: src.priceLabel, width: 100 },
{ key: "cand", label: "이음 후보 (누름 = 다음)", width: 260 },
{ key: "price", label: "후보 단가", kind: "number", decimals: 1, width: 90 },
{ key: "gap", label: "금액 차이", kind: "number", decimals: 1, width: 90 },
{ key: "pick", label: "고름", width: 48 },
{ key: "name", label: "품명 · 규격", width: 190 },
{ key: "pick", label: "고름", width: 44 },
{ key: "unit", label: "단위", width: 44 },
{ key: "qty", label: "수량", kind: "number", decimals: 4, trim: true, width: 60 },
{ key: "pasted", label: "붙인 단가", kind: "number", decimals: 1, width: 84 },
{ key: "now", label: src.priceLabel, width: 84 },
{ key: "cand", label: "이음 후보 (누름 = 다음)", width: 210 },
{ key: "price", label: "후보 단가", kind: "number", decimals: 1, width: 84 },
{ key: "gap", label: "금액 차이", kind: "number", decimals: 1, width: 84 },
],
rows: data,
frozen: 1,
empty: "고정 줄 없음",
empty: "",
onCellClick: (i, key) => {
const r = rows[i];
if (key === "cand") {
@@ -83,7 +83,7 @@ export async function toItem(o: ToItemOptions): Promise<void> {
close();
document.dispatchEvent(new Event(ITEM_BUILT));
showToast(
`구조물 조합 「${got.이름}」 만듦 — 「실무자료」 폴더 · 줄 ${got.줄수}` +
`구조물 조합 「${label}」 만듦 — 「실무자료」 폴더 · 줄 ${got.줄수}` +
(got.열수 > 1 ? ` · 규격 열 ${got.열수}` : ""),
"success",
6000,