import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { changeMerchantMemberStatus, changeMerchantRoleStatus, createMerchantMember, createMerchantRole, deleteMerchantRole, getMerchantMembers, getMerchantPermissionResources, getMerchantRoles, getTenantProfile, getTenantQualifications, resetMerchantMemberPassword, replaceMerchantRolePermissions, updateMerchantMember, updateMerchantRole, updateTenantProfile, type MerchantRole, type MerchantMember, type MerchantPermissionResource, } from '@/modules/tenant-management/api/merchantIam' import { FRONTEND_TO_MERCHANT_PERMISSION_CODES, TENANT_PERMISSION_GROUPS, type MerchantTenantStatus, } from '@/modules/tenant-management/contracts' export type TenantRecordStatus = 'ACTIVE' | 'INACTIVE' export interface TenantCompanyProfile { companyName: string creditCode: string legalRepresentative: string contactName: string contactPhone: string contactEmail: string auditStatus: MerchantTenantStatus } export interface TenantQualification { id: string type: string fileName: string status: 'VERIFIED' | 'PENDING' | 'REJECTED' | 'EXPIRED' expiresAt: string updatedAt: string documentUrl: string } export interface TenantMember { id: string name: string account: string phone: string email: string roleIds: string[] status: TenantRecordStatus lastLoginAt: string isAdministrator: boolean } export interface TenantRole { id: string name: string code: string description: string status: TenantRecordStatus builtin: boolean permissionIds: string[] permissionCodes: string[] } const allPermissionCodes = TENANT_PERMISSION_GROUPS.flatMap((group) => group.items.map((item) => item.code)) const mappedMerchantPermissionCodes = new Set(Object.values(FRONTEND_TO_MERCHANT_PERMISSION_CODES).flat()) const emptyCompany: TenantCompanyProfile = { companyName: '', creditCode: '', legalRepresentative: '', contactName: '', contactPhone: '', contactEmail: '', auditStatus: 'PROFILE_REQUIRED', } export const useTenantAdministrationStore = defineStore('tenant-administration', () => { const company = ref({ ...emptyCompany }) const qualifications = ref([]) const members = ref([]) const roles = ref([]) const permissionResources = ref([]) const activeMemberCount = computed(() => members.value.filter((member) => member.status === 'ACTIVE').length) async function loadCompanyContext() { company.value = { ...emptyCompany } qualifications.value = [] const [profile, qualificationRecords] = await Promise.all([ getTenantProfile(), getTenantQualifications(), ]) company.value = { companyName: profile.tenantName, creditCode: profile.unifiedSocialCreditCode ?? '', legalRepresentative: profile.legalName, contactName: profile.contactName, contactPhone: profile.contactPhone, contactEmail: profile.contactEmail ?? '', auditStatus: profile.status, } qualifications.value = qualificationRecords.map((item) => ({ id: String(item.id), type: item.qualificationType === 'BUSINESS_LICENSE' ? '营业执照' : item.qualificationType, fileName: item.documentUrl.split('/').pop() || '营业执照', status: item.status === 'APPROVED' ? 'VERIFIED' : item.status, expiresAt: '-', updatedAt: '-', documentUrl: item.documentUrl, })) } function mapMerchantRole(role: MerchantRole): TenantRole { const grantedIds = (role.permissionIds ?? []).map(String) const grantedBackendCodes = new Set(permissionResources.value .filter((resource) => grantedIds.includes(String(resource.id))) .map((resource) => resource.permissionCode)) const permissionCodes = Object.entries(FRONTEND_TO_MERCHANT_PERMISSION_CODES) .filter(([, backendCodes]) => backendCodes.some((code) => grantedBackendCodes.has(code))) .map(([frontendCode]) => frontendCode) return { id: String(role.id), name: role.roleName, code: role.roleCode, description: role.description ?? '暂无角色说明', status: role.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE', builtin: role.roleType === 'SYSTEM', permissionIds: grantedIds, permissionCodes: role.roleType === 'SYSTEM' && role.roleCode === 'MERCHANT_OWNER' ? [...allPermissionCodes] : permissionCodes, } } function mapMerchantMember(member: MerchantMember): TenantMember { return { id: String(member.membershipId), name: member.displayName || `企业成员 ${member.membershipId}`, account: member.memberNo || `用户 ${member.authUserId}`, phone: member.phone ?? '', email: member.email ?? '', roleIds: member.roleIds.map(String), status: member.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE', lastLoginAt: '服务端未提供', isAdministrator: member.memberType === 'OWNER', } } async function loadRoles() { roles.value = [] permissionResources.value = [] const [roleRecords, resources] = await Promise.all([getMerchantRoles(), getMerchantPermissionResources()]) permissionResources.value = resources.filter((item) => item.status === 'ACTIVE') roles.value = roleRecords.map(mapMerchantRole) } async function loadMembers() { members.value = [] members.value = (await getMerchantMembers()).map(mapMerchantMember) } async function updateCompany(payload: TenantCompanyProfile) { const updated = await updateTenantProfile({ tenantName: payload.companyName.trim(), legalName: payload.legalRepresentative.trim(), contactName: payload.contactName.trim(), contactPhone: payload.contactPhone.trim(), contactEmail: payload.contactEmail.trim(), }) company.value = { companyName: updated.tenantName, creditCode: updated.unifiedSocialCreditCode ?? '', legalRepresentative: updated.legalName, contactName: updated.contactName, contactPhone: updated.contactPhone, contactEmail: updated.contactEmail ?? '', auditStatus: updated.status, } } async function createMember(payload: { name: string; account: string; password: string; phone: string; email: string; roleIds: string[]; memberNo: string }) { const created = await createMerchantMember({ username: payload.account.trim(), password: payload.password, displayName: payload.name.trim(), phone: payload.phone.trim(), email: payload.email.trim(), memberType: 'EMPLOYEE', memberNo: payload.memberNo.trim(), roleIds: payload.roleIds, }) members.value.unshift(mapMerchantMember(created)) } async function updateMember(id: string, payload: Pick & { memberNo: string }) { const member = members.value.find((item) => item.id === id) if (!member) throw new Error('成员不存在') const updated = await updateMerchantMember(id, { displayName: payload.name.trim(), phone: payload.phone.trim(), email: payload.email.trim(), memberType: member.isAdministrator ? 'OWNER' : 'EMPLOYEE', memberNo: payload.memberNo.trim(), roleIds: payload.roleIds, }) Object.assign(member, mapMerchantMember(updated)) } async function toggleMemberStatus(id: string) { const member = members.value.find((item) => item.id === id) if (!member) throw new Error('成员不存在') if (member.isAdministrator) throw new Error('企业管理员不能停用') const updated = await changeMerchantMemberStatus(id, member.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE') Object.assign(member, mapMerchantMember(updated)) } async function resetMemberPassword(id: string, newPassword: string) { await resetMerchantMemberPassword(id, newPassword) } async function createRole(payload: Pick) { if (roles.value.some((role) => role.code === payload.code)) throw new Error('角色编码已存在') const created = await createMerchantRole({ roleCode: payload.code, roleName: payload.name, description: payload.description }) roles.value.push(mapMerchantRole(created)) } async function updateRole(id: string, payload: Pick) { const role = roles.value.find((item) => item.id === id) if (!role) throw new Error('角色不存在') if (role.builtin) throw new Error('内置角色不能编辑') const updated = await updateMerchantRole(id, { roleName: payload.name.trim(), description: payload.description.trim() }) Object.assign(role, mapMerchantRole(updated)) } async function toggleRoleStatus(id: string) { const role = roles.value.find((item) => item.id === id) if (!role) throw new Error('角色不存在') if (role.builtin) throw new Error('内置角色不能停用') const updated = await changeMerchantRoleStatus(id, role.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE') Object.assign(role, mapMerchantRole(updated)) } async function deleteRole(id: string) { const role = roles.value.find((item) => item.id === id) if (!role) throw new Error('角色不存在') if (role.builtin) throw new Error('内置角色不能删除') if (members.value.some((member) => member.roleIds.includes(id))) throw new Error('该角色仍有成员使用,不能删除') await deleteMerchantRole(id) roles.value = roles.value.filter((item) => item.id !== id) } async function setRolePermissions(id: string, permissionCodes: string[]) { const role = roles.value.find((item) => item.id === id) if (!role) throw new Error('角色不存在') if (role.builtin) throw new Error('内置管理员权限不能修改') const backendCodes = new Set(permissionCodes.flatMap((code) => FRONTEND_TO_MERCHANT_PERMISSION_CODES[code] ?? [])) const selectedPermissionIds = permissionResources.value .filter((resource) => backendCodes.has(resource.permissionCode)) .map((resource) => resource.id) const mappedResourceIds = new Set(permissionResources.value .filter((resource) => mappedMerchantPermissionCodes.has(resource.permissionCode)) .map((resource) => String(resource.id))) // The endpoint replaces every ID, including MENU nodes this screen does not render. const preservedPermissionIds = role.permissionIds.filter((permissionId) => !mappedResourceIds.has(permissionId)) const nextPermissionIds = [...new Set([...preservedPermissionIds, ...selectedPermissionIds])] await replaceMerchantRolePermissions(id, nextPermissionIds) role.permissionIds = nextPermissionIds.map(String) role.permissionCodes = [...new Set(permissionCodes)] } function roleMemberCount(roleId: string) { return members.value.filter((member) => member.roleIds.includes(roleId)).length } function roleNames(roleIds: readonly string[]) { return roleIds.map((id) => roles.value.find((role) => role.id === id)?.name).filter((name): name is string => Boolean(name)) } return { company, qualifications, members, roles, activeMemberCount, loadCompanyContext, loadRoles, loadMembers, updateCompany, createMember, updateMember, toggleMemberStatus, resetMemberPassword, createRole, updateRole, toggleRoleStatus, deleteRole, setRolePermissions, roleMemberCount, roleNames, } })