diff --git a/B01_Dashboard/B01_Dashboard_Api_Fetch.ts b/B01_Dashboard/B01_Dashboard_Api_Fetch.ts index 7e9a782a..46e87fa0 100644 --- a/B01_Dashboard/B01_Dashboard_Api_Fetch.ts +++ b/B01_Dashboard/B01_Dashboard_Api_Fetch.ts @@ -43,6 +43,10 @@ export interface ProjectItem { estimated_length_m?: number | null; memo?: string | null; status?: string | null; + /** 도면 표제란·표지에 실리는 값 — 프로그램이 지어낼 수 없어 사람이 넣는다 */ + client_org?: string | null; + project_number?: string | null; + work_amount?: string | null; owner_name?: string | null; workflow_stage: number; progress_percent: number; @@ -132,6 +136,9 @@ export interface UpdateProjectRequest { estimated_length_m?: number | null; memo?: string | null; status?: string | null; + client_org?: string | null; + project_number?: string | null; + work_amount?: string | null; } export interface AdminUpdateUserRequest extends UpdateUserRequest { diff --git a/B01_Dashboard/B01_Dashboard_Repository.py b/B01_Dashboard/B01_Dashboard_Repository.py index 1132aec2..a8f73bc6 100644 --- a/B01_Dashboard/B01_Dashboard_Repository.py +++ b/B01_Dashboard/B01_Dashboard_Repository.py @@ -148,7 +148,8 @@ async def list_user_projects(user_id: int) -> list[dict[str, Any]]: async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT id, company_id, name, region, road_type, project_year, - estimated_length_m, memo, status, updated_at, created_at + estimated_length_m, memo, status, updated_at, created_at, + client_org, project_number, work_amount FROM projects WHERE user_id = %s AND deleted_at IS NULL ORDER BY updated_at DESC, created_at DESC""", (user_id,), @@ -170,6 +171,7 @@ async def list_company_projects(company_id: int) -> list[dict[str, Any]]: await cursor.execute( """SELECT p.id, p.company_id, p.name, p.region, p.road_type, p.project_year, p.estimated_length_m, p.memo, p.status, p.updated_at, p.created_at, + p.client_org, p.project_number, p.work_amount, u.name AS owner_name, u.email AS owner_email FROM projects p LEFT JOIN users u ON u.id = p.user_id WHERE p.company_id = %s AND p.deleted_at IS NULL @@ -193,6 +195,7 @@ async def list_all_projects() -> list[dict[str, Any]]: await cursor.execute( """SELECT p.id, p.company_id, p.name, p.region, p.road_type, p.project_year, p.estimated_length_m, p.memo, p.status, p.updated_at, p.created_at, + p.client_org, p.project_number, p.work_amount, u.name AS owner_name, u.email AS owner_email FROM projects p LEFT JOIN users u ON u.id = p.user_id WHERE p.deleted_at IS NULL @@ -214,7 +217,8 @@ async def get_project(project_id: str) -> dict[str, Any] | None: async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT id, user_id, company_id, name, region, road_type, project_year, - estimated_length_m, memo, status + estimated_length_m, memo, status, + client_org, project_number, work_amount FROM projects WHERE id = %s AND deleted_at IS NULL""", (project_id,), ) @@ -228,7 +232,8 @@ async def update_project(project_id: str, data: dict[str, Any], actor_id: int) - await cursor.execute( """UPDATE projects SET name = %s, region = %s, road_type = %s, project_year = %s, - estimated_length_m = %s, memo = %s, status = COALESCE(%s, status) + estimated_length_m = %s, memo = %s, status = COALESCE(%s, status), + client_org = %s, project_number = %s, work_amount = %s WHERE id = %s AND deleted_at IS NULL""", ( data["name"], @@ -238,6 +243,9 @@ async def update_project(project_id: str, data: dict[str, Any], actor_id: int) - data.get("estimated_length_m"), data.get("memo"), data.get("status"), + data.get("client_org"), + data.get("project_number"), + data.get("work_amount"), project_id, ), ) diff --git a/B01_Dashboard/B01_Dashboard_Schema.py b/B01_Dashboard/B01_Dashboard_Schema.py index de9e931c..7b5ef6d9 100644 --- a/B01_Dashboard/B01_Dashboard_Schema.py +++ b/B01_Dashboard/B01_Dashboard_Schema.py @@ -46,6 +46,10 @@ class UpdateProjectRequest(BaseModel): estimated_length_m: float | None = Field(default=None, ge=0) memo: str | None = Field(default=None, max_length=5000) status: str | None = Field(default=None, max_length=50) + # 도면 표제란·표지에 실리는 값 (2026-09-02). 프로그램이 지어낼 수 없어 사람이 넣는다. + client_org: str | None = Field(default=None, max_length=255) + project_number: str | None = Field(default=None, max_length=100) + work_amount: str | None = Field(default=None, max_length=100) class AdminUpdateUserRequest(UpdateUserRequest): diff --git a/B01_Dashboard/B01_Dashboard_UI_Modals.ts b/B01_Dashboard/B01_Dashboard_UI_Modals.ts index d0f941a5..96535b2e 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Modals.ts +++ b/B01_Dashboard/B01_Dashboard_UI_Modals.ts @@ -90,6 +90,21 @@ export function openEditProjectModal(user: DashboardUser, project: ProjectItem): value: String(project.estimated_length_m ?? ""), }); const memo = createInputField({ label: "비고", value: project.memo ?? "" }); + // 도면 표제란·표지에 그대로 실리는 값 — 프로그램이 지어낼 수 없어 여기서 받는다. + const clientOrg = createInputField({ + label: "시행청 (도면 표제란)", + value: project.client_org ?? "", + }); + const projectNumber = createInputField({ + label: "연도·기번 (표지)", + value: project.project_number ?? "", + placeholder: "예: 2026년 간선임도(기번3-울진.대흥)", + }); + const workAmount = createInputField({ + label: "사업량 (표지)", + value: project.work_amount ?? "", + placeholder: "예: L=2.14km", + }); if (isUserOnly) { name.input.disabled = true; @@ -98,11 +113,24 @@ export function openEditProjectModal(user: DashboardUser, project: ProjectItem): year.input.disabled = true; length.input.disabled = true; memo.input.disabled = true; + clientOrg.input.disabled = true; + projectNumber.input.disabled = true; + workAmount.input.disabled = true; } openModal( L("B01_Dashboard_EditProject"), - [name.root, region.root, roadType.root, year.root, length.root, memo.root], + [ + name.root, + region.root, + roadType.root, + year.root, + length.root, + memo.root, + clientOrg.root, + projectNumber.root, + workAmount.root, + ], async () => { await updateProject(project.id, { name: name.input.value.trim(), @@ -112,6 +140,9 @@ export function openEditProjectModal(user: DashboardUser, project: ProjectItem): estimated_length_m: length.input.value ? Number(length.input.value) : null, memo: memo.input.value.trim() || null, status: project.status, + client_org: clientOrg.input.value.trim() || null, + project_number: projectNumber.input.value.trim() || null, + work_amount: workAmount.input.value.trim() || null, }); showToast(L("B01_Dashboard_Saved"), "success"); }, diff --git a/B07_DesignDetail/B07_DesignDetail_Router.py b/B07_DesignDetail/B07_DesignDetail_Router.py index d7a77523..edc3f355 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router.py +++ b/B07_DesignDetail/B07_DesignDetail_Router.py @@ -122,7 +122,7 @@ async def _title_block_fields(project_id: UUID) -> dict[str, str]: 배정이 없으면 프로젝트 소유자로 떨어진다 — 혼자 쓰는 계정에서도 칸이 차게. 아직 못 채우는 자리와 이유: - - 축척(A1/A3)·사업량·연도기번 — 값을 지어내지 않는다(임의 수치 금지). + - 사업량·연도기번은 사람이 넣는 값이다(B01 프로젝트 수정 화면). 비어 있으면 빈칸. - 설계일자 — "확정일"인데 도각은 **확정 전**에 그려져 저장본에 굳는다. 채울 시점 정의가 미결이라 비워 둔다. - 도면번호 — 단건 조회가 목록 순서를 모른다(목록을 다시 만들면 도면을 열 때마다 @@ -132,7 +132,8 @@ async def _title_block_fields(project_id: UUID) -> dict[str, str]: async with pool.acquire() as connection, connection.cursor() as cursor: await cursor.execute( """ - SELECT p.name, p.region, p.client_org, c.name, c.logo_path, + SELECT p.name, p.region, p.client_org, p.project_number, p.work_amount, + c.name, c.logo_path, COALESCE(designer.name, owner.name), COALESCE(designer.signature_path, owner.signature_path), pm.name, lead.name @@ -149,11 +150,25 @@ async def _title_block_fields(project_id: UUID) -> dict[str, str]: row = await cursor.fetchone() if not row: return {} - name, region, client_org, company, logo_path, designer, signature_path, pm, lead = row + ( + name, + region, + client_org, + project_number, + work_amount, + company, + logo_path, + designer, + signature_path, + pm, + lead, + ) = row fields = { "공사명": name, "위치": region, "시행청": client_org, + "연도기번": project_number, + "사업량": work_amount, "용역회사": company, "설계자": designer, "과업책임자": pm, diff --git a/db_management/012_title_block_inputs.sql b/db_management/012_title_block_inputs.sql new file mode 100644 index 00000000..832e9cd5 --- /dev/null +++ b/db_management/012_title_block_inputs.sql @@ -0,0 +1,15 @@ +-- 012_title_block_inputs.sql +-- 표지에 들어갈 사람이 넣는 값 (2026-09-02) +-- +-- 011 로 시행청·담당자 배정 자리를 냈고, 남은 표지 빈칸 둘은 **지어낼 수 없는 값**이라 +-- 사람이 넣을 칸을 만든다. 값 출처를 프로그램이 정하지 않는다. +-- 연도·기번 — 실무문서 폴더명 관행: `2024년 간선임도(기번3-울진.대흥)` +-- 사업량 — 표지 「- 사 업 량 :」 칸. 단위·표기가 사업 종류마다 달라 자유 문자열로 받는다. +-- +-- 전부 ADD COLUMN(NULL 허용)이라 기존 행·기존 동작은 그대로다. + +USE aislo_db; + +ALTER TABLE projects + ADD COLUMN IF NOT EXISTS project_number VARCHAR(100) NULL COMMENT '표지 연도·기번' AFTER client_org, + ADD COLUMN IF NOT EXISTS work_amount VARCHAR(100) NULL COMMENT '표지 사업량' AFTER project_number;