Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_4' into main_laptop_1
This commit is contained in:
@@ -24,12 +24,42 @@ export interface DrawingTemplateDoc extends CadHostDrawing {
|
||||
}
|
||||
|
||||
export interface DrawingTemplateOptions {
|
||||
/** [저장] 을 누르면 편집본을 넘긴다. 없으면 [저장] 단추를 두지 않는다. */
|
||||
/** 쓰지 않음 — 저장은 페이지 머리 [저장] 한 곳(`getDoc()` 으로 편집본을 가져감 · 판 · 409 흐름). */
|
||||
onSave?: (doc: DrawingTemplateDoc) => void | Promise<void>;
|
||||
/** 보기 전용 — CAD 그리기·수정 · 작도 영역 · 불러오기가 막힌다. */
|
||||
readOnly?: boolean;
|
||||
/** 양식 이름 — CAD 자동백업 칸을 양식마다 나눈다(B07 도면 백업과도 안 겹침). */
|
||||
name?: string;
|
||||
/** 양식 층 — 같은 이름도 층마다 백업 칸이 갈린다(`m02:층:이름`). */
|
||||
layer?: string;
|
||||
/** 프로젝트 층이면 프로젝트 id — 칸이 `m02:project:프로젝트id:이름` 이 된다. */
|
||||
projectId?: string | null;
|
||||
}
|
||||
|
||||
const RECOVERY_PREFIX = "OPEN_WEB_CAD__RECOVERY__";
|
||||
|
||||
/** 자동백업 칸 이름 — 층(과 프로젝트)을 붙여 같은 이름의 양식끼리 안 겹치게 한다. */
|
||||
function recoveryScopeOf(options: DrawingTemplateOptions): string {
|
||||
const name = options.name ?? "drawing";
|
||||
if (!options.layer) return `m02:${name}`;
|
||||
const project = options.layer === "project" ? `:${options.projectId ?? ""}` : "";
|
||||
return `m02:${options.layer}${project}:${name}`;
|
||||
}
|
||||
|
||||
/** 층 없는 옛 칸(`m02:이름`)을 새 칸으로 한 번 옮기고 지운다 — 새 칸에 이미 있으면 그대로 두고 지움. */
|
||||
function migrateOldRecovery(options: DrawingTemplateOptions, scope: string): void {
|
||||
const old = `${RECOVERY_PREFIX}m02:${options.name ?? "drawing"}`;
|
||||
if (!options.layer || old === `${RECOVERY_PREFIX}${scope}`) return;
|
||||
try {
|
||||
const value = localStorage.getItem(old);
|
||||
if (value === null) return;
|
||||
if (localStorage.getItem(`${RECOVERY_PREFIX}${scope}`) === null) {
|
||||
localStorage.setItem(`${RECOVERY_PREFIX}${scope}`, value);
|
||||
}
|
||||
localStorage.removeItem(old);
|
||||
} catch {
|
||||
// 저장소를 못 써도 편집은 됨
|
||||
}
|
||||
}
|
||||
|
||||
export interface DrawingTemplateHandle {
|
||||
@@ -79,7 +109,8 @@ export function mountDrawingTemplate(
|
||||
options: DrawingTemplateOptions = {},
|
||||
): DrawingTemplateHandle {
|
||||
const readOnly = options.readOnly === true;
|
||||
const recoveryScope = `m02:${options.name ?? "drawing"}`;
|
||||
const recoveryScope = recoveryScopeOf(options);
|
||||
migrateOldRecovery(options, recoveryScope);
|
||||
let base: DrawingTemplateDoc = doc;
|
||||
|
||||
const root = document.createElement("div");
|
||||
@@ -139,7 +170,13 @@ export function mountDrawingTemplate(
|
||||
const cad = createCadHost<DrawingTemplateDoc>({ title: "도면 양식" });
|
||||
const load = (drawing: DrawingTemplateDoc): void => {
|
||||
cad.beginLoading();
|
||||
cad.load(drawing, null, !readOnly, {}, { recoveryScope, readOnly });
|
||||
cad.load(
|
||||
drawing,
|
||||
null,
|
||||
!readOnly,
|
||||
{},
|
||||
{ recoveryScope, readOnly, hostTitle: "도면 양식 편집" },
|
||||
);
|
||||
};
|
||||
|
||||
const actions = document.createElement("div");
|
||||
@@ -184,27 +221,6 @@ export function mountDrawingTemplate(
|
||||
return base;
|
||||
};
|
||||
|
||||
if (options.onSave && !readOnly) {
|
||||
const onSave = options.onSave;
|
||||
const saveButton = createButton({
|
||||
label: "저장",
|
||||
variant: "filled",
|
||||
onClick: () => {
|
||||
saveButton.disabled = true;
|
||||
getDoc()
|
||||
.then((next) => onSave(next))
|
||||
.catch((error) =>
|
||||
showToast(
|
||||
error instanceof Error ? error.message : "양식을 저장하지 못했습니다.",
|
||||
"error",
|
||||
),
|
||||
)
|
||||
.finally(() => (saveButton.disabled = false));
|
||||
},
|
||||
});
|
||||
actions.append(saveButton);
|
||||
}
|
||||
|
||||
toolbar.append(area, fields, actions);
|
||||
root.append(toolbar, cad.element);
|
||||
container.append(root);
|
||||
|
||||
@@ -116,6 +116,8 @@ export function createMain(isAdmin: boolean): MainHandle {
|
||||
onSave: () => void doSave(),
|
||||
readOnly,
|
||||
name: at.name,
|
||||
layer: at.layer,
|
||||
projectId: at.projectId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
B06 횡단 설계 `design.pipe_length_m` 관 연장(부르는 쪽이 DB 에서 읽어 넘김)
|
||||
|
||||
- 펼침 열 — 마스터 한 칸 → 설계에 쓰인 값마다 열(choices 차례) · 쓰인 값이 없으면 열을 뺌.
|
||||
열 id = `마스터 id|펼친 값|…`(머리 글자가 바뀌어도 id 는 그대로).
|
||||
열 id = `마스터 id|펼친 값|…`(머리 글자가 바뀌어도 id 는 그대로 · 빈 값은 `-`).
|
||||
한 묶음 열은 값마다 한데 모음 · 계산 열은 `펼침틀` 로 묶음을 따라 나뉨(바인딩 없음).
|
||||
- 줄 — 측점마다 한 줄(같은 측점 구조물은 한 줄) · 점 `NO.x+y` · 구간 `NO.a~NO.b` ·
|
||||
같은 측점에 관이 둘이면 줄을 나눔 · 줄 id 는 측점에서 지어 손 값이 다시 채워도 붙어 있음.
|
||||
- 손 열 값 · 전구간 줄 · 사용자가 더한 줄(`손`)은 작업본 것을 그대로 지킴.
|
||||
@@ -35,7 +36,10 @@ FACILITY_TYPES = {
|
||||
"ford_bridge": "ford_bridge",
|
||||
"revetment": "revetment",
|
||||
}
|
||||
EMPTY_LABEL = "-"
|
||||
EMPTY_LABEL = "-" # 열 id 안 빈 값(저장본과 맞춤) — 머리 글은 `빈값`
|
||||
EMPTY_TEXT = "미입력"
|
||||
#: 계산 열이 설계값 열 묶음을 따라 나뉠 때 쓰는 틀(바인딩 아님 — 표 부품이 설계값으로 안 봄).
|
||||
FRAME_KEY = "펼침틀"
|
||||
ORPHAN_NOTE = "설계에서 측점 없어짐"
|
||||
#: 채운 표에 실어 보내는 펼침 전 열 — 저장 때 빠진 규칙 열을 되살리는 데만 씀.
|
||||
RULE_COLUMNS_KEY = "양식열"
|
||||
@@ -271,12 +275,31 @@ def _blank_vars(formula: str, variables: dict[str, Any]) -> list[str]:
|
||||
return [name for name in names if variables.get(name) in (None, "")]
|
||||
|
||||
|
||||
def _frame(column: dict[str, Any]) -> dict[str, Any]:
|
||||
"""펼침 틀 — 설계값 열은 `바인딩` · 따라 나뉘는 계산 열은 `펼침틀`(설계값 딱지 없음)."""
|
||||
return column.get("바인딩") or column.get(FRAME_KEY) or {}
|
||||
|
||||
|
||||
def _group(column: dict[str, Any]) -> str:
|
||||
return _frame(column).get("묶음") or column["id"]
|
||||
|
||||
|
||||
def _head_part(frame: dict[str, Any], index: int, value: str) -> str:
|
||||
"""펼친 값 한 자리 머리 글 — `값꼴`(`H={}`) · 빈 값은 `빈값`(`높이 미입력`)."""
|
||||
if value == EMPTY_LABEL:
|
||||
empties = frame.get("빈값") or []
|
||||
return (empties[index] if index < len(empties) else "") or EMPTY_TEXT
|
||||
shapes = frame.get("값꼴") or []
|
||||
shape = (shapes[index] if index < len(shapes) else "") or "{}"
|
||||
return shape.replace("{}", str((frame.get("이름") or {}).get(value, value)))
|
||||
|
||||
|
||||
def _unexpand(columns: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""채운 표가 작업본으로 저장됐으면 펼친 열을 마스터 칸(`원열`)으로 되돌림 — 두 번 안 펼침."""
|
||||
out: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
for column in columns:
|
||||
origin = (column.get("바인딩") or {}).get("원열")
|
||||
origin = _frame(column).get("원열")
|
||||
if origin is None:
|
||||
out.append(column)
|
||||
elif origin["id"] not in seen:
|
||||
@@ -291,62 +314,82 @@ def _expand_columns(
|
||||
variables: dict[str, Any],
|
||||
choices: dict[str, list[str]],
|
||||
) -> tuple[list[dict[str, Any]], dict[str, list[tuple[str, str, dict[str, Any]]]]]:
|
||||
"""펼침 열 → 값마다 열. 돌려주는 둘째 = `{master id: [(열 id, suffix, 펼친 열)]}`."""
|
||||
"""펼침 열 → 값마다 열 · 묶음은 값마다 한데(관 Φ800 관매설 · 커플링 → Φ1000 …).
|
||||
|
||||
돌려주는 둘째 = `{master id: [(열 id, suffix, 펼친 열)]}`.
|
||||
"""
|
||||
columns = _unexpand(columns)
|
||||
groups: dict[str, set[tuple[str, ...]]] = {}
|
||||
orders_of: dict[str, list[list[str]]] = {}
|
||||
members: dict[str, list[dict[str, Any]]] = {}
|
||||
for column in columns:
|
||||
binding = column.get("바인딩") or {}
|
||||
if not column.get("펼침") or not binding:
|
||||
if not column.get("펼침") or not _frame(column):
|
||||
continue
|
||||
group = binding.get("묶음") or column["id"]
|
||||
orders_of.setdefault(group, _position_orders(binding, choices))
|
||||
found = groups.setdefault(group, set())
|
||||
members.setdefault(_group(column), []).append(column)
|
||||
binding = column.get("바인딩")
|
||||
if not binding:
|
||||
continue # 계산 열 — 설계값 열이 연 값을 따름
|
||||
orders_of.setdefault(_group(column), _position_orders(binding, choices))
|
||||
found = groups.setdefault(_group(column), set())
|
||||
for source in _sources(binding):
|
||||
# 값이 빈 구조물도 열은 세움 — 놓였는데 수량이 빈칸인 것이 보여야 함
|
||||
found.update(_expansion(source, item) for item in items if _matches(source, item))
|
||||
|
||||
members: dict[str, set[str]] = {}
|
||||
for column in columns:
|
||||
binding = column.get("바인딩") or {}
|
||||
if column.get("펼침") and binding:
|
||||
members.setdefault(binding.get("묶음") or column["id"], set()).add(column["id"])
|
||||
|
||||
out: list[dict[str, Any]] = []
|
||||
made: dict[str, list[tuple[str, str, dict[str, Any]]]] = {}
|
||||
for column in columns:
|
||||
binding = column.get("바인딩") or {}
|
||||
if not column.get("펼침") or not binding:
|
||||
if not column.get("펼침") or not _frame(column):
|
||||
out.append(copy.deepcopy(column))
|
||||
continue
|
||||
group = binding.get("묶음") or column["id"]
|
||||
ordered = sorted(groups.get(group, set()), key=lambda v: _order_key(v, orders_of[group]))
|
||||
names = binding.get("이름") or {}
|
||||
for values in ordered:
|
||||
shown = [names.get(value, value) for value in values]
|
||||
suffix = "|".join(values)
|
||||
new = copy.deepcopy(column)
|
||||
new["id"] = f"{column['id']}|{suffix}"
|
||||
new["펼침"] = False
|
||||
new["머리"] = [
|
||||
None if part is None else part.format(*shown)
|
||||
for part in (binding.get("머리틀") or column["머리"])
|
||||
]
|
||||
new["바인딩"] = {**binding, "펼친값": list(values), "원열": copy.deepcopy(column)}
|
||||
formula = binding.get("식틀")
|
||||
formula = formula.format(*values) if formula else column.get("식")
|
||||
if formula:
|
||||
formula = _rewrite(formula, members.get(group, set()), suffix)
|
||||
if _blank_vars(formula, variables):
|
||||
new.pop("식", None)
|
||||
new["설명"] = f"{column.get('설명', '')} · 변수가 빈칸이라 계산 안 함".strip()
|
||||
else:
|
||||
new["식"] = formula
|
||||
out.append(new)
|
||||
made.setdefault(column["id"], []).append((new["id"], suffix, new))
|
||||
group = _group(column)
|
||||
if members[group][0] is not column:
|
||||
continue # 묶음 첫 열 자리에서 값마다 묶음 열을 한데 냄
|
||||
orders = orders_of.get(group, [])
|
||||
sibling_ids = {member["id"] for member in members[group]}
|
||||
for values in sorted(groups.get(group, set()), key=lambda v: _order_key(v, orders)):
|
||||
for member in members[group]:
|
||||
new = _expand_one(member, values, sibling_ids, variables)
|
||||
out.append(new)
|
||||
made.setdefault(member["id"], []).append((new["id"], "|".join(values), new))
|
||||
return out, made
|
||||
|
||||
|
||||
def _expand_one(
|
||||
column: dict[str, Any],
|
||||
values: tuple[str, ...],
|
||||
siblings: set[str],
|
||||
variables: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
frame = _frame(column)
|
||||
suffix = "|".join(values)
|
||||
shown = [_head_part(frame, index, value) for index, value in enumerate(values)]
|
||||
new = copy.deepcopy(column)
|
||||
new["id"] = f"{column['id']}|{suffix}"
|
||||
new["펼침"] = False
|
||||
new["머리"] = [
|
||||
None if part is None else part.format(*shown)
|
||||
for part in (frame.get("머리틀") or column["머리"])
|
||||
]
|
||||
key = "바인딩" if column.get("바인딩") else FRAME_KEY
|
||||
new[key] = {**frame, "펼친값": list(values), "원열": copy.deepcopy(column)}
|
||||
template = frame.get("식틀")
|
||||
used = [int(i) for i in re.findall(r"\{(\d+)\}", template or "")]
|
||||
blank = ""
|
||||
if any(values[i] == EMPTY_LABEL for i in used if i < len(values)):
|
||||
formula, blank = None, "설계값이"
|
||||
else:
|
||||
formula = template.format(*values) if template else column.get("식")
|
||||
if formula:
|
||||
formula = _rewrite(formula, siblings, suffix)
|
||||
blank = "변수가" if _blank_vars(formula, variables) else ""
|
||||
if blank:
|
||||
new.pop("식", None)
|
||||
new["설명"] = f"{column.get('설명', '')} · {blank} 빈칸이라 계산 안 함".strip()
|
||||
elif formula:
|
||||
new["식"] = formula
|
||||
return new
|
||||
|
||||
|
||||
def fill_document(
|
||||
document: dict[str, Any],
|
||||
items: list[dict[str, Any]],
|
||||
@@ -400,8 +443,8 @@ def fill_document(
|
||||
spacing = _number(item["options"].get("joint_spacing_m"))
|
||||
if item["type_id"] != "pavement_concrete" or spacing in (None, base_spacing):
|
||||
continue
|
||||
for new_id, _, new in made.get("pv_jt", []):
|
||||
if list(_expansion({"펼침": new["바인딩"]["펼침"]}, item)) == new["바인딩"]["펼친값"]:
|
||||
for new_id, suffix, new in made.get("pv_jt", []):
|
||||
if "|".join(_expansion({"펼침": ["thickness_cm"]}, item)) == suffix:
|
||||
text = new.get("식", "").replace("[$수축줄눈_간격_m]", _label("", spacing))
|
||||
slots[slot].setdefault("식", {})[new_id] = text
|
||||
|
||||
@@ -452,11 +495,11 @@ def fill_document(
|
||||
|
||||
|
||||
def _hand_ids(columns: list[dict[str, Any]]) -> set[str]:
|
||||
"""사람이 적는 열 — 바인딩 없는 열(손 열 · 비고 · 사용자가 더한 열) · 차례 · 측점 빼고."""
|
||||
"""사람이 적는 열 — 설계값 · 계산 열 말고(손 열 · 비고 · 더한 열) · 차례 · 측점 빼고."""
|
||||
return {
|
||||
column["id"]
|
||||
for column in columns
|
||||
if not column.get("바인딩") and column["id"] not in ("no", "sta")
|
||||
if not _frame(column) and not column.get("식") and column["id"] not in ("no", "sta")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -209,6 +209,15 @@ def _write_manifest(layer_dir: str | Path, entries: dict[str, Any]) -> None:
|
||||
atomic_write_json(Path(layer_dir) / MANIFEST_NAME, {"양식": entries})
|
||||
|
||||
|
||||
def note_source(
|
||||
layer_dir: str | Path, kind: str, name: str, source_layer: str, version: str
|
||||
) -> None:
|
||||
"""manifest 한 줄 — 복사가 아니라 고쳐 쓴 양식의 출처 · 판."""
|
||||
manifest = read_manifest(layer_dir)
|
||||
manifest[f"{kind}/{name}"] = _stamp(source_layer, name, version)
|
||||
_write_manifest(layer_dir, manifest)
|
||||
|
||||
|
||||
def _stamp(layer: str, name: str, version: str | None, **extra: Any) -> dict[str, Any]:
|
||||
entry = {
|
||||
"층": layer,
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
프로젝트마다 `seed_project(only_missing=True)` — 작업본 · `_initial/` 에 **없는 것만 더함**.
|
||||
이미 있는 양식 · 다른 파일은 안 건드림 · 폴더가 없는 프로젝트는 건너뜀(만들지 않음).
|
||||
다시 돌려도 됨 — 시스템 양식이 늘었으면 그 몫만 더해짐.
|
||||
`--refresh-tables` — 옛 표 틀(계산 열에 바인딩)만 새 판으로 · 손 값은 지킴.
|
||||
|
||||
./venv/Scripts/python.exe M02_MasterTemplete/M02_Template_Migrate.py [--dry-run] [--report 파일]
|
||||
./venv/Scripts/python.exe M02_MasterTemplete/M02_Template_Migrate.py \\
|
||||
[--dry-run] [--refresh-tables] [--report 파일]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
@@ -21,6 +24,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from config import config_system # noqa: E402
|
||||
from config.config_db import close_db_pool, get_db_pool, init_db_pool # noqa: E402
|
||||
from M02_MasterTemplete import M02_Table_Fill as fill # noqa: E402
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers # noqa: E402
|
||||
|
||||
|
||||
@@ -51,13 +55,62 @@ def project_root(storage_path: str | None) -> Path | None:
|
||||
return path
|
||||
|
||||
|
||||
def migrate(projects: list[dict[str, Any]], *, dry_run: bool = False) -> list[dict[str, Any]]:
|
||||
"""프로젝트마다 넣은 것 `[{…프로젝트, 작업본, 초기, 상태}]`."""
|
||||
def _stale_frame(document: Any) -> bool:
|
||||
"""옛 표 틀 — 계산 열에 바인딩(설계값 딱지)이 붙은 판."""
|
||||
return isinstance(document, dict) and any(
|
||||
isinstance(column, dict)
|
||||
and column.get("바인딩")
|
||||
and (column.get("식") or column["바인딩"].get("값") == "식")
|
||||
for column in document.get("열") or []
|
||||
)
|
||||
|
||||
|
||||
def refresh_tables(root: Path, *, dry_run: bool = False) -> list[str]:
|
||||
"""옛 표 틀을 시스템 새 판으로 — 작업본 · `_initial/` 둘 다 · 줄(손 값) · 변수 · 보기는 지킴.
|
||||
|
||||
손댄 것이 없으면 시스템 파일을 그대로 복사(판이 시스템과 같음).
|
||||
"""
|
||||
done: list[str] = []
|
||||
for row in layers.list_templates(layers.system_dir()):
|
||||
if row["종류"] != "table":
|
||||
continue
|
||||
system = layers.read_template(layers.system_dir(), "table", row["이름"])
|
||||
for folder in (layers.project_dir(root), layers.initial_dir(root)):
|
||||
got = layers.read_template(folder, "table", row["이름"])
|
||||
if not system or not got or not _stale_frame(got["문서"]):
|
||||
continue
|
||||
done.append(f"{folder.name}/table/{row['이름']}")
|
||||
if dry_run:
|
||||
continue
|
||||
old = fill.strip_design(got["문서"])
|
||||
doc = copy.deepcopy(system["문서"])
|
||||
doc["변수"] = {**(doc.get("변수") or {}), **(old.get("변수") or {})}
|
||||
doc.update({key: old[key] for key in ("줄", "보기") if key in old})
|
||||
if doc == system["문서"]:
|
||||
layers.copy_templates(
|
||||
layers.system_dir(),
|
||||
folder,
|
||||
source_layer="system",
|
||||
kind="table",
|
||||
name=row["이름"],
|
||||
)
|
||||
else:
|
||||
layers.write_template(folder, "table", row["이름"], doc)
|
||||
layers.note_source(folder, "table", row["이름"], "system", system["판"])
|
||||
return done
|
||||
|
||||
|
||||
def migrate(
|
||||
projects: list[dict[str, Any]], *, dry_run: bool = False, refresh: bool = False
|
||||
) -> list[dict[str, Any]]:
|
||||
"""프로젝트마다 넣은 것 `[{…프로젝트, 작업본, 초기, 표갱신, 상태}]`."""
|
||||
system = {f"{row['종류']}/{row['이름']}" for row in layers.list_templates(layers.system_dir())}
|
||||
report = []
|
||||
for project in projects:
|
||||
root = project_root(project.get("storage_path"))
|
||||
entry = {**project, "작업본": [], "초기": [], "상태": ""}
|
||||
entry = {**project, "작업본": [], "초기": [], "표갱신": [], "상태": ""}
|
||||
if root is not None and refresh:
|
||||
entry["표갱신"] = refresh_tables(root, dry_run=dry_run)
|
||||
if root is None:
|
||||
entry["상태"] = "폴더 없음 — 건너뜀"
|
||||
elif dry_run:
|
||||
@@ -85,14 +138,14 @@ def render(report: list[dict[str, Any]], system: list[dict[str, Any]]) -> str:
|
||||
"- 방법: 작업본 · `_initial/` 에 없는 것만 더함 · 있는 양식 · 다른 파일은 그대로",
|
||||
f"- 프로젝트 {len(report)}개 · 넣음 {sum(r['상태'] == '넣음' for r in report)}개",
|
||||
"",
|
||||
"| 회사 | 만든 사람 | 프로젝트 | ID | 넣은 양식 | 상태 |",
|
||||
"| --- | --- | --- | --- | --- | --- |",
|
||||
"| 회사 | 만든 사람 | 프로젝트 | ID | 넣은 양식 | 표 틀 새 판 | 상태 |",
|
||||
"| --- | --- | --- | --- | --- | --- | --- |",
|
||||
]
|
||||
for row in report:
|
||||
added = " · ".join(row["작업본"]) or "—"
|
||||
lines.append(
|
||||
f"| {row['company_id']} | {row.get('owner') or row['user_id']} | {row['name']} "
|
||||
f"| `{row['id']}` | {added} | {row['상태']} |"
|
||||
f"| `{row['id']}` | {added} | {' · '.join(row['표갱신']) or '—'} | {row['상태']} |"
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
@@ -100,6 +153,7 @@ def render(report: list[dict[str, Any]], system: list[dict[str, Any]]) -> str:
|
||||
async def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--refresh-tables", action="store_true")
|
||||
parser.add_argument("--report", type=Path)
|
||||
args = parser.parse_args()
|
||||
await init_db_pool()
|
||||
@@ -107,7 +161,7 @@ async def main() -> None:
|
||||
projects = await _projects()
|
||||
finally:
|
||||
await close_db_pool()
|
||||
report = migrate(projects, dry_run=args.dry_run)
|
||||
report = migrate(projects, dry_run=args.dry_run, refresh=args.refresh_tables)
|
||||
text = render(report, layers.list_templates(layers.system_dir()))
|
||||
if args.report:
|
||||
args.report.write_text(text, encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user