Files
Aislo/B01_Dashboard/B01_Dashboard_Repository_Members.py
T
eomsangdonandClaude Opus 5 3a36af9a2c feat(B01): 서명은 사람 계정에, 회사 로고는 회사 등록 단계로 재배치
사용자 확정(2026-09-02) — 이름이 들어가는 자리는 서명도 받고, 담당자 선택에 신규 등록
항목을 두며, 회사 로고는 회사 등록 단계에서 받아 이후 변경.

- 014_company_logo.sql: companies.logo_asset_id 추가 (서명은 company_assets.user_id 로
  이미 표현되어 컬럼 없음). 공유 DB 적용 완료
- 사용자 수정 모달에 「서명 (도면 표제란)」 칸 — createAssetField 에 owner(주인 못박기)·
  onChange(즉시 반영) 추가로 재사용, 주인 고정 시 물리기 체크 잠금
- 담당자 select 3개에 「+ 신규 등록…」 — 계정 생성 모달 뒤 세 select 에 항목 삽입·자동 선택
- POST /admin/members 가 이름·직위·부서 수신, 계정 없으면 status=PENDING 으로 생성
  (로그인 불가 비밀번호). 700줄 제한으로 B01_Dashboard_Repository_Members.py 분리
- 회사 등록 모달에 로고 파일 칸, PUT /company/logo 신설, 회사 패널·회사 목록에서 변경
- 프로젝트 수정 모달의 「설계자 서명」 칸 제거 — signature_asset_id 는 null 로 고정 저장
- 검증(5174 실조작): 신규 등록 구성원 2→3(id 6 PENDING)·select 자동 선택, 서명 자산
  [4,'검증신규 서명',user_id 6] 생성, 회사 logo_asset_id None→1, 검증 자산·계정 정리.
  pytest 143 passed, tsc·ruff·prettier 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 18:57:16 +09:00

112 lines
4.4 KiB
Python

"""회사 구성원과 회사 대표 로고 저장소 (B01_Dashboard_Repository 에서 분리, 700줄 제한).
구성원은 도면 표제란의 사람 자리(과업책임자·분야별책임자·설계자)를 채우는 원천이라
자산·로고와 같은 결로 묶어 둔다.
"""
from __future__ import annotations
import secrets
from typing import Any
import aiomysql
from common_util.common_util_auth import hash_password
from config.config_db import get_db_pool
from .B01_Dashboard_Repository import _role, get_dashboard_me
async def list_company_members(company_id: int) -> list[dict[str, Any]]:
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
await cursor.execute(
"""SELECT id, email, name, position, department, role, is_master, status
FROM users WHERE company_id = %s AND deleted_at IS NULL ORDER BY name, email""",
(company_id,),
)
rows = list(await cursor.fetchall())
for row in rows:
row["role"] = _role(row.get("role"))
row["is_master"] = bool(row.get("is_master"))
return rows
async def add_company_member(
company_id: int,
email: str,
profile: dict[str, Any] | None = None,
) -> dict[str, Any] | None:
"""회사에 사람을 붙인다.
`profile["name"]` 이 있으면 **계정이 없는 사람도 그 자리에서 만든다**
(2026-09-02 사용자 확정 — 담당자 선택의 「신규 등록…」). 새 계정은 로그인할 수 없는
비밀번호로 서고(`status='PENDING'`), 본인이 비밀번호를 세우면 그때 쓰인다.
"""
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
await connection.begin()
await cursor.execute(
"""SELECT id, company_id FROM users
WHERE email = %s AND deleted_at IS NULL FOR UPDATE""",
(email.lower(),),
)
user = await cursor.fetchone()
if not user and profile and profile.get("name"):
await cursor.execute(
"""INSERT INTO users (email, password_hash, name, position, department,
company_id, role, status)
VALUES (%s, %s, %s, %s, %s, %s, 'USER', 'PENDING')""",
(
email.lower(),
hash_password(secrets.token_urlsafe(32)), # 아무도 못 맞히는 비밀번호
profile["name"],
profile.get("position"),
profile.get("department"),
company_id,
),
)
await connection.commit()
return await get_dashboard_me(cursor.lastrowid)
if not user or user["company_id"] == company_id:
await connection.rollback()
return None
await cursor.execute(
"""UPDATE users SET company_id = %s, status = 'ACTIVE', role = 'USER',
is_master = FALSE
WHERE id = %s""",
(company_id, user["id"]),
)
await connection.commit()
return await get_dashboard_me(user["id"])
async def remove_company_member(company_id: int, user_id: int) -> bool:
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
await cursor.execute(
"""UPDATE users SET company_id = NULL, status = 'NO_COMPANY', role = 'USER',
is_master = FALSE
WHERE id = %s AND company_id = %s AND is_master = FALSE""",
(user_id, company_id),
)
changed = cursor.rowcount > 0
await connection.commit()
return changed
async def set_company_logo(company_id: int, asset_id: int | None) -> bool:
"""회사 대표 로고를 지정한다 (2026-09-02 사용자 확정 — 회사 등록 단계에서 받고 변경).
`asset_id` 가 같은 회사의 `kind='LOGO'` 자산인지는 라우터가 확인한다.
"""
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
await cursor.execute(
"UPDATE companies SET logo_asset_id = %s WHERE id = %s AND deleted_at IS NULL",
(asset_id, company_id),
)
changed = cursor.rowcount > 0
await connection.commit()
return changed