import { createApiClient } from "../lib/apiClient"; import { authorizedPostBlobDownload, authorizedPostMultipartJson } from "../lib/batchFileHttp"; import type { PagedResultDto, TeamMemberCreateInput, TeamMemberDto, TeamMemberGetListInput, TeamMemberUpdateInput, } from "../types/teamMember"; const api = createApiClient({ getToken: () => { try { return localStorage.getItem("access_token") ?? localStorage.getItem("token") ?? null; } catch { return null; } }, }); const PATH = "/team-member"; function toStringArray(v: unknown): string[] { if (Array.isArray(v)) return v.map((x) => String(x)); return []; } function toIdArray(v: unknown): string[] { if (!Array.isArray(v)) return []; const out: string[] = []; for (const x of v) { if (x === null || x === undefined) continue; if (typeof x === "string" || typeof x === "number") { out.push(String(x)); continue; } if (typeof x === "object") { const o = x as Record; const id = o.id ?? o.Id ?? o.locationId ?? o.LocationId ?? o.location_id ?? o.locationID; if (id !== null && id !== undefined) out.push(String(id)); } } return out; } function toLocationLabels(v: unknown): string[] { if (!Array.isArray(v)) return []; const out: string[] = []; for (const x of v) { if (x === null || x === undefined) continue; if (typeof x === "string" || typeof x === "number") { out.push(String(x)); continue; } if (typeof x === "object") { const o = x as Record; const code = (o.locationCode ?? o.LocationCode ?? o.code ?? o.Code ?? o.location_code ?? o.locationCodeId) as unknown; const name = (o.locationName ?? o.LocationName ?? o.name ?? o.Name ?? o.location_name) as unknown; const id = (o.id ?? o.Id ?? o.locationId ?? o.LocationId) as unknown; const codeS = code === null || code === undefined ? "" : String(code).trim(); const nameS = name === null || name === undefined ? "" : String(name).trim(); const idS = id === null || id === undefined ? "" : String(id).trim(); if (codeS && nameS) out.push(`${codeS} - ${nameS}`); else if (nameS) out.push(nameS); else if (codeS) out.push(codeS); else if (idS) out.push(idS); } } return out; } function normalizeTeamMemberDto(row: unknown): TeamMemberDto { if (!row || typeof row !== "object") return { id: "" }; const r = row as Record; // ABP 常见 camelCase + 少量 PascalCase 兼容 const id = String( r.id ?? r.Id ?? r.userId ?? r.UserId ?? r.user_id ?? r.UserID ?? r.memberId ?? r.MemberId ?? "", ); // name fields: fullName or name const fullName = (r.fullName ?? r.FullName ?? r.name ?? r.Name) as string | null | undefined; const userName = (r.userName ?? r.UserName ?? r.username ?? r.UserName) as string | null | undefined; const email = (r.email ?? r.Email) as string | null | undefined; const phone = (r.phone ?? r.Phone) as string | null | undefined; // role 可能是扁平字段(roleId/roleName),也可能是嵌套对象(role: { id, roleName }) let roleId = (r.roleId ?? r.RoleId) as string | null | undefined; let roleName = (r.roleName ?? r.RoleName ?? r.roleName ?? r.Role) as string | null | undefined; const roleObj = (r.role ?? r.Role) as unknown; if ((!roleId || !roleName) && roleObj && typeof roleObj === "object") { const ro = roleObj as Record; roleId = (ro.id ?? ro.Id ?? ro.roleId ?? ro.RoleId ?? roleId) as string | null | undefined; roleName = (ro.roleName ?? ro.RoleName ?? ro.name ?? ro.Name ?? ro.role ?? ro.Role ?? roleName) as | string | null | undefined; } const stateRaw = r.state ?? r.State; const state = typeof stateRaw === "boolean" ? (stateRaw as boolean) : stateRaw === "true" ? true : stateRaw === "false" ? false : undefined; const rawLocationIds = r.locationIds ?? r.LocationIds ?? r.assignedLocationIds ?? r.AssignedLocationIds ?? r.location_id_list ?? r.LocationIdList; let locationIds = toIdArray(rawLocationIds); const rawLocations = r.locations ?? r.Locations ?? r.assignedLocations ?? r.AssignedLocations ?? r.locationNames ?? r.LocationNames; // locations 可能包含对象数组:{ id, locationCode, locationName } 或类似 let locations = toLocationLabels(rawLocations); // 如果 locations 返回的是对象数组但没有显式 locationIds,则从 locations 对象里抽 id if (locationIds.length === 0 && Array.isArray(rawLocations)) { const inferredIds: string[] = []; for (const x of rawLocations) { if (typeof x !== "object" || !x) continue; const o = x as Record; const id = o.id ?? o.Id ?? o.locationId ?? o.LocationId; if (id !== null && id !== undefined) inferredIds.push(String(id)); } if (inferredIds.length) locationIds = inferredIds; } const partnerIds = toIdArray(r.partnerIds ?? r.PartnerIds); const regionIds = toIdArray( r.regionIds ?? r.RegionIds ?? r.groupIds ?? r.GroupIds, ); const partnerId = (r.partnerId ?? r.PartnerId ?? partnerIds[0] ?? null) as string | null | undefined; const groupId = (r.groupId ?? r.GroupId ?? regionIds[0] ?? null) as string | null | undefined; // 有些接口可能只返回一个 locations,但我们仍尽量填充 return { id, fullName, userName, email, phone, roleId, roleName, locationIds, locations, partnerId: partnerId != null ? String(partnerId) : null, groupId: groupId != null ? String(groupId) : null, partnerIds, regionIds, groupIds: regionIds, state: state ?? (r.status ? (String(r.status).toLowerCase() === "active") : undefined), }; } export async function getTeamMembers( input: TeamMemberGetListInput, signal?: AbortSignal, ): Promise> { const raw = await api.requestJson>({ path: PATH, method: "GET", query: { SkipCount: input.skipCount, MaxResultCount: input.maxResultCount, Keyword: input.keyword, RoleId: input.roleId, PartnerId: input.partnerId, GroupId: input.groupId, LocationId: input.locationId, State: input.state, Sorting: input.sorting, }, signal, }); // requestJson 已做分页 shape 规范化,但这里做 DTO 规范 const items = (raw.items ?? []) as unknown[]; return { totalCount: raw.totalCount ?? 0, items: items.map(normalizeTeamMemberDto), }; } export async function getTeamMemberById(id: string, signal?: AbortSignal): Promise { const raw = await api.requestJson({ path: `${PATH}/${encodeURIComponent(id)}`, method: "GET", signal, }); return normalizeTeamMemberDto(raw); } function toPhoneNumber(v: string | number | null | undefined): number | null { if (v === null || v === undefined || v === "") return null; const s = String(v).trim(); if (!s) return null; const num = Number(s.replace(/\D/g, "")) || 0; return num; } function scopeIdsForApi(ids: string[] | undefined): string[] | undefined { const list = [...new Set((ids ?? []).map((x) => String(x).trim()).filter(Boolean))]; return list.length ? list : undefined; } function buildCreatePayload(input: TeamMemberCreateInput): Record { const phoneVal = input.phone != null && input.phone !== "" ? toPhoneNumber(String(input.phone)) : null; const partnerId = (input.partnerId ?? "").trim() || undefined; const regionIds = scopeIdsForApi(input.regionIds); return { fullName: input.fullName, userName: input.userName, password: input.password, email: input.email ?? null, phone: phoneVal, roleId: input.roleId, partnerId, partnerIds: partnerId ? [partnerId] : undefined, regionIds, groupIds: regionIds, locationIds: input.locationIds, locations: input.locationIds, state: input.state, }; } function buildUpdatePayload(input: TeamMemberUpdateInput): Record { const phoneVal = input.phone != null && input.phone !== "" ? toPhoneNumber(String(input.phone)) : null; const partnerId = (input.partnerId ?? "").trim() || undefined; const regionIds = scopeIdsForApi(input.regionIds); const payload: Record = { fullName: input.fullName, userName: input.userName, email: input.email ?? null, phone: phoneVal, roleId: input.roleId, partnerId, partnerIds: partnerId ? [partnerId] : undefined, regionIds, groupIds: regionIds, locationIds: input.locationIds, locations: input.locationIds, state: input.state, }; if (input.password) payload.password = input.password; return payload; } export async function createTeamMember(input: TeamMemberCreateInput): Promise { const raw = await api.requestJson({ path: PATH, method: "POST", body: buildCreatePayload(input), }); return normalizeTeamMemberDto(raw); } export async function updateTeamMember(id: string, input: TeamMemberUpdateInput): Promise { const raw = await api.requestJson({ path: `${PATH}/${encodeURIComponent(id)}`, method: "PUT", body: buildUpdatePayload(input), }); return normalizeTeamMemberDto(raw); } export async function deleteTeamMember(id: string): Promise { await api.requestJson({ path: `${PATH}/${encodeURIComponent(id)}`, method: "DELETE", }); } /** PDF 全量导出筛选(与列表一致,不含分页)。 */ export type TeamMemberExportQueryInput = { keyword?: string; roleId?: string; locationId?: string; state?: boolean; sorting?: string; }; export type TeamMemberBatchImportResultDto = { successCount: number; failCount: number; errors?: Array<{ rowNumber?: number; userName?: string; message?: string }>; }; export type TeamMemberBulkUpdateItemVo = { id: string; fullName: string; userName: string; password?: string | null; email?: string | null; phone?: number | null; roleId: string; locationIds: string[]; state: boolean; }; export type TeamMemberBulkUpdateResultDto = { successCount: number; failCount: number; errors?: Array<{ rowNumber?: number; id?: string; message?: string }>; }; export async function downloadTeamMemberImportTemplate(signal?: AbortSignal): Promise { await authorizedPostBlobDownload({ path: `${PATH}/download-team-member-import-template`, defaultFileName: "Team-Member-template.xlsx", signal, }); } export async function exportTeamMembersPdf(input: TeamMemberExportQueryInput, signal?: AbortSignal): Promise { await authorizedPostBlobDownload({ path: `${PATH}/export-team-members-pdf`, query: { Keyword: input.keyword, RoleId: input.roleId, LocationId: input.locationId, State: input.state, Sorting: input.sorting, }, defaultFileName: "team-members.pdf", signal, }); } export async function importTeamMembersBatch(file: File, signal?: AbortSignal): Promise { return authorizedPostMultipartJson({ path: `${PATH}/import-team-members-batch`, fieldName: "file", file, signal, }); } export async function updateTeamMembersBulk( body: { items: TeamMemberBulkUpdateItemVo[] }, ): Promise { // ABP 约定 URL:`UpdateTeamMembersBulkAsync` 去掉动词前缀后为 `team-members-bulk`(勿用 `update-...`,否则会被 `PUT …/{id}` 当成 Guid) return api.requestJson({ path: `${PATH}/team-members-bulk`, method: "PUT", body, }); }