持久化用户历史消费泥点投影
Project CI / Repository checks (push) Successful in 59s
Project CI / Frontend tests (push) Successful in 1m58s
Project CI / Native shell tests (push) Successful in 2m28s
Project CI / Backend tests (push) Successful in 3m5s

后台用户详情展示历史花费并保留手动对账
消费落账原子更新投影并提供存量初始化
为手动对账增加独立管理员操作权限
更新SpacetimeDB绑定、文档和测试
This commit is contained in:
2026-07-28 16:20:43 +08:00
parent 66a8b7f597
commit 271d5d7bd3
45 changed files with 2141 additions and 105 deletions
+40 -1
View File
@@ -6,6 +6,7 @@ import {
getAdminFeatureGateConfig,
getAdminUserDetail,
listAdminRechargeOrders,
reconcileAdminUserConsumption,
resolveAdminRechargeRefundManualReview,
updateAdminAccount,
uploadAdminEditorShowcaseCampaignImage,
@@ -16,7 +17,7 @@ afterEach(() => {
vi.unstubAllGlobals();
});
test('后台账号创建和更新携带 owner 会话与 Tab 权限', async () => {
test('后台账号创建和更新同时携带 Tab 与独立操作权限', async () => {
const fetchMock = vi.fn().mockImplementation(() =>
Promise.resolve(
new Response(JSON.stringify({ account: { accountId: 'member-1' } }), {
@@ -31,11 +32,13 @@ test('后台账号创建和更新携带 owner 会话与 Tab 权限', async () =>
displayName: '运营',
password: 'secret123',
tabPermissions: ['dashboard', 'tracking'],
actionPermissions: ['profile-wallet-consumption-reconcile'],
enabled: true,
});
await updateAdminAccount('owner-token', 'member/1', {
displayName: '运营二组',
tabPermissions: ['tracking'],
actionPermissions: [],
enabled: false,
});
@@ -44,6 +47,14 @@ test('后台账号创建和更新携带 owner 会话与 Tab 权限', async () =>
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ Authorization: 'Bearer owner-token' }),
body: JSON.stringify({
username: 'operator',
displayName: '运营',
password: 'secret123',
tabPermissions: ['dashboard', 'tracking'],
actionPermissions: ['profile-wallet-consumption-reconcile'],
enabled: true,
}),
}),
);
expect(fetchMock.mock.calls[1]?.[0]).toBe('/admin/api/accounts/member%2F1');
@@ -53,6 +64,7 @@ test('后台账号创建和更新携带 owner 会话与 Tab 权限', async () =>
body: JSON.stringify({
displayName: '运营二组',
tabPermissions: ['tracking'],
actionPermissions: [],
enabled: false,
}),
}),
@@ -238,6 +250,33 @@ test('用户详情只发送实际提供的用户定位字段', async () => {
});
});
test('历史花费手动对账使用独立管理员写接口', async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(
JSON.stringify({
userId: 'user-1',
historicalConsumedPoints: 1300,
changed: true,
}),
{ status: 200 },
),
);
vi.stubGlobal('fetch', fetchMock);
await reconcileAdminUserConsumption('token-1', { userId: 'user-1' });
expect(String(fetchMock.mock.calls[0]?.[0])).toBe(
'/admin/api/profile/users/reconcile-consumption',
);
expect(fetchMock.mock.calls[0]?.[1]).toEqual(
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ Authorization: 'Bearer token-1' }),
body: JSON.stringify({ userId: 'user-1' }),
}),
);
});
test('退款执行使用独立 execute 管理员路由', async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ outRefundNo: 'refund-1' }), {
+12
View File
@@ -48,6 +48,8 @@ import type {
AdminUpsertProfileRedeemCodeRequest,
AdminUpsertProfileTaskConfigRequest,
AdminUpsertProfileWalletConfigRequest,
AdminUserConsumptionReconcileRequest,
AdminUserConsumptionReconcileResponse,
AdminUserDetailQuery,
AdminUserDetailResponse,
AdminWalletRestrictionRequest,
@@ -577,6 +579,16 @@ export function getAdminUserDetail(token: string, query: AdminUserDetailQuery) {
);
}
export function reconcileAdminUserConsumption(
token: string,
payload: AdminUserConsumptionReconcileRequest,
) {
return request<AdminUserConsumptionReconcileResponse>(
'/admin/api/profile/users/reconcile-consumption',
{ method: 'POST', token, body: payload },
);
}
export function previewAdminRechargeRefund(
token: string,
payload: AdminRechargeRefundPreviewRequest,
+18
View File
@@ -38,6 +38,7 @@ export interface AdminSessionPayload {
roles: string[];
accountRole: 'owner' | 'member';
tabPermissions: string[];
actionPermissions: string[];
issuedAt: string;
expiresAt: string;
}
@@ -48,6 +49,7 @@ export interface AdminAccountPayload {
displayName: string;
accountRole: 'owner' | 'member';
tabPermissions: string[];
actionPermissions: string[];
enabled: boolean;
tokenVersion: number;
createdBy: string;
@@ -65,6 +67,7 @@ export interface AdminCreateAccountRequest {
displayName: string;
password: string;
tabPermissions: string[];
actionPermissions: string[];
enabled: boolean;
}
@@ -76,6 +79,7 @@ export interface AdminUpdateAccountRequest {
displayName: string;
password?: string;
tabPermissions: string[];
actionPermissions: string[];
enabled: boolean;
}
@@ -819,10 +823,24 @@ export interface AdminUserDetailResponse {
bindingStatus: string;
phoneBound: boolean;
wechatBound: boolean;
historicalConsumedPoints: number;
canReconcileConsumption: boolean;
wallet: AdminProfileWalletPayload;
rechargeOrders: AdminRechargeOrderEntryPayload[];
}
export interface AdminUserConsumptionReconcileRequest {
userId: string;
}
export interface AdminUserConsumptionReconcileResponse {
userId: string;
previousHistoricalConsumedPoints?: number | null;
historicalConsumedPoints: number;
changed: boolean;
reconciledAtMicros: number;
}
export interface AdminRechargeRefundPreviewRequest {
orderId: string;
refundAmountCents: number;