Files
invyone/backend-spring/src/main/resources/mapper/provisioning.xml
T
hjjeong e16fb16987 어드민 cross-tenant 집계 (SUPER_ADMIN) + 사용자관리 자체 스크롤
SUPER_ADMIN 토큰(company_code=*)이면 등록 회사들 DB 를 순회해 결과를
집계해 돌려주는 CrossTenantAggregator/Controller 추가. 사용자/권한그룹/
배치/다국어 키 4개 도메인의 list API 가 cross-tenant 모드 지원.

UserTable + ResponsiveDataView 에 compact/scrollContainer prop 추가.
페이지 헤더/툴바/페이지네이션은 고정, 테이블만 자체 스크롤.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:52:30 +09:00

222 lines
7.4 KiB
XML

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="provisioning">
<select id="existsCompanyCode" parameterType="map" resultType="int">
SELECT 1 FROM COMPANY_MNG WHERE COMPANY_CODE = #{company_code} LIMIT 1
</select>
<select id="existsSubdomain" parameterType="map" resultType="int">
SELECT 1 FROM COMPANY_MNG WHERE SUBDOMAIN = #{subdomain} LIMIT 1
</select>
<select id="existsDbName" parameterType="map" resultType="int">
SELECT 1 FROM COMPANY_MNG WHERE DB_NAME = #{db_name} LIMIT 1
</select>
<insert id="insertCompanyWithTenant" parameterType="map">
INSERT INTO COMPANY_MNG (
COMPANY_CODE
, COMPANY_NAME
, BUSINESS_REGISTRATION_NUMBER
, REPRESENTATIVE_NAME
, REPRESENTATIVE_PHONE
, EMAIL
, WEBSITE
, ADDRESS
, STATUS
, DB_NAME
, SUBDOMAIN
, DB_HOST
, DB_STATUS
, PLAN
, INDUSTRY
, TEMPLATES_COUNT
, WRITER
, CREATED_DATE
) VALUES (
#{company_code}
, #{company_name}
, #{business_registration_number}
, #{representative_name}
, #{representative_phone}
, #{email}
, #{website}
, #{address}
, COALESCE(#{status}, 'active')
, #{db_name}
, #{subdomain}
, #{db_host}
, COALESCE(#{db_status}, 'provisioning')
, COALESCE(#{plan}, 'Starter')
, #{industry}
, COALESCE(#{templates_count}, 0)
, #{writer}
, NOW()
)
</insert>
<!--
회사관리 UI (v9 accordion) 렌더용 전체 목록.
정적 필드만 반환. users / active30 / db_size / spark 등 derived 는 CompanyStatsService 가 덧붙임.
-->
<select id="listCompaniesForUi" resultType="map">
SELECT
COMPANY_CODE as company_code
, COMPANY_NAME as company_name
, SUBDOMAIN as subdomain
, DB_NAME as db_name
, DB_HOST as db_host
, DB_STATUS as db_status
, STATUS as status
, COALESCE(PLAN, 'Starter') as plan
, INDUSTRY as industry
, REPRESENTATIVE_NAME as owner
, BUSINESS_REGISTRATION_NUMBER as brn
, EMAIL as email
, COALESCE(TEMPLATES_COUNT, 0) as templates
, COALESCE(DB_QUOTA_GB, 20) as db_quota_gb
, CREATED_DATE as created
, WRITER as writer
FROM COMPANY_MNG
ORDER BY CREATED_DATE DESC NULLS LAST, COMPANY_CODE
</select>
<update id="updateDbStatus" parameterType="map">
UPDATE COMPANY_MNG
SET DB_STATUS = #{db_status}
WHERE COMPANY_CODE = #{company_code}
</update>
<!-- 부팅 시 스키마 마이그레이션 러너용: 활성 테넌트 DB 이름 목록 -->
<select id="listActiveDbNames" resultType="string">
SELECT DB_NAME
FROM COMPANY_MNG
WHERE DB_STATUS = 'active'
AND DB_NAME IS NOT NULL
</select>
<!-- 회사 상세 조회 (lifecycle 관리 액션 수행 전 검증용) -->
<select id="selectCompanyByCode" parameterType="map" resultType="map">
SELECT
COMPANY_CODE as company_code
, COMPANY_NAME as company_name
, SUBDOMAIN as subdomain
, DB_NAME as db_name
, DB_HOST as db_host
, DB_STATUS as db_status
, STATUS as status
, COALESCE(INSTALLED_GROUPS, '[]'::jsonb) as installed_groups
, DEACTIVATED_AT as deactivated_at
, DEACTIVATION_REASON as deactivation_reason
, CREATED_DATE as created
FROM COMPANY_MNG
WHERE COMPANY_CODE = #{company_code}
</select>
<!-- 비활성화: DB_STATUS='suspended' + 타임스탬프/사유 기록 -->
<update id="deactivateCompany" parameterType="map">
UPDATE COMPANY_MNG
SET DB_STATUS = 'suspended'
, DEACTIVATED_AT = NOW()
, DEACTIVATION_REASON = #{reason}
WHERE COMPANY_CODE = #{company_code}
</update>
<!-- 재활성화: DB_STATUS='active' + 타임스탬프/사유 클리어 -->
<update id="reactivateCompany" parameterType="map">
UPDATE COMPANY_MNG
SET DB_STATUS = 'active'
, DEACTIVATED_AT = NULL
, DEACTIVATION_REASON = NULL
WHERE COMPANY_CODE = #{company_code}
</update>
<!-- 영구 삭제 (메타 DB row). 실제 DROP DATABASE 는 서비스 레이어가 먼저 실행 -->
<delete id="deleteCompany" parameterType="map">
DELETE FROM COMPANY_MNG
WHERE COMPANY_CODE = #{company_code}
</delete>
<!-- installed_groups 저장 (provisioning 시 selected_groups 를 JSONB 로) -->
<update id="updateInstalledGroups" parameterType="map">
UPDATE COMPANY_MNG
SET INSTALLED_GROUPS = #{installed_groups_json}::jsonb
WHERE COMPANY_CODE = #{company_code}
</update>
<!-- ─────────────── 회사 관리 감사 로그 (COMPANY_AUDIT_LOG) ─────────────── -->
<insert id="insertAuditLog" parameterType="map">
INSERT INTO COMPANY_AUDIT_LOG (
COMPANY_CODE
, ACTOR_USER_ID
, ACTION
, TARGET
, DETAILS
, SUCCESS
, ERROR_MESSAGE
, CREATED_AT
) VALUES (
#{company_code}
, #{actor_user_id}
, #{action}
, #{target}
, <choose>
<when test="details_json != null">#{details_json}::jsonb</when>
<otherwise>NULL</otherwise>
</choose>
, COALESCE(#{success}, TRUE)
, #{error_message}
, NOW()
)
</insert>
<select id="selectAuditLog" parameterType="map" resultType="map">
SELECT
ID as id
, COMPANY_CODE as company_code
, ACTOR_USER_ID as actor_user_id
, ACTION as action
, TARGET as target
, DETAILS as details
, SUCCESS as success
, ERROR_MESSAGE as error_message
, CREATED_AT as created_at
FROM COMPANY_AUDIT_LOG
<where>
<if test="company_code != null">AND COMPANY_CODE = #{company_code}</if>
<if test="action != null">AND ACTION = #{action}</if>
</where>
ORDER BY CREATED_AT DESC, ID DESC
LIMIT #{limit} OFFSET #{offset}
</select>
<select id="countAuditLog" parameterType="map" resultType="long">
SELECT COUNT(*) FROM COMPANY_AUDIT_LOG
<where>
<if test="company_code != null">AND COMPANY_CODE = #{company_code}</if>
<if test="action != null">AND ACTION = #{action}</if>
</where>
</select>
<!--
Phase A (cross-tenant 집계, 2026-04-27): SUPER_ADMIN fan-out 대상 회사 목록.
listCompaniesForUi 와 다름 — 그 쿼리는 회사관리 화면 렌더용으로 모든 상태 포함.
이건 active 상태만, 라우팅 가능한(DB_NAME 박힌) 회사만.
-->
<select id="listActiveCompanies" resultType="map">
SELECT
COMPANY_CODE AS company_code
, COMPANY_NAME AS company_name
, DB_NAME AS db_name
FROM COMPANY_MNG
WHERE DB_STATUS = 'active'
AND DB_NAME IS NOT NULL
ORDER BY COMPANY_CODE
</select>
</mapper>