feat: update seat map dxf workflow and organization ui

This commit is contained in:
hyunho
2026-03-25 18:00:09 +09:00
parent 8f073e1458
commit e62a6a5458
27 changed files with 2517660 additions and 125 deletions

View File

@@ -121,8 +121,10 @@ LEGACY_HEADER_MAP = {
"근무시간": "work_time",
"work_time": "work_time",
"전화번호": "phone",
"ph": "phone",
"phone": "phone",
"이메일": "email",
"mail": "email",
"email": "email",
"자리위치": "seat_label",
"seat_label": "seat_label",
@@ -131,6 +133,20 @@ LEGACY_HEADER_MAP = {
}
def normalize_phone(value: object) -> str:
raw = str(value or "").strip()
digits = "".join(ch for ch in raw if ch.isdigit())
if not digits:
return ""
if len(digits) == 10 and not digits.startswith("0"):
digits = f"0{digits}"
if len(digits) == 11 and digits.startswith("0"):
return f"{digits[:3]}-{digits[3:7]}-{digits[7:]}"
if len(digits) == 10 and digits.startswith("0"):
return f"{digits[:3]}-{digits[3:6]}-{digits[6:]}"
return raw
def serialize_member_payload(item: MemberPayload, sort_order: int) -> tuple[object, ...]:
return (
item.name.strip(),
@@ -145,7 +161,7 @@ def serialize_member_payload(item: MemberPayload, sort_order: int) -> tuple[obje
item.cell.strip(),
item.work_status.strip(),
item.work_time.strip(),
item.phone.strip(),
normalize_phone(item.phone),
item.email.strip(),
item.seat_label.strip(),
item.photo_url.strip(),
@@ -385,6 +401,33 @@ def compute_focus_bounds(slot_points: list[tuple[float, float]]) -> tuple[float,
return (min_x - pad_x, min_y - pad_y, max_x + pad_x, max_y + pad_y)
def get_entity_max_span(entity: ezdxf.entities.DXFGraphic) -> float:
bounds = get_entity_bounds(entity)
if bounds is None:
return 0.0
min_x, min_y, max_x, max_y = bounds
return max(max_x - min_x, max_y - min_y)
def compute_outline_bounds(entities: list[ezdxf.entities.DXFGraphic]) -> tuple[float, float, float, float] | None:
outline_layers = {"0", "0-COL", "WID", "XH", "CO-DOOR", "CO-DO-FR", "", "회의실"}
outline_points: list[tuple[float, float]] = []
for entity in entities:
if is_chair_layer(entity.dxf.layer):
continue
if entity.dxf.layer not in outline_layers:
continue
if get_entity_max_span(entity) < 3000:
continue
outline_points.extend(get_entity_points(entity))
if not outline_points:
return None
min_x, min_y, width, height = compute_bounds_from_points(outline_points)
pad_x = max(width * 0.025, 300.0)
pad_y = max(height * 0.025, 300.0)
return (min_x - pad_x, min_y - pad_y, min_x + width + pad_x, min_y + height + pad_y)
def bounds_intersect(bounds: tuple[float, float, float, float], focus_bounds: tuple[float, float, float, float]) -> bool:
min_x, min_y, max_x, max_y = bounds
focus_min_x, focus_min_y, focus_max_x, focus_max_y = focus_bounds
@@ -537,7 +580,7 @@ def parse_dxf_layout(file_path: Path) -> tuple[dict[str, object], list[dict[str,
raise HTTPException(status_code=400, detail="chair 레이어에서 좌석 위치를 추출하지 못했습니다.")
slot_points = [(float(slot["x"]), float(slot["y"])) for slot in slots]
focus_bounds = compute_focus_bounds(slot_points)
focus_bounds = compute_outline_bounds(all_entities) or compute_focus_bounds(slot_points)
visible_entities: list[ezdxf.entities.DXFGraphic] = []
visible_points: list[tuple[float, float]] = []
for entity in all_entities:
@@ -765,7 +808,10 @@ def rows_to_member_payloads(rows: list[list[object]]) -> list[MemberPayload]:
mapped = LEGACY_HEADER_MAP.get(header)
if not mapped:
continue
record[mapped] = str(row[col_idx] if col_idx < len(row) and row[col_idx] is not None else "").strip()
value = str(row[col_idx] if col_idx < len(row) and row[col_idx] is not None else "").strip()
if mapped == "phone":
value = normalize_phone(value)
record[mapped] = value
if not str(record.get("name", "")).strip():
continue
payloads.append(MemberPayload(**record))