Files
qipai/admin/src/api.ts
T

917 lines
31 KiB
TypeScript

import type {
Advertisement,
AdvertisementInput,
AdminIdentity,
AdminSessionPayload,
AdminAccess,
AuditLog,
CleaningSettlement,
CleaningSettlementDetail,
CleaningStatistics,
CleaningTask,
CleaningTemplate,
CleaningTemplateScope,
CleaningExemptPolicy,
CleaningTaskEvent,
CleaningTaskMember,
CleaningTaskSubmission,
DecorationComponent,
DecorationVersion,
FranchiseApplication,
FranchiseApplicationDetail,
FranchiseFollowUpType,
FranchiseStatus,
DeviceTopology,
DeviceType,
BusinessStatistics,
ManagedRoom,
MemberCard,
MemberDetail,
ManagedOrder,
ManagedStore,
ManagedUser,
MediaAsset,
PageResult,
OrderAction,
OrderHistoryItem,
OrderStatus,
PaymentAuthorizationStatus,
PlatformApplication,
ProfitSharingSnapshot,
PayoutStateFilter,
StaffRole,
SettlementStatus,
SystemOverview,
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;
}
const ACCESS_TOKEN_KEY = 'qipai.admin.token';
const REFRESH_TOKEN_KEY = 'qipai.admin.refreshToken';
let refreshInFlight: Promise<AdminSessionPayload> | null = null;
export async function adminPasswordLogin(input: {
tenantCode: string; loginName: string; password: string;
}) {
return publicRequest<AdminSessionPayload>('/auth/login', {
method: 'POST', body: JSON.stringify(input)
});
}
export function getAdminMe(session: ApiSession) {
return request<{ user: AdminIdentity; access: AdminAccess }>(session, '/auth/me');
}
export function logoutAdmin(session: ApiSession) {
return request<{ revoked: boolean }>(session, '/auth/logout', { method: 'POST' });
}
export function setAdminCredential(session: ApiSession, userId: string, input: {
loginName: string; password: string;
}) {
return request<{ userId: string; configured: boolean }>(session,
`/auth/credentials/${encodeURIComponent(userId)}`,
{ method: 'PUT', body: JSON.stringify(input) });
}
export function listManagedStores(session: ApiSession) {
return request<ManagedStore[]>(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<ManagedRoom[]>(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<PageResult<ManagedOrder>>(session, `/orders?${params}`);
}
export function getManagedOrder(session: ApiSession, orderId: string) {
return request<ManagedOrder>(session, `/orders/${orderId}`);
}
export function listManagedOrderHistory(session: ApiSession, orderId: string) {
return request<OrderHistoryItem[]>(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<ProfitSharingSnapshot>(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<ThirdPartyRecords>(session, `/third-party/records${query ? `?${query}` : ''}`);
}
export function getThirdPartySetup(session: ApiSession, provider?: ThirdPartyProvider) {
const query = provider ? `?${new URLSearchParams({ provider })}` : '';
return request<ThirdPartySetup>(session, `/third-party/setup${query}`);
}
export function saveThirdPartyConfig(session: ApiSession, input: {
provider: ThirdPartyProvider; storeId: string | null; mode: ThirdPartyMode;
enabled: boolean; credentialRef: string; settings: Record<string, unknown>;
}) {
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<BusinessStatistics>(session, `/statistics?${params}`);
}
async function request<T>(
session: ApiSession,
path: string,
options: RequestInit = {}
): Promise<T> {
if (!session.token.trim()) {
throw new ApiError('AUTH_TOKEN_REQUIRED', '需要先填入后台访问令牌');
}
let response = await authorizedFetch(path, options, session.token.trim());
if (response.status === 401 && localStorage.getItem(REFRESH_TOKEN_KEY)) {
try {
const refreshed = await refreshAccessToken();
response = await authorizedFetch(path, options, refreshed.accessToken);
} catch {
clearStoredSession();
window.dispatchEvent(new Event('qipai:admin-unauthorized'));
}
}
const payload = await response.json().catch(() => ({}));
if (!response.ok || payload.code !== 0) {
if (response.status === 401) {
clearStoredSession();
window.dispatchEvent(new Event('qipai:admin-unauthorized'));
}
throw new ApiError(
String(payload.code || `HTTP_${response.status}`),
String(payload.message || '请求失败'),
String(payload.traceId || '')
);
}
return payload.data as T;
}
async function publicRequest<T>(path: string, options: RequestInit): Promise<T> {
const headers = new Headers(options.headers);
headers.set('content-type', 'application/json');
headers.set('x-request-id', requestId());
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;
}
async function authorizedFetch(path: string, options: RequestInit, token: string) {
const headers = new Headers(options.headers);
headers.set('authorization', `Bearer ${token}`);
headers.set('x-request-id', requestId());
if (options.body && !headers.has('content-type')) headers.set('content-type', 'application/json');
return fetch(`${API_BASE}${path}`, { ...options, headers });
}
async function refreshAccessToken() {
if (!refreshInFlight) {
const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY) || '';
refreshInFlight = publicRequest<AdminSessionPayload>('/auth/refresh', {
method: 'POST', body: JSON.stringify({ refreshToken })
}).then((payload) => {
storeAdminSession(payload);
window.dispatchEvent(new CustomEvent('qipai:admin-refreshed', { detail: payload }));
return payload;
}).finally(() => { refreshInFlight = null; });
}
return refreshInFlight;
}
export function storeAdminSession(payload: AdminSessionPayload) {
localStorage.setItem(ACCESS_TOKEN_KEY, payload.accessToken);
localStorage.setItem(REFRESH_TOKEN_KEY, payload.refreshToken);
}
export function clearStoredSession() {
localStorage.removeItem(ACCESS_TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
}
function requestId() {
return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
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<PageResult<CleaningTask>>(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<CleaningStatistics>(session, `/cleaning/statistics${query ? `?${query}` : ''}`);
}
export function listCleaningTemplates(session: ApiSession) {
return request<CleaningTemplate[]>(session, '/cleaning/templates');
}
export function upsertCleaningTemplate(
session: ApiSession,
input: {
scopeType: CleaningTemplateScope;
scopeId?: string;
name: string;
requirement: string;
photoRequired: boolean;
minPhotoCount: number;
maxPhotoCount: number;
exemptPolicy: CleaningExemptPolicy;
status: 'ACTIVE' | 'DISABLED';
}
) {
return request<CleaningTemplate>(session, '/cleaning/templates', {
method: 'PUT', body: JSON.stringify(input)
});
}
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<MemberDetail>(session, `/members/${encodeURIComponent(memberId)}`);
}
export function getDeviceTopology(session: ApiSession, storeId: string) {
return request<DeviceTopology>(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<string, unknown>) {
return request<{ commandId?: string; status?: string }>(session, `/device-control/${action}`, { method: 'POST', body: JSON.stringify(input) });
}
export function listPlatformApplications(session: ApiSession) {
return request<PlatformApplication[]>(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<MediaAsset[]>(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<DecorationVersion[]>(
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<Advertisement[]>(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 listFranchiseApplications(session: ApiSession, input: {
page: number; pageSize: number; status?: FranchiseStatus; assigneeUserId?: string; search?: string;
}) {
const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) });
if (input.status) params.set('status', input.status);
if (input.assigneeUserId) params.set('assigneeUserId', input.assigneeUserId);
if (input.search) params.set('search', input.search);
return request<PageResult<FranchiseApplication>>(session, `/franchise-applications?${params}`);
}
export function getFranchiseApplication(session: ApiSession, applicationId: string) {
return request<FranchiseApplicationDetail>(session, `/franchise-applications/${encodeURIComponent(applicationId)}`);
}
export function assignFranchiseApplication(session: ApiSession, applicationId: string, assigneeUserId: string | null) {
return request<{ applicationId: string; assigneeUserId: string | null }>(session,
`/franchise-applications/${encodeURIComponent(applicationId)}/assignee`,
{ method: 'PATCH', body: JSON.stringify({ assigneeUserId }) });
}
export function addFranchiseFollowUp(session: ApiSession, applicationId: string, input: {
followUpType: Exclude<FranchiseFollowUpType, 'ASSIGNMENT' | 'STATUS'>;
note: string; nextFollowUpAt: string | null; status?: FranchiseStatus;
}) {
return request<{ applicationId: string; followUpId: string; status: FranchiseStatus }>(session,
`/franchise-applications/${encodeURIComponent(applicationId)}/follow-ups`,
{ method: 'POST', body: JSON.stringify(input) });
}
export function listAuditLogs(session: ApiSession, input: {
page: number; pageSize: number; action?: string; resourceType?: string; search?: string;
from?: string; to?: string;
}) {
const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) });
if (input.action) params.set('action', input.action);
if (input.resourceType) params.set('resourceType', input.resourceType);
if (input.search) params.set('search', input.search);
if (input.from) params.set('from', input.from);
if (input.to) params.set('to', input.to);
return request<PageResult<AuditLog>>(session, `/audit-logs?${params}`);
}
export function getSystemOverview(session: ApiSession) {
return request<SystemOverview>(session, '/system/overview');
}
export function updateTenantSystemConfig(session: ApiSession, input: {
name: string; timezone: string; status?: 'ACTIVE' | 'DISABLED';
}) {
return request<{ tenantId: string; updated: boolean }>(session, '/system/tenant', {
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<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/assign`, {
method: 'POST',
body: JSON.stringify(input)
});
}
export function completeCleaningTask(session: ApiSession, taskId: string, note?: string) {
return request<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/complete`, {
method: 'POST',
body: JSON.stringify({ note })
});
}
export function rejectCleaningTask(session: ApiSession, taskId: string, reason: string) {
return request<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/reject`, {
method: 'POST',
body: JSON.stringify({ reason })
});
}
export function exemptCleaningTask(session: ApiSession, taskId: string, note?: string) {
return request<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/exempt`, {
method: 'POST',
body: JSON.stringify({ note })
});
}
export function listCleaningTaskMembers(session: ApiSession, taskId: string) {
return request<CleaningTaskMember[]>(
session,
`/cleaning/tasks/${encodeURIComponent(taskId)}/members`
);
}
export function listCleaningTaskEvents(session: ApiSession, taskId: string) {
return request<CleaningTaskEvent[]>(
session,
`/cleaning/tasks/${encodeURIComponent(taskId)}/events`
);
}
export function listCleaningTaskSubmissions(session: ApiSession, taskId: string) {
return request<CleaningTaskSubmission[]>(
session,
`/cleaning/tasks/${encodeURIComponent(taskId)}/submissions`
);
}
export function addCleaningTaskMember(
session: ApiSession,
taskId: string,
input: { cleanerUserId: string; rewardCents: number; note?: string }
) {
return request<CleaningTaskMember[]>(
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<CleaningTaskMember[]>(
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<PageResult<CleaningSettlement>>(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<PageResult<CleaningTask>>(session, `/cleaning/settlement-candidates?${params}`);
}
export function generateCleaningSettlement(
session: ApiSession,
input: { cleanerUserId: string; storeId?: string; note?: string }
) {
return request<CleaningSettlement>(session, '/cleaning/settlements', {
method: 'POST',
body: JSON.stringify(input)
});
}
export function getCleaningSettlementDetail(session: ApiSession, settlementId: string) {
return request<CleaningSettlementDetail>(
session,
`/cleaning/settlements/${encodeURIComponent(settlementId)}`
);
}
export function confirmCleaningSettlement(session: ApiSession, settlementId: string, note?: string) {
return request<CleaningSettlement>(
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<WechatTransferPreflight>(
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<CleaningSettlement>(
session,
`/cleaning/settlements/${encodeURIComponent(settlementId)}/payout-failure`,
{
method: 'POST',
body: JSON.stringify(input)
}
);
}