import type { Advertisement, AdvertisementInput, CleaningSettlement, CleaningSettlementDetail, CleaningStatistics, CleaningTask, CleaningTaskEvent, CleaningTaskMember, DecorationComponent, DecorationVersion, DeviceTopology, DeviceType, BusinessStatistics, ManagedRoom, MemberCard, MemberDetail, ManagedOrder, ManagedStore, ManagedUser, MediaAsset, PageResult, OrderAction, OrderHistoryItem, OrderStatus, PaymentAuthorizationStatus, PlatformApplication, ProfitSharingSnapshot, PayoutStateFilter, StaffRole, SettlementStatus, TaskStatus, TransferMode, RoomConfigurationStatus, RoomInput, RoomOperationalStatus, StoreInput, TenantApplicationConfig, ThirdPartyMode, ThirdPartyProvider, ThirdPartyRecords, ThirdPartySetup, UserStatus, WechatTransferPreflight } from './types'; const API_BASE = (import.meta.env.VITE_ADMIN_API_BASE_URL || '/admin-api').replace(/\/$/, ''); export class ApiError extends Error { constructor( public readonly code: string, message = code, public readonly traceId = '' ) { super(message); } } export interface ApiSession { token: string; } export function listManagedStores(session: ApiSession) { return request(session, '/stores'); } export function createManagedStore(session: ApiSession, input: StoreInput) { return request<{ storeId: string }>(session, '/stores', { method: 'POST', body: JSON.stringify(input) }); } export function updateManagedStore(session: ApiSession, storeId: string, input: StoreInput) { return request<{ storeId: string }>(session, `/stores/${storeId}`, { method: 'PUT', body: JSON.stringify(input) }); } export function archiveManagedStore(session: ApiSession, storeId: string) { return request<{ storeId: string; archived: boolean }>(session, `/stores/${storeId}`, { method: 'DELETE' }); } export function listManagedRooms(session: ApiSession, storeId: string) { return request(session, `/stores/${storeId}/rooms`); } export function createManagedRoom(session: ApiSession, input: RoomInput) { return request<{ roomId: string }>(session, '/rooms', { method: 'POST', body: JSON.stringify(input) }); } export function updateManagedRoom(session: ApiSession, roomId: string, input: RoomInput) { return request<{ roomId: string }>(session, `/rooms/${roomId}`, { method: 'PUT', body: JSON.stringify(input) }); } export function updateManagedRoomStatus( session: ApiSession, roomId: string, input: { storeId: string; configurationStatus?: RoomConfigurationStatus; operationalStatus?: RoomOperationalStatus; reason: string; } ) { return request<{ roomId: string }>(session, `/rooms/${roomId}/status`, { method: 'PATCH', body: JSON.stringify(input) }); } export function archiveManagedRoom(session: ApiSession, roomId: string, storeId: string) { return request<{ roomId: string; archived: boolean }>( session, `/rooms/${roomId}?${new URLSearchParams({ storeId })}`, { method: 'DELETE' } ); } export function addManagedRoomDisabledPeriod( session: ApiSession, roomId: string, input: { storeId: string; startsAt: string; endsAt: string; reason: string } ) { return request<{ disabledPeriodId: string }>(session, `/rooms/${roomId}/disabled-periods`, { method: 'POST', body: JSON.stringify(input) }); } export function listManagedOrders( session: ApiSession, input: { page: number; pageSize: number; status?: OrderStatus; storeId?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); if (input.storeId) params.set('storeId', input.storeId); return request>(session, `/orders?${params}`); } export function getManagedOrder(session: ApiSession, orderId: string) { return request(session, `/orders/${orderId}`); } export function listManagedOrderHistory(session: ApiSession, orderId: string) { return request(session, `/orders/${orderId}/history`); } export function executeManagedOrderAction( session: ApiSession, orderId: string, action: OrderAction, reason: string ) { return request<{ orderId: string; status: OrderStatus }>(session, `/orders/${orderId}/actions`, { method: 'POST', body: JSON.stringify({ action, reason }) }); } export function addManagedOrderNote(session: ApiSession, orderId: string, note: string) { return request<{ orderId: string }>(session, `/orders/${orderId}/note`, { method: 'POST', body: JSON.stringify({ note }) }); } export function adjustManagedOrderTime( session: ApiSession, orderId: string, input: { startAt?: string; endAt?: string; reason: string } ) { return request<{ orderId: string }>(session, `/orders/${orderId}/adjust-time`, { method: 'POST', body: JSON.stringify(input) }); } export function renewManagedOrder( session: ApiSession, orderId: string, input: { endAt: string; pricingPolicy: 'CURRENT' | 'LOCKED'; reason: string } ) { return request<{ orderId: string }>(session, `/orders/${orderId}/renew`, { method: 'POST', body: JSON.stringify(input) }); } export function changeManagedOrderRoom( session: ApiSession, orderId: string, input: { roomId: string; reason: string } ) { return request<{ orderId: string }>(session, `/orders/${orderId}/change-room`, { method: 'POST', body: JSON.stringify(input) }); } export function getProfitSharingSnapshot(session: ApiSession, storeId?: string) { const query = storeId ? `?${new URLSearchParams({ storeId })}` : ''; return request(session, `/pay/profit-shares${query}`); } export function saveCollectionAccount(session: ApiSession, input: { storeId: string | null; merchantId: string; credentialRef: string; authorizationStatus: PaymentAuthorizationStatus; profitSharingEnabled: boolean; enabled: boolean; }) { return request<{ collectionAccountId: string }>(session, '/pay/collection-account', { method: 'PUT', body: JSON.stringify(input) }); } export function saveProfitShareReceiver(session: ApiSession, input: { collectionAccountId: string; receiverType: 'MERCHANT_ID' | 'PERSONAL_OPENID'; receiverAccount: string; receiverCredentialRef: string; relationType: string; name: string; authorizationStatus: PaymentAuthorizationStatus; enabled: boolean; }) { return request<{ receiverId: string }>(session, '/pay/profit-share-receiver', { method: 'PUT', body: JSON.stringify(input) }); } export function saveProfitSharePolicy(session: ApiSession, input: { collectionAccountId: string; storeId: string | null; receiverId: string; percentageBps: number; enabled: boolean; }) { return request<{ policyId: string }>(session, '/pay/profit-share-policy', { method: 'PUT', body: JSON.stringify(input) }); } export function executeProfitSharing( session: ApiSession, input: { paymentId: string; clientRequestId: string; mode: 'API' | 'MOCK' } ) { return request<{ shares: unknown[]; idempotent: boolean }>(session, '/pay/profit-shares', { method: 'POST', body: JSON.stringify(input) }); } export function createWechatRefund(session: ApiSession, input: { paymentId: string; amountCents: number; reason: string; clientRequestId: string; }) { return request<{ refundId: string; refundNo: string; status: string; refundableCents: number }>( session, '/pay/refund', { method: 'POST', body: JSON.stringify(input) } ); } export function requestWechatReconciliation(session: ApiSession, input: { storeId: string; billDate: string; billType: 'ALL' | 'SUCCESS' | 'REFUND'; }) { return request<{ id: string; status: string; downloadUrl: string; idempotent: boolean }>( session, '/pay/reconciliation', { method: 'POST', body: JSON.stringify(input) } ); } export function listThirdPartyRecords(session: ApiSession, input: { provider?: ThirdPartyProvider; status?: string; storeId?: string; }) { const params = new URLSearchParams(); if (input.provider) params.set('provider', input.provider); if (input.status) params.set('status', input.status); if (input.storeId) params.set('storeId', input.storeId); const query = params.toString(); return request(session, `/third-party/records${query ? `?${query}` : ''}`); } export function getThirdPartySetup(session: ApiSession, provider?: ThirdPartyProvider) { const query = provider ? `?${new URLSearchParams({ provider })}` : ''; return request(session, `/third-party/setup${query}`); } export function saveThirdPartyConfig(session: ApiSession, input: { provider: ThirdPartyProvider; storeId: string | null; mode: ThirdPartyMode; enabled: boolean; credentialRef: string; settings: Record; }) { return request<{ configId: string; created: boolean }>(session, '/third-party/config', { method: 'PUT', body: JSON.stringify(input) }); } export function saveThirdPartyMapping(session: ApiSession, input: { provider: ThirdPartyProvider; resourceType: 'STORE' | 'ROOM'; externalRef: string; localResourceId: string; }) { return request<{ mapped: boolean }>(session, '/third-party/mappings', { method: 'PUT', body: JSON.stringify(input) }); } export function redeemGroupVoucher(session: ApiSession, input: { provider: ThirdPartyProvider; voucherCode: string; orderId: string; clientRequestId: string; }) { return request<{ redemptionId: string; status: string; voucherMasked: string }>( session, '/group-vouchers/redeem', { method: 'POST', body: JSON.stringify(input) } ); } export function redeemGroupVoucherManually(session: ApiSession, input: { provider: ThirdPartyProvider; voucherCode: string; orderId: string; amountCents: number; note: string; clientRequestId: string; }) { return request<{ redemptionId: string; status: string; voucherMasked: string }>( session, '/group-vouchers/redeem-manual', { method: 'POST', body: JSON.stringify(input) } ); } export function getBusinessStatistics( session: ApiSession, input: { storeId: string; from: string; to: string } ) { const params = new URLSearchParams(input); return request(session, `/statistics?${params}`); } async function request( session: ApiSession, path: string, options: RequestInit = {} ): Promise { if (!session.token.trim()) { throw new ApiError('AUTH_TOKEN_REQUIRED', '需要先填入后台访问令牌'); } const headers = new Headers(options.headers); headers.set('authorization', `Bearer ${session.token.trim()}`); if (options.body && !headers.has('content-type')) { headers.set('content-type', 'application/json'); } const response = await fetch(`${API_BASE}${path}`, { ...options, headers }); const payload = await response.json().catch(() => ({})); if (!response.ok || payload.code !== 0) { throw new ApiError( String(payload.code || `HTTP_${response.status}`), String(payload.message || '请求失败'), String(payload.traceId || '') ); } return payload.data as T; } export function listCleaningTasks( session: ApiSession, input: { page: number; pageSize: number; status?: TaskStatus; storeId?: string; cleanerUserId?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/tasks?${params}`); } export function getCleaningStatistics( session: ApiSession, input: { from?: string; to?: string; storeId?: string; cleanerUserId?: string } = {} ) { const params = new URLSearchParams(); if (input.from) params.set('from', input.from); if (input.to) params.set('to', input.to); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); const query = params.toString(); return request(session, `/cleaning/statistics${query ? `?${query}` : ''}`); } export function listStaffUsers( session: ApiSession, input: { page: number; pageSize: number; role?: StaffRole; status?: UserStatus; search?: string; } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize), userType: 'STAFF' }); if (input.role) params.set('role', input.role); if (input.status) params.set('status', input.status); if (input.search) params.set('search', input.search); return request<{ items: ManagedUser[]; total: number }>(session, `/users?${params}`); } export function listMembers( session: ApiSession, input: { page: number; pageSize: number; status?: UserStatus; search?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); if (input.search) params.set('search', input.search); return request<{ items: MemberCard[]; total: number }>(session, `/members?${params}`); } export function getMember(session: ApiSession, memberId: string) { return request(session, `/members/${encodeURIComponent(memberId)}`); } export function getDeviceTopology(session: ApiSession, storeId: string) { return request(session, `/device-topology?${new URLSearchParams({ storeId })}`); } export function createDeviceAsset(session: ApiSession, input: { storeId: string; roomId: string | null; deviceId: string; imei: string; iccid: string | null; deviceType: DeviceType; model: string; firmwareVersion: string; signalStrength: number | null; capabilities: string[]; }) { return request<{ assetId: string }>(session, '/devices', { method: 'POST', body: JSON.stringify(input) }); } export function bindDeviceChannel(session: ApiSession, input: { assetId: string; storeId: string; roomId: string; channelCode: string; purpose: string; }) { return request<{ assetId: string; targetKey: string }>(session, '/device-channels', { method: 'POST', body: JSON.stringify(input) }); } export function bindSubDevice(session: ApiSession, input: { parentAssetId: string; childAssetId: string; storeId: string; roomId: string; subId: string; subtype: string; }) { return request<{ childAssetId: string; parentAssetId: string }>(session, '/device-links', { method: 'POST', body: JSON.stringify(input) }); } export function addDeviceMaintenance(session: ApiSession, assetId: string, input: { storeId: string; roomId: string | null; recordType: 'INSPECTION' | 'REPAIR' | 'REPLACEMENT'; status: 'OPEN' | 'RESOLVED'; description: string; }) { return request<{ maintenanceId: string }>(session, `/devices/${encodeURIComponent(assetId)}/maintenance`, { method: 'POST', body: JSON.stringify(input) }); } export function controlDevice(session: ApiSession, action: 'door' | 'power' | 'socket/switch', input: Record) { return request<{ commandId?: string; status?: string }>(session, `/device-control/${action}`, { method: 'POST', body: JSON.stringify(input) }); } export function listPlatformApplications(session: ApiSession) { return request(session, '/platform-apps'); } export function updatePlatformApplicationConfig(session: ApiSession, platformAppId: string, input: TenantApplicationConfig) { return request<{ platformAppId: string; updated: boolean }>(session, `/platform-apps/${encodeURIComponent(platformAppId)}/config`, { method: 'PUT', body: JSON.stringify(input) }); } export function bindPlatformApplication(session: ApiSession, input: { appId: string; appName: string; appStatus: 'ACTIVE' | 'DISABLED'; config: TenantApplicationConfig; }) { return request<{ platformAppId: string; bound: boolean }>(session, '/platform-apps/bind', { method: 'POST', body: JSON.stringify(input) }); } export function listMediaAssets(session: ApiSession, storeId?: string) { const query = storeId ? `?${new URLSearchParams({ storeId })}` : ''; return request(session, `/media/images${query}`); } export async function uploadMediaImage(session: ApiSession, file: File, storeId?: string) { const headers = new Headers({ 'content-type': 'application/octet-stream', 'x-image-content-type': file.type, 'x-file-name': file.name.replace(/[^\x20-\x7e]/g, '_') || 'image' }); if (storeId) headers.set('x-store-id', storeId); return request<{ assetId: string; url: string }>(session, '/media/images', { method: 'POST', headers, body: await file.arrayBuffer() }); } export function listDecorations(session: ApiSession, storeId: string) { return request( session, `/decorations?${new URLSearchParams({ storeId })}` ); } export function saveDecoration(session: ApiSession, input: { storeId: string; templateCode: string; schemaVersion: number; content: { components: DecorationComponent[] }; }) { return request<{ decorationId: string; version: number }>(session, '/decorations', { method: 'POST', body: JSON.stringify(input) }); } export function publishDecoration(session: ApiSession, decorationId: string, storeId: string) { return request<{ decorationId: string; published: boolean }>( session, `/decorations/${encodeURIComponent(decorationId)}/publish?${new URLSearchParams({ storeId })}`, { method: 'POST' } ); } export function listAdvertisements(session: ApiSession) { return request(session, '/advertisements'); } export function saveAdvertisement(session: ApiSession, input: AdvertisementInput) { return request<{ advertisementId: string }>(session, '/advertisements', { method: 'POST', body: JSON.stringify(input) }); } export function updateAdvertisement( session: ApiSession, advertisementId: string, input: AdvertisementInput ) { return request<{ advertisementId: string }>( session, `/advertisements/${encodeURIComponent(advertisementId)}`, { method: 'PUT', body: JSON.stringify(input) } ); } export function createStaffUser( session: ApiSession, input: { nickname: string; phone: string; note?: string; roles: StaffRole[]; storeIds: string[] } ) { return request<{ userId: string }>(session, '/staff', { method: 'POST', body: JSON.stringify(input) }); } export function updateStaffUser( session: ApiSession, userId: string, input: Partial<{ nickname: string; phone: string; note: string; status: UserStatus; roles: StaffRole[]; storeIds: string[]; }> ) { return request<{ userId: string }>(session, `/users/${encodeURIComponent(userId)}`, { method: 'PATCH', body: JSON.stringify(input) }); } export function resetStaffSessions(session: ApiSession, userId: string) { return request<{ userId: string; revokedSessions: number }>( session, `/users/${encodeURIComponent(userId)}/reset-sessions`, { method: 'POST' } ); } export function assignCleaningTask( session: ApiSession, taskId: string, input: { cleanerUserId: string; note?: string } ) { return request(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/assign`, { method: 'POST', body: JSON.stringify(input) }); } export function completeCleaningTask(session: ApiSession, taskId: string, note?: string) { return request(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/complete`, { method: 'POST', body: JSON.stringify({ note }) }); } export function rejectCleaningTask(session: ApiSession, taskId: string, reason: string) { return request(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/reject`, { method: 'POST', body: JSON.stringify({ reason }) }); } export function exemptCleaningTask(session: ApiSession, taskId: string, note?: string) { return request(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/exempt`, { method: 'POST', body: JSON.stringify({ note }) }); } export function listCleaningTaskMembers(session: ApiSession, taskId: string) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/members` ); } export function listCleaningTaskEvents(session: ApiSession, taskId: string) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/events` ); } export function addCleaningTaskMember( session: ApiSession, taskId: string, input: { cleanerUserId: string; rewardCents: number; note?: string } ) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/members`, { method: 'POST', body: JSON.stringify(input) } ); } export function removeCleaningTaskMember( session: ApiSession, taskId: string, cleanerUserId: string, note?: string ) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/members/${encodeURIComponent(cleanerUserId)}/remove`, { method: 'POST', body: JSON.stringify({ note }) } ); } export function reclaimCleaningTimeouts( session: ApiSession, input: { olderThanMinutes: number; limit: number } ) { return request<{ reclaimed: number; taskIds: string[] }>(session, '/cleaning/reclaim-timeouts', { method: 'POST', body: JSON.stringify(input) }); } export function listCleaningSettlements( session: ApiSession, input: { page: number; pageSize: number; status?: SettlementStatus; payoutState?: PayoutStateFilter; storeId?: string; cleanerUserId?: string; } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); if (input.payoutState) params.set('payoutState', input.payoutState); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/settlements?${params}`); } export function listCleaningSettlementCandidates( session: ApiSession, input: { page: number; pageSize: number; storeId?: string; cleanerUserId?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/settlement-candidates?${params}`); } export function generateCleaningSettlement( session: ApiSession, input: { cleanerUserId: string; storeId?: string; note?: string } ) { return request(session, '/cleaning/settlements', { method: 'POST', body: JSON.stringify(input) }); } export function getCleaningSettlementDetail(session: ApiSession, settlementId: string) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}` ); } export function confirmCleaningSettlement(session: ApiSession, settlementId: string, note?: string) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/confirm`, { method: 'POST', body: JSON.stringify({ note }) } ); } export function executeWechatTransfer( session: ApiSession, settlementId: string, input: { mode: TransferMode; note?: string } ) { return request<{ settlement: CleaningSettlement; transferState: string; idempotent: boolean }>( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/wechat-transfer`, { method: 'POST', body: JSON.stringify(input) } ); } export function preflightWechatTransfer(session: ApiSession, settlementId: string) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/wechat-transfer/preflight` ); } export function syncWechatTransfer(session: ApiSession, settlementId: string, note?: string) { return request<{ settlement: CleaningSettlement; transferState: string; idempotent: boolean }>( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/wechat-transfer/sync`, { method: 'POST', body: JSON.stringify({ note }) } ); } export function recordPayoutFailure( session: ApiSession, settlementId: string, input: { error: string; payoutChannel?: string; payoutReference?: string; note?: string } ) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/payout-failure`, { method: 'POST', body: JSON.stringify(input) } ); }