"""회사·가입 신청 저장소 (B01_Dashboard_Repository 에서 분리, 700줄 제한). 회사를 만들고 고치고 찾는 일, 그리고 그 회사에 들어가겠다는 신청을 다루는 곳이다. 사용자·프로젝트 쪽은 `B01_Dashboard_Repository` 에 남는다. """ from __future__ import annotations from typing import Any import aiomysql from common_util.common_util_audit import record_audit from config.config_db import get_db_pool from .B01_Dashboard_Repository import role_for_company async def get_user_company(company_id: int | None) -> dict[str, Any] | None: if company_id is None: return None pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT c.id, c.name, c.business_registration_number, c.business_address, c.business_owner, c.business_status, c.logo_asset_id, COUNT(DISTINCT u.id) AS user_count, COUNT(DISTINCT p.id) AS project_count FROM companies c LEFT JOIN users u ON u.company_id = c.id AND u.deleted_at IS NULL LEFT JOIN projects p ON p.company_id = c.id AND p.deleted_at IS NULL WHERE c.id = %s AND c.deleted_at IS NULL GROUP BY c.id""", (company_id,), ) return await cursor.fetchone() async def update_company(company_id: int, data: dict[str, Any]) -> bool: """회사 정보 수정 (2026-09-06 사용자 지시) — 회사 관리자는 자기 회사, 시스템관리자는 전체.""" pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: await cursor.execute( """UPDATE companies SET name = %s, business_registration_number = %s, business_address = %s, business_owner = %s WHERE id = %s AND deleted_at IS NULL""", ( data["name"], data["business_registration_number"], data.get("business_address"), data.get("business_owner"), company_id, ), ) changed = cursor.rowcount > 0 if not changed: # 값이 하나도 안 바뀌면 rowcount 가 0 이다 — 회사가 없는 것과는 다르다. await cursor.execute( "SELECT 1 FROM companies WHERE id = %s AND deleted_at IS NULL", (company_id,) ) changed = await cursor.fetchone() is not None await connection.commit() return changed async def search_companies(query: str) -> list[dict[str, Any]]: pool = get_db_pool() pattern = f"%{query}%" async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT id, name, business_registration_number, business_status FROM companies WHERE deleted_at IS NULL AND (name LIKE %s OR business_registration_number LIKE %s) ORDER BY name LIMIT 20""", (pattern, pattern), ) return list(await cursor.fetchall()) async def create_company( user_id: int, data: dict[str, Any], request: Any | None = None ) -> dict[str, Any]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: try: await connection.begin() await cursor.execute( """INSERT INTO companies (name, business_registration_number, business_address, business_owner, business_status, master_user_id, created_by, status) VALUES (%s, %s, %s, %s, '활동중', %s, %s, 'ACTIVE')""", ( data["name"], data["business_registration_number"], data.get("business_address"), data.get("business_owner"), user_id, user_id, ), ) company_id = cursor.lastrowid await cursor.execute( """UPDATE users SET company_id = %s, role = 'ADMIN', is_master = TRUE, status = 'ACTIVE' WHERE id = %s""", (company_id, user_id), ) await record_audit( cursor, actor_id=user_id, action="COMPANY_CREATE", resource_type="company", resource_ref=company_id, request=request, ) await connection.commit() return {"company_id": company_id, "status": "ACTIVE"} except Exception: await connection.rollback() raise async def create_system_company( actor_id: int, data: dict[str, Any], request: Any | None = None ) -> dict[str, Any]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: try: await connection.begin() await cursor.execute( """INSERT INTO companies (name, business_registration_number, business_address, business_owner, business_status, created_by, status) VALUES (%s, %s, %s, %s, '활동중', %s, 'ACTIVE')""", ( data["name"], data["business_registration_number"], data.get("business_address"), data.get("business_owner"), actor_id, ), ) company_id = cursor.lastrowid await record_audit( cursor, actor_id=actor_id, action="COMPANY_CREATE", resource_type="company", resource_ref=company_id, request=request, ) await connection.commit() return {"company_id": company_id, "status": "ACTIVE"} except Exception: await connection.rollback() raise async def join_company(user_id: int, company_id: int) -> dict[str, Any] | None: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: try: await connection.begin() # 이미 소속이 있는 사람의 재신청은 막는다 (2026-09-06 사용자 지시) — # 예전에는 신청 즉시 소속이 풀리고 승인대기로 떨어져 회사를 잃었다. await cursor.execute( "SELECT company_id FROM users WHERE id = %s AND deleted_at IS NULL FOR UPDATE", (user_id,), ) current = await cursor.fetchone() if current and current[0]: await connection.rollback() return None await cursor.execute( """INSERT INTO join_requests (user_id, company_id, status) VALUES (%s, %s, 'PENDING') ON DUPLICATE KEY UPDATE status = 'PENDING', requested_at = CURRENT_TIMESTAMP, reviewed_by = NULL, reviewed_at = NULL""", (user_id, company_id), ) request_id = cursor.lastrowid await cursor.execute( "UPDATE users SET status = 'PENDING', company_id = NULL WHERE id = %s", (user_id,) ) await connection.commit() return {"join_request_id": request_id, "status": "PENDING"} except Exception: await connection.rollback() raise async def list_join_requests(company_id: int | None = None) -> list[dict[str, Any]]: # 처리 끝난 신청은 목록에 남기지 않는다 (2026-09-06 사용자 지시) — 승인된 사람은 # 사용자 관리 목록에 이미 있어 같은 사람이 두 번 보였다. where = "WHERE jr.status = 'PENDING'" + (" AND jr.company_id = %s" if company_id else "") params = (company_id,) if company_id else () pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( f"""SELECT jr.id, jr.user_id, jr.company_id, jr.requested_at, jr.status, u.email AS user_email, u.name AS user_name, c.name AS company_name FROM join_requests jr JOIN users u ON u.id = jr.user_id JOIN companies c ON c.id = jr.company_id {where} ORDER BY jr.requested_at DESC LIMIT 100""", params, ) return list(await cursor.fetchall()) async def process_join_request( request_id: int, reviewer_id: int, approved: bool, company_id: int | None = None ) -> bool: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await connection.begin() where = "id = %s AND status = 'PENDING'" params: tuple[Any, ...] = (request_id,) if company_id is not None: where += " AND company_id = %s" params = (request_id, company_id) await cursor.execute(f"SELECT * FROM join_requests WHERE {where} FOR UPDATE", params) row = await cursor.fetchone() if not row: await connection.rollback() return False status = "APPROVED" if approved else "REJECTED" await cursor.execute( """UPDATE join_requests SET status = %s, reviewed_by = %s, reviewed_at = CURRENT_TIMESTAMP WHERE id = %s""", (status, reviewer_id, request_id), ) if approved: # 시스템 회사로 들어오면 역할도 함께 올린다 (2026-09-06 사용자 확정). role = await role_for_company(int(row["company_id"])) await cursor.execute( """UPDATE users SET company_id = %s, status = 'ACTIVE', role = %s WHERE id = %s""", (row["company_id"], role, row["user_id"]), ) else: await cursor.execute( "UPDATE users SET status = 'REJECTED' WHERE id = %s", (row["user_id"],) ) await connection.commit() return True async def list_all_companies() -> list[dict[str, Any]]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT c.id, c.name, c.business_registration_number, c.business_status, c.business_address, c.business_owner, c.logo_asset_id, c.created_at, COUNT(DISTINCT u.id) AS user_count, COUNT(DISTINCT p.id) AS project_count FROM companies c LEFT JOIN users u ON u.company_id = c.id AND u.deleted_at IS NULL LEFT JOIN projects p ON p.company_id = c.id AND p.deleted_at IS NULL WHERE c.deleted_at IS NULL GROUP BY c.id ORDER BY c.created_at DESC""" ) return list(await cursor.fetchall())