import { jsonError, jsonOk, readJsonBody } from "@/lib/server/api"; import { requireAdminSession } from "@/lib/server/auth/current-user"; import { addOrganizationMemberAndCreatePlatformUser, emptyPage, getOrganizationApiConfig, isOrganizationPermissionDenied, isOrganizationRouteNotFound, listOrganizationGroups, listOrganizationMembers, listOrganizationRoles, listOrganizations, modifyOrganizationMemberStatus, removeOrganizationMember, updateOrganizationMember } from "@/lib/server/organization-client"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function GET(request: Request) { try { const session = await requireAdminSession(); const context = { accessToken: session.accessToken }; const config = getOrganizationApiConfig(context.accessToken); const url = new URL(request.url); const pageNum = positiveInteger(url.searchParams.get("pageNum"), 1); const pageSize = positiveInteger(url.searchParams.get("pageSize"), 10, 50); if (!config.configured) { return jsonOk({ configured: false, missing: config.missing, staffConfigured: config.staffConfigured, staffMissing: config.staffMissing, selectedOrganizationId: config.defaultOrganizationId, organizations: [], groups: [], roles: [], members: emptyPage(pageNum, pageSize), warnings: [], memberListAvailable: false, passwordManagementAvailable: config.staffConfigured }); } const warnings: string[] = []; let organizations: Awaited> = []; try { organizations = await listOrganizations(context); } catch (error) { if (!isOrganizationRouteNotFound(error) || !config.defaultOrganizationId) throw error; warnings.push(error.message); organizations = [{ organizationId: config.defaultOrganizationId, organizationName: config.defaultOrganizationId }]; } const selectedOrganizationId = url.searchParams.get("organizationId")?.trim() || config.defaultOrganizationId || organizations[0]?.organizationId || ""; if (!selectedOrganizationId) { return jsonOk({ configured: true, missing: [], staffConfigured: config.staffConfigured, staffMissing: config.staffMissing, selectedOrganizationId: "", organizations, groups: [], roles: [], members: emptyPage(pageNum, pageSize), warnings: uniqueWarnings(warnings), memberListAvailable: false, passwordManagementAvailable: config.staffConfigured }); } const [groupsResult, rolesResult, membersResult] = await Promise.allSettled([ listOrganizationGroups(selectedOrganizationId, context), listOrganizationRoles(selectedOrganizationId, context), listOrganizationMembers({ organizationId: selectedOrganizationId, pageNum, pageSize, memberName: url.searchParams.get("memberName") || undefined, memberStatus: statusFilter(url.searchParams.get("memberStatus")) }, context) ]); const groups = routeFallback(groupsResult, warnings, []); const roles = routeFallback(rolesResult, warnings, []); const members = memberListFallback(membersResult, warnings, emptyPage(pageNum, pageSize)); const memberListAvailable = membersResult.status === "fulfilled"; return jsonOk({ configured: true, missing: [], staffConfigured: config.staffConfigured, staffMissing: config.staffMissing, selectedOrganizationId, organizations, groups, roles, members, warnings: uniqueWarnings(warnings), memberListAvailable, passwordManagementAvailable: config.staffConfigured }); } catch (error) { return jsonError(error, 500, { request, source: "api.admin.accounts", logClientErrors: true }); } } export async function POST(request: Request) { try { const session = await requireAdminSession(); const context = { accessToken: session.accessToken }; const config = getOrganizationApiConfig(context.accessToken); if (!config.staffConfigured) { throw Object.assign(new Error(`企业端用户接口配置不完整:${config.staffMissing.join(", ")}`), { status: 503 }); } const body = await readJsonBody>(request); const memberName = requiredString(body, "memberName", "成员名称"); const memberPhone = requiredString(body, "memberPhone", "手机号"); const user = await addOrganizationMemberAndCreatePlatformUser({ tenantId: tenantIdNumber(config.tenantId), username: optionalString(body.username), phone: optionalString(body.phone) || memberPhone, name: optionalString(body.name) || memberName, nickname: optionalString(body.nickname) || optionalString(body.name) || memberName, initialPassword: optionalString(body.initialPassword), mustChangePassword: booleanValue(body.mustChangePassword, true), memberName, memberPhone, roleId: requiredString(body, "roleId", "角色"), organizationId: requiredString(body, "organizationId", "组织"), groupId: requiredString(body, "groupId", "部门") }, context); return jsonOk({ ok: true, user }); } catch (error) { return jsonError(error, 500, { request, source: "api.admin.accounts", logClientErrors: true }); } } export async function PATCH(request: Request) { try { const session = await requireAdminSession(); const context = { accessToken: session.accessToken }; const body = await readJsonBody>(request); const update = { memberId: requiredString(body, "memberId", "成员 ID"), memberName: optionalString(body.memberName), roleId: optionalString(body.roleId), groupId: optionalString(body.groupId) }; if (!update.memberName && !update.roleId && !update.groupId) { throw badRequest("至少需要修改一个成员字段。"); } await updateOrganizationMember(update, context); return jsonOk({ ok: true }); } catch (error) { return jsonError(error, 500, { request, source: "api.admin.accounts", logClientErrors: true }); } } export async function PUT(request: Request) { try { const session = await requireAdminSession(); const context = { accessToken: session.accessToken }; const body = await readJsonBody>(request); await modifyOrganizationMemberStatus({ memberId: requiredString(body, "memberId", "成员 ID"), memberStatus: memberStatus(requiredString(body, "memberStatus", "成员状态")) }, context); return jsonOk({ ok: true }); } catch (error) { return jsonError(error, 500, { request, source: "api.admin.accounts", logClientErrors: true }); } } export async function DELETE(request: Request) { try { const session = await requireAdminSession(); const context = { accessToken: session.accessToken }; const body = await readJsonBody>(request); await removeOrganizationMember(requiredString(body, "memberId", "成员 ID"), context); return jsonOk({ ok: true }); } catch (error) { return jsonError(error, 500, { request, source: "api.admin.accounts", logClientErrors: true }); } } function requiredString(body: Record, key: string, label: string): string { const value = optionalString(body[key]); if (!value) throw badRequest(`${label}不能为空。`); return value; } function optionalString(value: unknown): string | undefined { return typeof value === "string" && value.trim() ? value.trim() : undefined; } function memberStatus(value: string): "0" | "1" | "2" { if (value === "0" || value === "1" || value === "2") return value; throw badRequest("成员状态只能是 0、1、2。"); } function statusFilter(value: string | null): string | undefined { return value === "0" || value === "1" || value === "2" ? value : undefined; } function routeFallback(result: PromiseSettledResult, warnings: string[], fallback: T): T { if (result.status === "fulfilled") return result.value; if (!isOrganizationRouteNotFound(result.reason)) throw result.reason; warnings.push(result.reason.message); return fallback; } function memberListFallback(result: PromiseSettledResult, warnings: string[], fallback: T): T { if (result.status === "fulfilled") return result.value; if (!isOrganizationRouteNotFound(result.reason) && !isOrganizationPermissionDenied(result.reason)) { throw result.reason; } warnings.push(memberListWarning(result.reason)); return fallback; } function memberListWarning(error: unknown): string { if (isOrganizationPermissionDenied(error)) { return "当前登录 token 缺少上游 hotelStaff 管理员角色,成员列表暂不可用。"; } return error instanceof Error ? error.message : String(error); } function uniqueWarnings(warnings: string[]): string[] { return [...new Set(warnings.filter(Boolean))]; } function tenantIdNumber(value: string): number { const tenantId = Number(value); if (!Number.isFinite(tenantId)) throw badRequest("租户 ID 必须是数字。"); return tenantId; } function booleanValue(value: unknown, fallback: boolean): boolean { if (typeof value === "boolean") return value; if (typeof value === "string") { if (value === "true") return true; if (value === "false") return false; } return fallback; } function positiveInteger(value: string | null, fallback: number, max = 200): number { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < 1) return fallback; return Math.min(parsed, max); } function badRequest(message: string): Error & { status: number } { const error = new Error(message) as Error & { status: number }; error.status = 400; return error; }