fix: harden real backend workflows and channel connections

This commit is contained in:
hectorzhao
2026-07-06 17:54:53 +08:00
parent 8cca361441
commit b5132d7f4e
47 changed files with 2530 additions and 314 deletions
+5
View File
@@ -12,6 +12,11 @@ export class TenantsController {
return this.tenants.list();
}
@Get('management-list')
listManagementRows() {
return this.tenants.listManagementRows();
}
@Get(':id')
get(@Param('id') id: string) {
return this.tenants.get(id);
+27
View File
@@ -22,6 +22,12 @@ function createPrismaMock() {
create: jest.fn().mockResolvedValue({ id: 'cert-1' }),
update: jest.fn().mockResolvedValue({ id: 'cert-1' }),
},
tenantAccount: {
findMany: jest.fn().mockResolvedValue([{ tenantId: 'tenant-1', balanceCents: 12000, smsUnits: 300, creditCents: 5000, status: 'active' }]),
},
smsMessageRecord: {
groupBy: jest.fn().mockResolvedValue([{ tenantId: 'tenant-1', _sum: { amountCents: 350 } }]),
},
};
}
@@ -71,4 +77,25 @@ describe('TenantsService', () => {
await expect(service.delete('missing')).rejects.toBeInstanceOf(NotFoundException);
expect(prisma.tenant.update).not.toHaveBeenCalled();
});
it('lists management rows with real account and today spend fields', async () => {
const prisma = createPrismaMock();
const service = new TenantsService(prisma as never);
await expect(service.listManagementRows()).resolves.toEqual([
expect.objectContaining({
id: 'tenant-1',
name: '测试企业',
account: expect.objectContaining({ balanceCents: 12000, creditCents: 5000 }),
todaySpendCents: 350,
}),
]);
expect(prisma.tenant.findMany).toHaveBeenCalledWith(expect.objectContaining({
where: { status: { not: 'deleted' } },
}));
expect(prisma.smsMessageRecord.groupBy).toHaveBeenCalledWith(expect.objectContaining({
by: ['tenantId'],
_sum: { amountCents: true },
}));
});
});
+41 -2
View File
@@ -4,7 +4,7 @@ import { PrismaService } from '../prisma/prisma.service';
export interface CreateTenantDto {
name: string;
code: string;
code?: string;
status?: string;
creditCode?: string;
province?: string;
@@ -44,6 +44,31 @@ export class TenantsService {
}).then((items) => items.map(withEnterpriseProfile));
}
async listManagementRows() {
const sinceToday = startOfToday();
const [tenants, accounts, todaySpendGroups] = await Promise.all([
this.prisma.tenant.findMany({
where: { status: { not: 'deleted' } },
include: { enterpriseCertifications: { orderBy: { submittedAt: 'desc' }, take: 1 } },
orderBy: { createdAt: 'desc' },
take: 100,
}),
this.prisma.tenantAccount.findMany({ take: 200 }),
this.prisma.smsMessageRecord.groupBy({
by: ['tenantId'],
where: { queuedAt: { gte: sinceToday } },
_sum: { amountCents: true },
}),
]);
const accountsByTenant = new Map(accounts.map((account) => [account.tenantId, account]));
const todaySpendByTenant = new Map(todaySpendGroups.map((group) => [group.tenantId, group._sum.amountCents ?? 0]));
return tenants.map((tenant) => ({
...withEnterpriseProfile(tenant),
account: accountsByTenant.get(tenant.id) ?? null,
todaySpendCents: todaySpendByTenant.get(tenant.id) ?? 0,
}));
}
get(id: string) {
return this.prisma.tenant.findUnique({
where: { id },
@@ -57,8 +82,9 @@ export class TenantsService {
}
async create(data: CreateTenantDto) {
const code = data.code?.trim() || generateTenantCode(data);
const tenant = await this.prisma.tenant.create({
data: { name: data.name, code: data.code, status: data.status ?? 'active' },
data: { name: data.name, code, status: data.status ?? 'active' },
});
await this.upsertProfile(tenant.id, data);
return this.get(tenant.id);
@@ -163,3 +189,16 @@ function withEnterpriseProfile<T extends { enterpriseCertifications?: Array<{ li
} : null,
};
}
function startOfToday() {
const date = new Date();
date.setHours(0, 0, 0, 0);
return date;
}
function generateTenantCode(data: CreateTenantDto) {
const source = data.creditCode?.trim() || data.name.trim();
const normalized = source.replace(/[^\da-zA-Z]/g, '').toLowerCase();
const suffix = Date.now().toString(36).slice(-6);
return `ent-${(normalized || 'tenant').slice(0, 18)}-${suffix}`;
}