/** 数据权限分组列表模型(任务 59;module 13 task 253 扩展编辑能力):解析 VO + 摘要 + 提交载荷。 */ import { unwrap } from '../../api/envelope.ts' export interface ShopGroupItem { id: number name: string leaderUsername: string leaderUserId?: number memberCount: number memberUsernames: string[] memberUserIds?: number[] createdAt?: string updatedAt?: string } export interface ShopGroupFormErrors { groupName?: string } function text(value: unknown): string { return typeof value === 'string' ? value.trim() : '' } function numberOrNull(value: unknown): number | null { return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : null } /** 解析单条分组项;缺 id 视为无效。 */ export function parseShopGroupItem(raw: unknown): ShopGroupItem | null { if (!raw || typeof raw !== 'object') return null const record = raw as Record const id = numberOrNull(record.id) if (id === null) return null const names = Array.isArray(record.memberUsernames) ? record.memberUsernames.map((v) => text(v)).filter(Boolean) : [] const memberIds = Array.isArray(record.memberUserIds) ? record.memberUserIds.map((v) => numberOrNull(v)).filter((v): v is number => v !== null && v > 0) : [] const count = numberOrNull(record.memberCount) const item: ShopGroupItem = { id, name: text(record.groupName ?? record.name) || `分组${id}`, leaderUsername: text(record.leaderUsername ?? record.leader_name), memberCount: count === null ? names.length : count, memberUsernames: names, } const leaderId = numberOrNull(record.leaderUserId ?? record.leader_user_id) if (leaderId !== null && leaderId > 0) item.leaderUserId = leaderId if (memberIds.length) item.memberUserIds = memberIds const createdAt = text(record.createdAt ?? record.created_at) if (createdAt) item.createdAt = createdAt const updatedAt = text(record.updatedAt ?? record.updated_at) if (updatedAt) item.updatedAt = updatedAt return item } /** 归一化分组列表响应(信封或裸数组)。 */ export function parseShopGroupList(payload: unknown): ShopGroupItem[] { const data = unwrap(payload) if (!Array.isArray(data)) return [] return data .map((raw) => parseShopGroupItem(raw)) .filter((item): item is ShopGroupItem => item !== null) } /** 分组摘要:分组总数 + 组员总数。 */ export function shopGroupSummary(groups: ShopGroupItem[]): { groupCount: number; memberTotal: number } { let memberTotal = 0 for (const group of groups) memberTotal += group.memberCount || 0 return { groupCount: groups.length, memberTotal } } /** 分组名必填校验(镜像后端 NotBlank 文案「请输入分组名称」)。 */ export function validateShopGroupForm(name: string): ShopGroupFormErrors { const errors: ShopGroupFormErrors = {} if (!text(name)) errors.groupName = '请输入分组名称' return errors } /** 创建载荷:groupName + 成员 + 创建人(= 组长,当前操作者)。 */ export function toShopGroupCreatePayload( name: string, memberUserIds: number[], createdById: number, ): { groupName: string; memberUserIds: number[]; createdById: number } { return { groupName: text(name), memberUserIds: dedupeIds(memberUserIds), createdById } } /** 更新载荷:groupName + 成员(组长/创建人不可改)。 */ export function toShopGroupUpdatePayload(name: string, memberUserIds: number[]): { groupName: string; memberUserIds: number[] } { return { groupName: text(name), memberUserIds: dedupeIds(memberUserIds) } } function dedupeIds(ids: unknown[]): number[] { if (!Array.isArray(ids)) return [] const set = new Set() for (const v of ids) { const id = numberOrNull(v) if (id !== null && id > 0) set.add(id) } return [...set].sort((a, b) => a - b) }