-- 租户主数据底座 -- 目的: -- 1. 为“地区 -> 租户”升级提供稳定主数据 -- 2. 为后续入口模块、RAG、文档、模板等模块提供 tenant_code -- 3. 保持对现有 area / region / tenant_name 的兼容 BEGIN; CREATE TABLE IF NOT EXISTS sys_tenants ( id BIGSERIAL PRIMARY KEY, tenant_code VARCHAR(64) NOT NULL UNIQUE, tenant_name VARCHAR(128) NOT NULL, tenant_short_name VARCHAR(64) NULL, tenant_type VARCHAR(32) NOT NULL, parent_tenant_code VARCHAR(64) NULL, display_order INT NOT NULL DEFAULT 0, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, is_builtin BOOLEAN NOT NULL DEFAULT FALSE, is_public BOOLEAN NOT NULL DEFAULT FALSE, can_host_entry_module BOOLEAN NOT NULL DEFAULT TRUE, can_host_documents BOOLEAN NOT NULL DEFAULT TRUE, can_host_rag BOOLEAN NOT NULL DEFAULT TRUE, can_host_templates BOOLEAN NOT NULL DEFAULT TRUE, ext JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ NULL ); CREATE INDEX IF NOT EXISTS idx_sys_tenants_enabled_order ON sys_tenants(is_enabled, display_order, id) WHERE deleted_at IS NULL; CREATE TABLE IF NOT EXISTS sys_tenant_aliases ( id BIGSERIAL PRIMARY KEY, tenant_code VARCHAR(64) NOT NULL, alias_value VARCHAR(128) NOT NULL, alias_type VARCHAR(32) NOT NULL, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ NULL, UNIQUE (tenant_code, alias_value) ); CREATE INDEX IF NOT EXISTS idx_sys_tenant_aliases_lookup ON sys_tenant_aliases(alias_value, alias_type) WHERE deleted_at IS NULL AND is_enabled = TRUE; CREATE TABLE IF NOT EXISTS sys_tenant_feature_flags ( id BIGSERIAL PRIMARY KEY, tenant_code VARCHAR(64) NOT NULL, feature_key VARCHAR(64) NOT NULL, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, ext JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ NULL, UNIQUE (tenant_code, feature_key) ); CREATE INDEX IF NOT EXISTS idx_sys_tenant_features_lookup ON sys_tenant_feature_flags(tenant_code, feature_key) WHERE deleted_at IS NULL; ALTER TABLE sso_users ADD COLUMN IF NOT EXISTS tenant_code VARCHAR(64) NULL; CREATE INDEX IF NOT EXISTS idx_sso_users_tenant_code ON sso_users(tenant_code) WHERE deleted_at IS NULL; COMMENT ON TABLE sys_tenants IS '系统租户主数据表'; COMMENT ON TABLE sys_tenant_aliases IS '租户历史别名与展示别名映射表'; COMMENT ON TABLE sys_tenant_feature_flags IS '租户能力开关表'; COMMENT ON COLUMN sso_users.tenant_code IS '用户主归属租户编码,优先于 area 作为租户边界'; INSERT INTO sys_tenants ( tenant_code, tenant_name, tenant_short_name, tenant_type, parent_tenant_code, display_order, is_enabled, is_builtin, is_public, can_host_entry_module, can_host_documents, can_host_rag, can_host_templates, ext ) VALUES ('MZ', '梅州', '梅州', 'LOCAL', NULL, 10, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, '{}'::jsonb), ('YF', '云浮', '云浮', 'LOCAL', NULL, 20, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, '{}'::jsonb), ('JY', '揭阳', '揭阳', 'LOCAL', NULL, 30, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, '{}'::jsonb), ('CZ', '潮州', '潮州', 'LOCAL', NULL, 40, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, '{}'::jsonb), ('PROVINCIAL', '省局', '省局', 'HEADQUARTER', NULL, 90, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, '{}'::jsonb), ('PUBLIC', '公共资源域', '公共', 'PUBLIC', NULL, 100, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, '{}'::jsonb) ON CONFLICT (tenant_code) DO UPDATE SET tenant_name = EXCLUDED.tenant_name, tenant_short_name = EXCLUDED.tenant_short_name, tenant_type = EXCLUDED.tenant_type, parent_tenant_code = EXCLUDED.parent_tenant_code, display_order = EXCLUDED.display_order, is_enabled = EXCLUDED.is_enabled, is_builtin = EXCLUDED.is_builtin, is_public = EXCLUDED.is_public, can_host_entry_module = EXCLUDED.can_host_entry_module, can_host_documents = EXCLUDED.can_host_documents, can_host_rag = EXCLUDED.can_host_rag, can_host_templates = EXCLUDED.can_host_templates, ext = EXCLUDED.ext, updated_at = NOW(); INSERT INTO sys_tenant_aliases (tenant_code, alias_value, alias_type, is_enabled) VALUES ('MZ', '梅州', 'LEGACY_AREA', TRUE), ('YF', '云浮', 'LEGACY_AREA', TRUE), ('JY', '揭阳', 'LEGACY_AREA', TRUE), ('CZ', '潮州', 'LEGACY_AREA', TRUE), ('PROVINCIAL', '省局', 'DISPLAY', TRUE), ('PUBLIC', '省级', 'LEGACY_REGION', TRUE), ('PUBLIC', 'default', 'LEGACY_REGION', TRUE), ('PUBLIC', '', 'LEGACY_REGION', TRUE) ON CONFLICT (tenant_code, alias_value) DO UPDATE SET alias_type = EXCLUDED.alias_type, is_enabled = EXCLUDED.is_enabled, updated_at = NOW(); INSERT INTO sys_tenant_feature_flags (tenant_code, feature_key, is_enabled) VALUES ('MZ', 'home.entry_module', TRUE), ('YF', 'home.entry_module', TRUE), ('JY', 'home.entry_module', TRUE), ('CZ', 'home.entry_module', TRUE), ('PROVINCIAL', 'home.entry_module', TRUE), ('PUBLIC', 'home.entry_module', TRUE), ('MZ', 'rag.dataset', TRUE), ('YF', 'rag.dataset', TRUE), ('JY', 'rag.dataset', TRUE), ('CZ', 'rag.dataset', TRUE), ('PROVINCIAL', 'rag.dataset', TRUE), ('PUBLIC', 'rag.dataset', TRUE), ('MZ', 'documents.upload', TRUE), ('YF', 'documents.upload', TRUE), ('JY', 'documents.upload', TRUE), ('CZ', 'documents.upload', TRUE), ('PROVINCIAL', 'documents.upload', TRUE), ('PUBLIC', 'documents.upload', TRUE) ON CONFLICT (tenant_code, feature_key) DO UPDATE SET is_enabled = EXCLUDED.is_enabled, updated_at = NOW(); UPDATE sso_users u SET tenant_code = mapped.tenant_code FROM ( SELECT DISTINCT ON (a.alias_value) a.alias_value, a.tenant_code FROM sys_tenant_aliases a WHERE a.deleted_at IS NULL AND a.is_enabled = TRUE ORDER BY a.alias_value, a.id ASC ) mapped WHERE u.deleted_at IS NULL AND COALESCE(BTRIM(u.tenant_code), '') = '' AND COALESCE(BTRIM(u.area), '') = mapped.alias_value; COMMIT;