Merge remote-tracking branch 'origin/main_desktop_1' into sub_laptop_2
This commit is contained in:
@@ -14,7 +14,7 @@ from typing import Any, Iterable
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance, structure_type_map
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from M02_MasterTemplete.M02_MasterTemplete_Options import hidden_keys
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Options import hidden_keys
|
||||
|
||||
STRUCTURES_FILE_NAME = "structures.json"
|
||||
_STAGE_DIR = os.path.join("B05_Profile", "route")
|
||||
|
||||
@@ -42,7 +42,7 @@ from common_util.common_util_drainage_pipes import (
|
||||
)
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
from config.config_db import get_db_pool
|
||||
from M02_MasterTemplete.M02_MasterTemplete_Options import TypesWithVisible, with_visible
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Options import TypesWithVisible, with_visible
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import { createPanelResizer } from "@ui/ui_template_resizer";
|
||||
import { readStateRaw, stateKey, writeStateRaw } from "../A00_Common/b_page_state";
|
||||
import { profilePanelMaxHeight } from "./B05_Profile_UI_Profile_Heights";
|
||||
import { MASSHAUL_MIN_HEIGHT } from "./B05_Profile_UI_Profile_MassHaul";
|
||||
import { TABLE_OVERLAY_MIN_HEIGHT } from "./B05_Profile_UI_Profile_TableOverlay";
|
||||
|
||||
@@ -19,8 +20,6 @@ const MIN_CHART_HEIGHT = 100;
|
||||
const MASSHAUL_HANDLE_GUTTER_PX = 13;
|
||||
/** 패널을 끌어 줄일 수 있는 하한(px) — 이보다 낮으면 그래프도 테이블도 못 읽는다. */
|
||||
const MIN_PANEL_HEIGHT = 180;
|
||||
/** 상한은 위 칸(3D · 횡단 카드)이 완전히 가려지지 않도록 부모 높이의 90%까지만 허용한다. */
|
||||
const MAX_PANEL_HEIGHT_RATIO = 0.9;
|
||||
|
||||
/** 접힘·높이는 화면 취향(①) — 두 보기가 **같은 키 하나**를 쓴다(계획서 24-2c). */
|
||||
const HEIGHT_KEY = stateKey("profile-height") ?? "";
|
||||
@@ -147,7 +146,8 @@ export function createProfileDrag(ctx: ProfileDragContext): ProfileDrag {
|
||||
chrome + MIN_CHART_HEIGHT + MASSHAUL_HANDLE_GUTTER_PX + massMin + tableMin,
|
||||
);
|
||||
},
|
||||
max: () => (root.parentElement?.clientHeight ?? window.innerHeight) * MAX_PANEL_HEIGHT_RATIO,
|
||||
// 상한 = 가운데 칸 높이의 90%(위 칸 3D · 횡단 카드가 다 가려지지 않게) — Heights 와 한 식
|
||||
max: () => profilePanelMaxHeight(root),
|
||||
storageKey: HEIGHT_KEY,
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ const MASSHAUL_HANDLE_GUTTER_PX = 13;
|
||||
/** 패널 자동 확대 상한 — 메인 리사이저 max와 같은 식이어야 한다(부모 높이 비율). */
|
||||
const MAX_PANEL_HEIGHT_RATIO = 0.9;
|
||||
|
||||
/** 패널 높이 상한(px) — 메인 리사이저 max 와 자동 확대가 같이 쓴다. 기준 = 한 페이지 가운데 칸
|
||||
* `.design-page__main`(바로 위 `.design-page__profile` 은 display: contents 라 높이 0). */
|
||||
export function profilePanelMaxHeight(root: HTMLElement): number {
|
||||
const basis = root.closest<HTMLElement>(".design-page__main") ?? root.parentElement;
|
||||
return (basis?.clientHeight || window.innerHeight) * MAX_PANEL_HEIGHT_RATIO;
|
||||
}
|
||||
|
||||
export interface HeightCascadeDeps {
|
||||
/** 패널 루트 — 자리가 모자라면 이 높이를 키운다. */
|
||||
root: HTMLElement;
|
||||
@@ -155,9 +162,7 @@ export function createHeightCascade(deps: HeightCascadeDeps): HeightCascade {
|
||||
// 상한은 메인 리사이저 max와 **같은 식**이어야 한다 — 예전 window 92% 상한은
|
||||
// 리사이저 상한(부모 90%)보다 높아, 자동 확대 직후 손잡이를 잡는 순간 낮은
|
||||
// 상한으로 clamp되며 패널이 뚝 떨어졌다(2026-08-06 분석).
|
||||
const growCap = Math.round(
|
||||
(deps.root.parentElement?.clientHeight ?? window.innerHeight) * MAX_PANEL_HEIGHT_RATIO,
|
||||
);
|
||||
const growCap = Math.round(profilePanelMaxHeight(deps.root));
|
||||
const grown = Math.min(deps.root.offsetHeight + deficit, growCap);
|
||||
if (grown > deps.root.offsetHeight + 1) {
|
||||
deps.root.style.setProperty("--b05-profile-height", `${grown}px`);
|
||||
|
||||
@@ -32,7 +32,8 @@ export interface TemplateDoc {
|
||||
/** 서버가 판 불일치로 막았을 때 */
|
||||
export class StaleError extends Error {}
|
||||
|
||||
async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
/** `/api/m02` 아래 길 부름 — 409 = StaleError · 그 밖 실패 = 서버 까닭 글(하위 폴더도 씀) */
|
||||
export async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
const response = await fetch(`${API_BASE_URL}/m02${path}`, {
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -192,30 +193,6 @@ export interface StructureCombos {
|
||||
export const fetchStructureCombos = (): Promise<StructureCombos> => scoped("/structure-combos");
|
||||
|
||||
/** 조합 갈라 내기 결과 — `새로만듦` false = 같은 담은 로직의 조합이 이미 있어 그 키를 가리킴 */
|
||||
/* --- 구조물 옵션 보이기(마스터 템플릿 `구조물옵션표시.json`) — 좌측 「구조물 옵션 보이기」 줄 --- */
|
||||
export interface OptionKind {
|
||||
type_id: string;
|
||||
group: string;
|
||||
name: string;
|
||||
/** 거짓이면 B05 선택지에 안 나오는 종류 */
|
||||
enabled: boolean;
|
||||
options: { key: string; label: string; phase: "b05" | "detail"; required: boolean }[];
|
||||
}
|
||||
|
||||
/** 판 · 표시(숨긴 것만 `{종류: {옵션: false}}`) · 종류(옵션이 있는 것과 그 옵션) */
|
||||
export interface OptionsDoc {
|
||||
판: string;
|
||||
표시: Record<string, Record<string, false>>;
|
||||
종류: OptionKind[];
|
||||
}
|
||||
|
||||
export const fetchStructureOptions = (): Promise<OptionsDoc> =>
|
||||
call("/layers/system/structure-options"); // 읽기 = 로그인 누구나 · 고쳐 씀은 옛 길(시스템 관리자)
|
||||
|
||||
/** 고쳐 씀 — 숨긴 것만 보냄 · 낡은 판이면 StaleError · 돌려받는 것은 새 문서 */
|
||||
export const saveStructureOptions = (판: string, 표시: OptionsDoc["표시"]): Promise<OptionsDoc> =>
|
||||
call("/structure-options", { method: "PUT", body: JSON.stringify({ 판, 표시 }) });
|
||||
|
||||
/** 항목 하나 복사해 가져오기(39-5 · 39-1b) — 받는 열(`column`)에 덮어씀 · 번호 · 열은 그대로 ·
|
||||
* `source` = 원본 열 id(실무문서면 조합 키) · `from` 없음 = 지금 층 · 층 · 주인 = 다른 층(원본 읽기 권한) ·
|
||||
* `practice` = 실무문서 조합 → 일위대가(시스템 관리자만) */
|
||||
|
||||
@@ -10,7 +10,6 @@ from typing import Any
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Options as options
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
router = APIRouter(prefix="/api/m02", tags=["M02 MasterTemplete"])
|
||||
@@ -21,11 +20,6 @@ class SaveBody(BaseModel):
|
||||
문서: Any
|
||||
|
||||
|
||||
class OptionsBody(BaseModel):
|
||||
판: str = "" # 옵션 표시 표 판 — 어긋나면 409
|
||||
표시: dict[str, Any] # {종류: {옵션 키: 참 · 거짓}} — 거짓만 남김
|
||||
|
||||
|
||||
def _call(fn, *args):
|
||||
try:
|
||||
return fn(*args)
|
||||
@@ -55,18 +49,6 @@ def post_structure(column: str) -> dict:
|
||||
return _call(store.create_structure, column)
|
||||
|
||||
|
||||
@router.get("/structure-options")
|
||||
def get_structure_options() -> dict:
|
||||
"""`{판, 표시, 종류}` — 표시 = 숨긴 것만 · 종류 = 옵션 있는 종류와 그 옵션."""
|
||||
return _call(options.listing)
|
||||
|
||||
|
||||
@router.put("/structure-options")
|
||||
def put_structure_options(body: OptionsBody) -> dict:
|
||||
"""고쳐 씀(낡은 판이면 409 · 없는 종류 · 키는 422) — 돌려주는 것 = 새 `{판, 표시, 종류}`."""
|
||||
return _call(options.save, body.표시, body.판)
|
||||
|
||||
|
||||
# 복사 가져오기 — 다른 열(`from`) 내용을 받는 열로 · 번호 · 열은 받는 쪽 그대로
|
||||
@router.post("/structures/{column}/copy")
|
||||
def post_structure_copy(column: str, source: str = Query(alias="from")) -> dict:
|
||||
|
||||
@@ -288,14 +288,6 @@ def get_numbers(layer: Layer, owner: str | None = None, session: Session = Depen
|
||||
return _call(_root(session, layer, owner), store.structure_numbers)
|
||||
|
||||
|
||||
@router.get("/layers/system/structure-options")
|
||||
def get_structure_options(session: Session = Depends(verify_session)) -> dict:
|
||||
"""구조물 옵션 보이기 읽기 — 로그인 누구나(마스터 한 벌 · 고쳐 씀은 옛 길 시스템 관리자만)."""
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Options as options
|
||||
|
||||
return _call(_root(session, "system"), options.listing)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/structure-combos")
|
||||
def get_combos(layer: Layer, owner: str | None = None, session: Session = Depends(verify_session)):
|
||||
return _call(_root(session, layer, owner), store.read_combos)
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
type TemplateInfo,
|
||||
} from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { STRUCTURE_TABLE } from "./M02_MasterTemplete_Structure";
|
||||
import { buildOptionsSection } from "./M02_MasterTemplete_Options";
|
||||
import { buildOptionsSection } from "./StructOptions/M02_SO_Options";
|
||||
import { buildPracticeSection } from "./M02_MasterTemplete_Practice";
|
||||
import { canEdit, type Selection } from "./M02_MasterTemplete_UI_Main";
|
||||
|
||||
|
||||
@@ -143,13 +143,6 @@
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
/* 구조물 옵션 보이기 — 종류 제목 */
|
||||
.m02-options__head {
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
font-size: var(--text-body);
|
||||
}
|
||||
|
||||
/* 층 화면 — 회사용 읽기만 안내 막대 · 회사 없음 안내 */
|
||||
.m02-layer-bar {
|
||||
display: flex;
|
||||
@@ -172,76 +165,6 @@
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* 좌측 「구조물 옵션 보이기」 화면(메인 칸) — 왼쪽 종류(군별) · 오른쪽 옵션 체크 */
|
||||
.m02-opts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-12);
|
||||
max-width: 64rem;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-opts__panes {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(14rem, 20rem) 1fr;
|
||||
gap: var(--spacing-16);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.m02-opts__kinds,
|
||||
.m02-opts__list {
|
||||
max-height: calc(100vh - 20rem);
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m02-opts__group {
|
||||
margin: var(--spacing-8) 0 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-opts__kind {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: none;
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-opts__kind:hover {
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.m02-opts__kind.is-active {
|
||||
border-color: var(--color-accent);
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.m02-opts__option {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--spacing-4) 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-opts__key {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.m02-main__head + .m02-tree__search {
|
||||
margin-bottom: var(--spacing-8);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/* =============================================================================
|
||||
* M02_SO_Api.ts
|
||||
* 구조물 옵션 보이기 서버 호출 — 서버 = `M02_SO_Router.py`(마스터 템플릿 `구조물옵션표시.json`)
|
||||
* ========================================================================== */
|
||||
|
||||
import { call } from "../M02_MasterTemplete_Api_Fetch";
|
||||
|
||||
export interface OptionKind {
|
||||
type_id: string;
|
||||
group: string;
|
||||
name: string;
|
||||
/** 거짓이면 B05 선택지에 안 나오는 종류 */
|
||||
enabled: boolean;
|
||||
options: { key: string; label: string; phase: "b05" | "detail"; required: boolean }[];
|
||||
}
|
||||
|
||||
/** 판 · 표시(숨긴 것만 `{종류: {옵션: false}}`) · 종류(옵션이 있는 것과 그 옵션) */
|
||||
export interface OptionsDoc {
|
||||
판: string;
|
||||
표시: Record<string, Record<string, false>>;
|
||||
종류: OptionKind[];
|
||||
}
|
||||
|
||||
export const fetchStructureOptions = (): Promise<OptionsDoc> =>
|
||||
call("/layers/system/structure-options"); // 읽기 = 로그인 누구나 · 고쳐 씀은 시스템 관리자
|
||||
|
||||
/** 고쳐 씀 — 숨긴 것만 보냄 · 낡은 판이면 StaleError · 돌려받는 것은 새 문서 */
|
||||
export const saveStructureOptions = (판: string, 표시: OptionsDoc["표시"]): Promise<OptionsDoc> =>
|
||||
call("/structure-options", { method: "PUT", body: JSON.stringify({ 판, 표시 }) });
|
||||
+6
-5
@@ -1,20 +1,21 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Options.ts
|
||||
* M02_SO_Options.ts
|
||||
* 좌측 「구조물 옵션 보이기」 줄(구조물 집계 양식 아래 별도) — 누르면 메인 칸에
|
||||
* 종류 목록(군별) → 고른 종류의 옵션 체크 목록(라벨 · 키 작은 글 · B06 전용 · 필수 표시) ·
|
||||
* [저장] · [모두 보임]. 체크 안 한 옵션은 B05 · B06 구조물 폼에서 숨음(정의 · 값은 그대로).
|
||||
* 서버 = `M02_MasterTemplete_Options.py`(마스터 템플릿 `구조물옵션표시.json` · 숨긴 것만 저장).
|
||||
* 서버 = `M02_SO_Options.py`(마스터 템플릿 `구조물옵션표시.json` · 숨긴 것만 저장).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, createTag, el, showToast } from "@ui/ui_template_elements";
|
||||
import { StaleError } from "../M02_MasterTemplete_Api_Fetch";
|
||||
import {
|
||||
fetchStructureOptions,
|
||||
saveStructureOptions,
|
||||
StaleError,
|
||||
type OptionKind,
|
||||
type OptionsDoc,
|
||||
} from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { to } from "./M02_MasterTemplete_Options_Text";
|
||||
} from "./M02_SO_Api";
|
||||
import "./M02_SO_Style.css";
|
||||
import { to } from "./M02_SO_Text";
|
||||
|
||||
export interface OptionsSectionOptions {
|
||||
isAdmin: boolean;
|
||||
@@ -0,0 +1,52 @@
|
||||
"""M02 구조물 옵션 보이기 길 — 정본 `M02_SO_Options.py`.
|
||||
|
||||
`router` = 고쳐 씀 · 관리 읽기(`main.py` 가 `system_admin_only` 로 붙임) ·
|
||||
`read_router` = 읽기만(로그인 누구나 · `main.py` 가 `protected` 로 붙임).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from common_util.common_util_auth import verify_session
|
||||
from M02_MasterTemplete import M02_Layer_Access as access
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Options as options
|
||||
|
||||
router = APIRouter(prefix="/api/m02", tags=["M02 MasterTemplete"])
|
||||
read_router = APIRouter(prefix="/api/m02", tags=["M02 MasterTemplete"])
|
||||
|
||||
|
||||
class OptionsBody(BaseModel):
|
||||
판: str = "" # 옵션 표시 표 판 — 어긋나면 409
|
||||
표시: dict[str, Any] # {종류: {옵션 키: 참 · 거짓}} — 거짓만 남김
|
||||
|
||||
|
||||
def _call(fn, *args):
|
||||
with store.at(store.FOLDER):
|
||||
try:
|
||||
return fn(*args)
|
||||
except store.StoreError as e:
|
||||
raise HTTPException(status_code=e.status, detail=e.detail) from e
|
||||
|
||||
|
||||
@router.get("/structure-options")
|
||||
def get_structure_options() -> dict:
|
||||
"""`{판, 표시, 종류}` — 표시 = 숨긴 것만 · 종류 = 옵션 있는 종류와 그 옵션."""
|
||||
return _call(options.listing)
|
||||
|
||||
|
||||
@router.put("/structure-options")
|
||||
def put_structure_options(body: OptionsBody) -> dict:
|
||||
"""고쳐 씀(낡은 판이면 409 · 없는 종류 · 키는 422) — 돌려주는 것 = 새 `{판, 표시, 종류}`."""
|
||||
return _call(options.save, body.표시, body.판)
|
||||
|
||||
|
||||
@read_router.get("/layers/system/structure-options")
|
||||
def get_structure_options_read(session=Depends(verify_session)) -> dict:
|
||||
"""구조물 옵션 보이기 읽기 — 로그인 누구나(마스터 한 벌 · 고쳐 씀은 시스템 관리자만)."""
|
||||
access.require(session, "system", None)
|
||||
return _call(options.listing)
|
||||
@@ -0,0 +1,76 @@
|
||||
/* 좌측 「구조물 옵션 보이기」 화면(메인 칸) — 왼쪽 종류(군별) · 오른쪽 옵션 체크 */
|
||||
.m02-opts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-12);
|
||||
max-width: 64rem;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
/* 종류 제목 */
|
||||
.m02-options__head {
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
font-size: var(--text-body);
|
||||
}
|
||||
|
||||
.m02-opts__panes {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(14rem, 20rem) 1fr;
|
||||
gap: var(--spacing-16);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.m02-opts__kinds,
|
||||
.m02-opts__list {
|
||||
max-height: calc(100vh - 20rem);
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m02-opts__group {
|
||||
margin: var(--spacing-8) 0 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-opts__kind {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: none;
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-opts__kind:hover {
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.m02-opts__kind.is-active {
|
||||
border-color: var(--color-accent);
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.m02-opts__option {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--spacing-4) 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-opts__key {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Options_Text.ts
|
||||
* M02_SO_Text.ts
|
||||
* 좌측 「구조물 옵션 보이기」 줄 · 상세 화면 글자 — [한국어, 영어]
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -68,6 +68,8 @@ from M02_MasterTemplete.M02_MasterTemplete_Router_Drawing import router as m02_d
|
||||
from M02_MasterTemplete.M02_MasterTemplete_Router_Items import router as m02_items_router
|
||||
from M02_MasterTemplete.M02_MasterTemplete_Router_Layers import router as m02_layers_router
|
||||
from M02_MasterTemplete.M02_Project_Items_Router import router as m02_project_items_router
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Router import read_router as m02_so_read_router
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Router import router as m02_so_router
|
||||
from common_util.common_util_audit import note_api_call, record_call_burst
|
||||
from common_util.common_util_auth import (
|
||||
require_company,
|
||||
@@ -646,6 +648,9 @@ app.include_router(
|
||||
)
|
||||
# 마스터 템플릿 — 시스템 층은 시스템 관리자만 · 층(회사·개인·프로젝트)·도면 길은 로그인만(권한은 길 안에서).
|
||||
app.include_router(m02_master_template_router, dependencies=system_admin_only)
|
||||
# 구조물 옵션 보이기 · 그룹(PLAN 46) — 고쳐 씀은 시스템 관리자 · 읽기는 로그인만
|
||||
app.include_router(m02_so_router, dependencies=system_admin_only)
|
||||
app.include_router(m02_so_read_router, dependencies=protected)
|
||||
app.include_router(m02_layers_router, dependencies=protected)
|
||||
app.include_router(m02_items_router, dependencies=protected)
|
||||
app.include_router(m02_combos_router, dependencies=protected)
|
||||
|
||||
@@ -25,6 +25,7 @@ from M02_MasterTemplete import M02_MasterTemplete_Router_Layers as layers_router
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Router as so_router
|
||||
from resources.tester.helper_m02_master_sample import copy_master_sample
|
||||
from resources.tester.test_item_copy_complete import ( # noqa: F401 — 복사본 world 고정물
|
||||
FOLDER,
|
||||
@@ -64,7 +65,7 @@ def empty(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
|
||||
gone = empty_master(system)
|
||||
session = {"user_id": 42, "company_id": 7, "role": "SYSTEM_ADMIN", "is_master": True}
|
||||
app = FastAPI()
|
||||
for module in (main_router, items_router, layers_router):
|
||||
for module in (main_router, items_router, layers_router, so_router):
|
||||
app.include_router(module.router)
|
||||
app.dependency_overrides[verify_session] = lambda: session
|
||||
return {"client": TestClient(app), "system": system, "gone": gone}
|
||||
|
||||
@@ -18,9 +18,9 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Router import router as b05_router
|
||||
from B05_Profile.B05_Profile_Structures_Schema import load_structure_types
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Options as opt
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete.M02_MasterTemplete_Router import router as m02_router
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Options as opt
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Router import router as m02_router
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
URL = "/api/m02/structure-options"
|
||||
@@ -131,12 +131,12 @@ def test_좌측_줄은_구조물_집계_양식_아래_한_줄_같은_한_곳에
|
||||
assert "matset" not in side
|
||||
paint = side.split("function paint(): void {")[1].split("\n }\n")[0]
|
||||
assert "if (opt.getOpen()) options.clear();" in paint # 양식을 열면 이 줄 고름 지움
|
||||
api = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts").read_text("utf-8")
|
||||
api = (ROOT / "M02_MasterTemplete/StructOptions/M02_SO_Api.ts").read_text("utf-8")
|
||||
assert (
|
||||
'call("/layers/system/structure-options")' in api
|
||||
and 'call("/structure-options", { method: "PUT"' in api
|
||||
)
|
||||
ui = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Options.ts").read_text("utf-8")
|
||||
ui = (ROOT / "M02_MasterTemplete/StructOptions/M02_SO_Options.ts").read_text("utf-8")
|
||||
assert 'o.phase === "detail"' in ui # B06 전용 표시
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from M02_MasterTemplete import M02_MasterTemplete_Router_Items as items_router
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Router_Layers as layers_router
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Router as so_router
|
||||
from resources.tester.test_m02_structure import write_sample_table
|
||||
|
||||
SYS = "/api/m02/layers/system"
|
||||
@@ -39,6 +40,7 @@ def world(tmp_path, monkeypatch) -> dict[str, Any]:
|
||||
app = FastAPI()
|
||||
app.include_router(items_router.router)
|
||||
app.include_router(layers_router.router)
|
||||
app.include_router(so_router.read_router)
|
||||
app.dependency_overrides[verify_session] = lambda: session
|
||||
return {"client": TestClient(app), "session": session}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user