Просмотр исходного кода

fix(admin): load store member counts on older gateways

彭峰 3 недель назад
Родитель
Сommit
6a63f3573d

+ 1 - 0
projects/xiaoshu-admin/src/app/admin.models.ts

@@ -487,6 +487,7 @@ export interface StoreOption extends Record<string, unknown> {
 
 export interface StorePage extends OperationsPage<StoreSummary> {
   canManage: boolean;
+  activeStudyingMemberCountError?: string;
   summary: {
     total: number;
     active: number;

+ 38 - 0
projects/xiaoshu-admin/src/app/operations.service.spec.ts

@@ -45,6 +45,44 @@ describe('OperationsService', () => {
     expect(functions.operations).toHaveBeenCalledWith('ops/stores/list', { page: 1, pageSize: 20, search: '南昌', filters: { status: 'active' } });
   });
 
+  it('fills missing store counts from filtered member totals on an older gateway', async () => {
+    functions.operations.and.callFake(async (operation, params) => {
+      if (operation === 'ops/stores/list') return { items: [{ userId: 287 }, { userId: 100 }, { userId: 73 }], total: 3 } as any;
+      const storeId = (params?.['filters'] as Record<string, unknown>)['parentUserId'];
+      return { items: [{}], total: storeId === 287 ? 0 : storeId === 100 ? 68 : 100 } as any;
+    });
+    const result = await service.stores(1, 20, '门店', { status: 'active' });
+    expect(result.items.map((store) => store.activeStudyingMemberCount)).toEqual([0, 68, 100]);
+    expect(result.activeStudyingMemberCountError).toBe('');
+    for (const parentUserId of [287, 100, 73]) {
+      expect(functions.operations).toHaveBeenCalledWith('ops/users/list', {
+        page: 1, pageSize: 1, search: '',
+        filters: { parentUserId, learningStatus: 'studying', status: 'active' },
+        sort: { field: 'registeredAt', direction: 'desc' }, refresh: false,
+      });
+    }
+  });
+
+  it('uses the server count including zero without extra member queries', async () => {
+    functions.operations.and.resolveTo({ items: [{ userId: 1, activeStudyingMemberCount: 0 }, { userId: 2, activeStudyingMemberCount: 8 }] });
+    const result = await service.stores(1, 20);
+    expect(result.items.map((store) => store.activeStudyingMemberCount)).toEqual([0, 8]);
+    expect(functions.operations).toHaveBeenCalledTimes(1);
+  });
+
+  it('keeps store data and reports count failures instead of inventing zero', async () => {
+    functions.operations.and.callFake(async (operation, params) => {
+      if (operation === 'ops/stores/list') return { items: [{ userId: 1 }, { userId: 2 }, { userId: 0 }], total: 3 } as any;
+      if ((params?.['filters'] as Record<string, unknown>)['parentUserId'] === 1) return { total: 5 } as any;
+      throw new Error('查询暂不可用');
+    });
+    const result = await service.stores(1, 20);
+    expect(result.items.length).toBe(3);
+    expect(result.items.map((store) => store.activeStudyingMemberCount)).toEqual([5, undefined, undefined]);
+    expect(result.activeStudyingMemberCountError).toContain('加载失败');
+    expect(functions.operations).toHaveBeenCalledTimes(3);
+  });
+
   it('loads one store operating detail with an independent month', async () => {
     functions.operations.and.resolveTo({ store: {}, month: '2026-08', summary: {}, members: [], coaches: [], appointments: [], courses: [] });
     await service.storeDetail('store-object-1', '2026-08', true);

+ 17 - 2
projects/xiaoshu-admin/src/app/operations.service.ts

@@ -61,8 +61,23 @@ export class OperationsService {
     return this.functions.operations<CoachPage>('ops/coaches/list', { page, pageSize, search, filters, refresh });
   }
 
-  stores(page: number, pageSize: number, search = '', filters: Record<string, unknown> = {}): Promise<StorePage> {
-    return this.functions.operations<StorePage>('ops/stores/list', { page, pageSize, search, filters });
+  async stores(page: number, pageSize: number, search = '', filters: Record<string, unknown> = {}): Promise<StorePage> {
+    const result = await this.functions.operations<StorePage>('ops/stores/list', { page, pageSize, search, filters });
+    let countFailed = false;
+    // Older gateways lack this field. Use the existing member query's total, not its page length.
+    const items = await Promise.all(result.items.map(async (store) => {
+      if (store.activeStudyingMemberCount != null) return store;
+      try {
+        if (!(store.userId > 0)) throw new Error('门店编号缺失');
+        const members = await this.users(1, 1, '', { parentUserId: store.userId, learningStatus: 'studying', status: 'active' });
+        if (!Number.isInteger(members.total) || members.total < 0) throw new Error('人数统计无效');
+        return { ...store, activeStudyingMemberCount: members.total };
+      } catch {
+        countFailed = true;
+        return store;
+      }
+    }));
+    return { ...result, items, activeStudyingMemberCountError: countFailed ? '部分门店的在学正常学员人数加载失败,请点击刷新重试。' : '' };
   }
 
   storeDetail(objectId: string, month: string, refresh = false): Promise<StoreDetailData> {

+ 1 - 0
projects/xiaoshu-admin/src/app/pages/operations-stores.component.html

@@ -23,6 +23,7 @@
 
 @if(message()){<p class="notice" role="status">{{message()}}</p>}
 @if(error()&&!drawerOpen()){<p class="notice error" role="alert">{{error()}}</p>}
+@if(page()?.activeStudyingMemberCountError){<p class="notice warning" role="alert">{{page()?.activeStudyingMemberCountError}}</p>}
 @if(page()&&!page()?.liveSourceAvailable&&page()?.liveSourceMessage){<p class="notice warning" role="status">{{page()?.liveSourceMessage}}</p>}
 
 <section class="content-card">