Merge remote-tracking branch 'origin/main_desktop_1' into main_laptop_1
This commit is contained in:
@@ -21,13 +21,21 @@ from M02_MasterTemplete.M02_Template_Layers import drawing_sheets
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
LINKS = "연결표"
|
||||
DROPPED = "지운연결"
|
||||
# common_util_spreadsheet_node.ts `NAME_GONE` 과 같은 글자 — 이름이 지워졌을 때 까닭
|
||||
NAME_GONE = "이름없음"
|
||||
|
||||
|
||||
def to_table(column: str, sheet: str, rng: str, font: float | None = None) -> dict[str, Any]:
|
||||
"""그 열 면적산출(저장본) 시트 · 범위 → `{표, 시트, 범위, 값}` — 저장 안 함 · 못 바꾸면 400."""
|
||||
def to_table(
|
||||
column: str, sheet: str, rng: str, font: float | None = None, name: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""그 열 면적산출(저장본) 시트 · 범위(또는 `name` 이 있으면 그 이름이 지금 가리키는 범위) →
|
||||
`{표, 시트, 범위, 값}` — 저장 안 함 · 못 바꾸면 400(이름이 지워졌으면 「이름없음」)."""
|
||||
book = store.read("area", column)["문서"]
|
||||
try:
|
||||
got = sheet_node.area_tables([{"book": book, "시트": sheet, "범위": rng, "글자크기": font}])
|
||||
got = sheet_node.area_tables(
|
||||
[{"book": book, "시트": sheet, "범위": rng, "이름": name, "글자크기": font}]
|
||||
)
|
||||
except sheet_node.RecalcError as e:
|
||||
raise store.StoreError(503, str(e)) from e
|
||||
if not got[0].get("ok"):
|
||||
@@ -35,6 +43,37 @@ def to_table(column: str, sheet: str, rng: str, font: float | None = None) -> di
|
||||
return {k: got[0][k] for k in ("표", "시트", "범위", "값")}
|
||||
|
||||
|
||||
def define_name(column: str, sheet: str, rng: str, name: str) -> None:
|
||||
"""처음 도면으로 보낼 때(T 번호 정해진 뒤) 면적 시트에 보낸 범위 이름을 더함/갱신(104-1) —
|
||||
`applyLinked`(행열 넣기 · 지우기)가 이 이름을 그대로 따라가게 함."""
|
||||
book = store.read("area", column)["문서"]
|
||||
try:
|
||||
got = sheet_node.define_names([{"book": book, "시트": sheet, "범위": rng, "이름": name}])
|
||||
except sheet_node.RecalcError as e:
|
||||
raise store.StoreError(503, str(e)) from e
|
||||
if not got[0].get("ok"):
|
||||
raise store.StoreError(400, got[0].get("까닭"))
|
||||
path = store._path("area", column)
|
||||
with store._LOCK:
|
||||
atomic_write_json(path, got[0]["book"]) # 디스크 글 자체가 책(`store.read` 의 "문서")
|
||||
|
||||
|
||||
def unlink(column: str, 연결: str) -> None:
|
||||
"""이름표 메뉴 [보내기 취소](104-3) — 연결표에서 그 줄만 빼고 저장 · 도면 표(CAD 엔티티)는 그대로 남음
|
||||
(지우기는 도면에서 — `withLinks` 가 다음 저장 때 표 없는 기록을 거둠)."""
|
||||
path = store._path("structure", column)
|
||||
if not path.is_file():
|
||||
return
|
||||
with store._LOCK:
|
||||
doc = json.loads(path.read_bytes())
|
||||
rows = doc.get(LINKS) or []
|
||||
kept = [x for x in rows if x.get("연결") != 연결]
|
||||
if len(kept) == len(rows):
|
||||
return
|
||||
doc[LINKS] = kept
|
||||
atomic_write_json(path, doc)
|
||||
|
||||
|
||||
def _style_link(entity: Any) -> Any:
|
||||
if not isinstance(entity, dict) or entity.get("type") != "Table":
|
||||
return None
|
||||
@@ -42,7 +81,9 @@ def _style_link(entity: Any) -> Any:
|
||||
|
||||
|
||||
def refresh_drawing(column: str) -> list[str]:
|
||||
"""지금 층 그 열 도면(모든 장)의 연결표마다 칸 글 다시 · 마지막 값 갱신 → 칸을 간 표의 `연결` 목록."""
|
||||
"""지금 층 그 열 도면(모든 장)의 연결표마다 칸 글 다시 · 마지막 값 갱신 → 칸을 간 표의 `연결` 목록.
|
||||
이름(104-1)이 있으면 그 이름이 지금 가리키는 범위로 `범위` 도 갱신 — 이름이 지워졌으면(범위가
|
||||
통째로 지워짐) 그 연결을 끊고(도면 표는 남음) `지운연결` 목록에 적어 다음에 열 때 알림."""
|
||||
try:
|
||||
drawing, area = store._path("structure", column), store._path("area", column)
|
||||
except store.StoreError:
|
||||
@@ -55,7 +96,13 @@ def refresh_drawing(column: str) -> list[str]:
|
||||
return []
|
||||
book = json.loads(area.read_bytes())
|
||||
asks = [
|
||||
{"book": book, "시트": x.get("시트"), "범위": x.get("범위"), "글자크기": x.get("글자크기")}
|
||||
{
|
||||
"book": book,
|
||||
"시트": x.get("시트"),
|
||||
"범위": x.get("범위"),
|
||||
"이름": x.get("이름"),
|
||||
"글자크기": x.get("글자크기"),
|
||||
}
|
||||
for x in links
|
||||
]
|
||||
try:
|
||||
@@ -65,9 +112,17 @@ def refresh_drawing(column: str) -> list[str]:
|
||||
return []
|
||||
entities = [e for _, sheet in drawing_sheets(doc) for e in sheet.get("entities") or []]
|
||||
done: list[str] = []
|
||||
kept: list[dict[str, Any]] = []
|
||||
dropped: list[str] = []
|
||||
for link, got in zip(links, results):
|
||||
if not got.get("ok"):
|
||||
if link.get("이름") and got.get("까닭") == NAME_GONE:
|
||||
dropped.append(link["연결"]) # 이름이 통째로 지워짐 — 연결만 끊음(도면 표는 남김)
|
||||
else:
|
||||
kept.append(link) # 그 밖 실패(시트 지움 등)는 다음 저장 때 다시 시도
|
||||
continue
|
||||
kept.append(link)
|
||||
link["범위"] = got["범위"] # 이름이 지금 가리키는 범위로(104-1)
|
||||
link["값"] = got["값"]
|
||||
new = got["표"]["shapeData"]
|
||||
for entity in entities:
|
||||
@@ -81,6 +136,11 @@ def refresh_drawing(column: str) -> list[str]:
|
||||
shape["rowHeights"], shape["columnWidths"] = new["rowHeights"], new["columnWidths"]
|
||||
shape["cells"] = new["cells"]
|
||||
done.append(link["연결"])
|
||||
if dropped:
|
||||
doc[LINKS] = kept
|
||||
doc[DROPPED] = sorted(set(doc.get(DROPPED) or []) | set(dropped))
|
||||
elif len(kept) != len(links):
|
||||
doc[LINKS] = kept
|
||||
with store._LOCK:
|
||||
atomic_write_json(drawing, doc)
|
||||
return done
|
||||
|
||||
@@ -159,9 +159,13 @@ export interface StructureDoc {
|
||||
장?: string;
|
||||
시트: string;
|
||||
범위: string;
|
||||
/** 보낸 범위 기준(104-1) — 면적 시트 이름정의(`도면_T1` 꼴) · 없으면(옛 연결) `범위` 를 그대로 씀 */
|
||||
이름?: string;
|
||||
글자크기: number;
|
||||
값: Record<string, string>;
|
||||
}[];
|
||||
/** 서버가 연결을 끊은 T번호(104-1 · 보낸 범위가 통째로 지워짐) — 열 때 한 번 알리고 다음 저장에서 지움 */
|
||||
지운연결?: string[];
|
||||
}
|
||||
|
||||
/** 구조물 도면 더한 장 하나(82-2) */
|
||||
|
||||
@@ -46,7 +46,7 @@ interface TableEntityJson {
|
||||
}
|
||||
/** 연결 기록 한 줄 — 도면 문서 맨 위 `연결표[]`(값 = 마지막 칸 글 · 다름 알림 기준) */
|
||||
export type TableLink = NonNullable<StructureDoc["연결표"]>[number];
|
||||
type LinkSpec = Pick<TableLink, "시트" | "범위" | "글자크기">;
|
||||
type LinkSpec = Pick<TableLink, "시트" | "범위" | "이름" | "글자크기">;
|
||||
/** 표 변환 길 답(41-1) — 시트 = 시트 id */
|
||||
interface AreaTable {
|
||||
표: TableEntityJson;
|
||||
@@ -82,10 +82,12 @@ const TEXT = {
|
||||
tag: "{id} 도면",
|
||||
tip: "도면 표 {id} 로 보낸 범위 {range} · 글자 {size}",
|
||||
updated: "표 {n}개를 지금 면적 값으로 바꿈 — [저장]하면 남음",
|
||||
rangeGone: "보낸 범위가 지워져 연결을 끊음 — {ids}(도면 표는 남음)",
|
||||
} as const;
|
||||
|
||||
const SIZE = 2.5; // 표 글자 크기 기본(정함 41)
|
||||
const LINK = "연결"; // 표 style 안 표지 칸
|
||||
const NAME = "도면_"; // 보낸 범위 이름정의 앞머리(104-1 · `도면_T1` 꼴)
|
||||
|
||||
const fill = (text: string, key: string, value: string | number): string =>
|
||||
text.replace(`{${key}}`, String(value));
|
||||
@@ -100,7 +102,12 @@ async function fetchTable(layer: Layer, column: string, spec: LinkSpec): Promise
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ 시트: spec.시트, 범위: spec.범위, 글자크기: spec.글자크기 }),
|
||||
body: JSON.stringify({
|
||||
시트: spec.시트,
|
||||
범위: spec.범위,
|
||||
이름: spec.이름,
|
||||
글자크기: spec.글자크기,
|
||||
}),
|
||||
});
|
||||
const data = (await res.json().catch(() => ({}))) as { detail?: unknown } & Partial<AreaTable>;
|
||||
if (!res.ok || !data.표) {
|
||||
@@ -110,6 +117,25 @@ async function fetchTable(layer: Layer, column: string, spec: LinkSpec): Promise
|
||||
return data as AreaTable;
|
||||
}
|
||||
|
||||
/** 보낸 범위 이름을 면적 시트에 더함/갱신(104-1 · 처음 놓을 때 1번) — 실패해도 놓기는 이미 끝났으니 토스트만 */
|
||||
async function defineAreaName(
|
||||
layer: Layer,
|
||||
column: string,
|
||||
시트: string,
|
||||
범위: string,
|
||||
이름: string,
|
||||
): Promise<void> {
|
||||
const owner = getActiveOwner();
|
||||
const q = owner && layer !== "system" && layer !== "project" ? `?owner=${enc(owner)}` : "";
|
||||
const res = await fetch(`${API_BASE_URL}/m02/layers/${layer}/area/${enc(column)}/name${q}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ 시트, 범위, 이름 }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
|
||||
/** 장마다 `[장id, entities]` — 1장 = 빈 값(서버 `drawing_sheets` 와 같음) */
|
||||
const sheetsOf = (doc: StructureDoc): [string, unknown[]][] => [
|
||||
["", doc.도면?.entities ?? []],
|
||||
@@ -128,6 +154,22 @@ const sameValues = (a: Record<string, string> = {}, b: Record<string, string> =
|
||||
return ka.length === Object.keys(b).length && ka.every((k) => a[k] === b[k]);
|
||||
};
|
||||
|
||||
/** 이름표 메뉴 [보내기 취소](104-3) — 연결표에서 그 줄만 빼고 저장(도면 표는 남음) */
|
||||
async function unlinkTable(at: Selection, 연결: string): Promise<boolean> {
|
||||
const owner = getActiveOwner();
|
||||
const q = owner && at.layer !== "system" && at.layer !== "project" ? `?owner=${enc(owner)}` : "";
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/m02/layers/${at.layer}/structures/${enc(at.name)}/unlink${q}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ 연결 }),
|
||||
},
|
||||
);
|
||||
return res.ok;
|
||||
}
|
||||
|
||||
/** 면적 시트에 겹쳐 보일 「도면으로 보낸 범위」 — 같은 열 도면 `연결표` 에서(받기 실패 = 없음 · 조용히) */
|
||||
export async function sentRanges(at: Selection): Promise<NonNullable<LinkOptions["표시범위"]>> {
|
||||
try {
|
||||
@@ -136,8 +178,10 @@ export async function sentRanges(at: Selection): Promise<NonNullable<LinkOptions
|
||||
return rows.map((row) => ({
|
||||
시트: row.시트,
|
||||
범위: row.범위,
|
||||
범위이름: row.이름,
|
||||
이름: fill(TEXT.tag, "id", row.연결),
|
||||
설명: fill(fill(fill(TEXT.tip, "id", row.연결), "range", row.범위), "size", row.글자크기),
|
||||
취소: () => unlinkTable(at, row.연결).catch(() => false),
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
@@ -319,17 +363,23 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
|
||||
else {
|
||||
const id = nextId(doc, new Set());
|
||||
const { 표, 시트, 범위, 값 } = give.got;
|
||||
const 이름 = `${NAME}${id}`;
|
||||
const style = { ...(표.shapeData.style ?? {}), [LINK]: id };
|
||||
fresh.set(id, { 연결: id, 시트, 범위, 글자크기: give.글자크기, 값 });
|
||||
fresh.set(id, { 연결: id, 시트, 범위, 이름, 글자크기: give.글자크기, 값 });
|
||||
handle.placeTable({ ...표, shapeData: { ...표.shapeData, style } }, id);
|
||||
waiting = true; // 놓기 전 떠나도 물음(놓으면 도면 고침 · [저장] 때 풂)
|
||||
showToast(TEXT.place, "info");
|
||||
// 보낸 범위 이름을 면적 시트에 — 행열 넣기 · 지우기를 따라가게(104-1) · 실패해도 놓기는 그대로
|
||||
void defineAreaName(at.layer, at.name, 시트, 범위, 이름).catch(() => {});
|
||||
}
|
||||
}
|
||||
if (doc.지운연결?.length) showToast(fill(TEXT.rangeGone, "ids", doc.지운연결.join(" · ")), "info");
|
||||
void checkStale(handle, at, doc);
|
||||
},
|
||||
withLinks: (doc) => {
|
||||
waiting = false;
|
||||
const { 지운연결: _dropped, ...doc_ } = doc;
|
||||
doc = doc_;
|
||||
const old = new Map((doc.연결표 ?? []).map((row) => [row.연결, row]));
|
||||
const rows: TableLink[] = [];
|
||||
for (const [sheet, entities] of sheetsOf(doc)) {
|
||||
|
||||
@@ -311,10 +311,12 @@ def _copy_practice(column: str, key: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
class AreaTableBody(BaseModel):
|
||||
"""면적산출 범위 → 도면 표(41-1) — `시트` = 시트 id · 이름 · `글자크기` 기본 2.5."""
|
||||
"""면적산출 범위 → 도면 표(41-1) — `시트` = 시트 id · 이름 · `범위` · `이름`(104-1) 이 있으면
|
||||
그 이름이 지금 가리키는 범위로 품(연결표 다시 읽을 때) · `글자크기` 기본 2.5."""
|
||||
|
||||
시트: str
|
||||
범위: str
|
||||
이름: str | None = None
|
||||
글자크기: float | None = None
|
||||
|
||||
|
||||
@@ -330,7 +332,55 @@ def post_area_table(
|
||||
from M02_MasterTemplete import M02_Area_Table as area_table
|
||||
|
||||
folder = _root(session, layer, owner)
|
||||
return _call(folder, area_table.to_table, column, body.시트, body.범위, body.글자크기)
|
||||
return _call(
|
||||
folder, area_table.to_table, column, body.시트, body.범위, body.글자크기, body.이름
|
||||
)
|
||||
|
||||
|
||||
class AreaNameBody(BaseModel):
|
||||
"""보낸 범위 이름 정의/갱신(104-1 · 처음 도면으로 보낼 때 1번) — `이름` = `도면_T1` 꼴."""
|
||||
|
||||
시트: str
|
||||
범위: str
|
||||
이름: str
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/area/{column}/name")
|
||||
def post_area_name(
|
||||
layer: Layer,
|
||||
column: str,
|
||||
body: AreaNameBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""면적 시트에 보낸 범위 이름을 더함/갱신 — 쓰기 권한."""
|
||||
from M02_MasterTemplete import M02_Area_Table as area_table
|
||||
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
_call(folder, area_table.define_name, column, body.시트, body.범위, body.이름)
|
||||
return {}
|
||||
|
||||
|
||||
class UnlinkBody(BaseModel):
|
||||
"""이름표 메뉴 [보내기 취소](104-3) — `연결` = T번호."""
|
||||
|
||||
연결: str
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/structures/{column}/unlink")
|
||||
def post_structure_unlink(
|
||||
layer: Layer,
|
||||
column: str,
|
||||
body: UnlinkBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""연결표에서 그 줄만 빼고 저장(104-3) — 도면 표는 남음 · 쓰기 권한."""
|
||||
from M02_MasterTemplete import M02_Area_Table as area_table
|
||||
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
_call(folder, area_table.unlink, column, body.연결)
|
||||
return {}
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/{kind}/{column}/clear")
|
||||
|
||||
Reference in New Issue
Block a user