fix: harden real backend admin workflows and ui

This commit is contained in:
hectorzhao
2026-07-03 19:29:56 +08:00
parent dd09d91c1e
commit 8cca361441
71 changed files with 5111 additions and 4439 deletions
+118 -15
View File
@@ -1,16 +1,35 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
export interface CreateTenantDto {
name: string;
code: string;
status?: string;
creditCode?: string;
province?: string;
city?: string;
address?: string;
contactName?: string;
contactIdCard?: string;
contactPhone?: string;
contactEmail?: string;
photoFileObjectId?: string;
}
export interface UpdateTenantDto {
name?: string;
code?: string;
status?: string;
creditCode?: string;
province?: string;
city?: string;
address?: string;
contactName?: string;
contactIdCard?: string;
contactPhone?: string;
contactEmail?: string;
photoFileObjectId?: string;
}
@Injectable()
@@ -19,27 +38,35 @@ export class TenantsService {
list() {
return this.prisma.tenant.findMany({
include: { enterpriseCertifications: { orderBy: { submittedAt: 'desc' }, take: 1 } },
orderBy: { createdAt: 'desc' },
take: 100,
});
}).then((items) => items.map(withEnterpriseProfile));
}
get(id: string) {
return this.prisma.tenant.findUnique({ where: { id } });
}
create(data: CreateTenantDto) {
return this.prisma.tenant.create({
data: {
name: data.name,
code: data.code,
status: data.status ?? 'active',
},
return this.prisma.tenant.findUnique({
where: { id },
include: { enterpriseCertifications: { orderBy: { submittedAt: 'desc' }, take: 1 } },
}).then((tenant) => {
if (!tenant) {
throw new NotFoundException('Tenant not found');
}
return withEnterpriseProfile(tenant);
});
}
update(id: string, data: UpdateTenantDto) {
return this.prisma.tenant.update({
async create(data: CreateTenantDto) {
const tenant = await this.prisma.tenant.create({
data: { name: data.name, code: data.code, status: data.status ?? 'active' },
});
await this.upsertProfile(tenant.id, data);
return this.get(tenant.id);
}
async update(id: string, data: UpdateTenantDto) {
await this.ensureTenant(id);
await this.prisma.tenant.update({
where: { id },
data: {
name: data.name,
@@ -47,9 +74,12 @@ export class TenantsService {
status: data.status,
},
});
await this.upsertProfile(id, data);
return this.get(id);
}
changeStatus(id: string, status: string) {
async changeStatus(id: string, status: string) {
await this.ensureTenant(id);
return this.prisma.tenant.update({
where: { id },
data: { status },
@@ -59,4 +89,77 @@ export class TenantsService {
delete(id: string) {
return this.changeStatus(id, 'deleted');
}
private async ensureTenant(id: string) {
const tenant = await this.prisma.tenant.findUnique({ where: { id } });
if (!tenant) {
throw new NotFoundException('Tenant not found');
}
return tenant;
}
private async upsertProfile(tenantId: string, data: CreateTenantDto | UpdateTenantDto) {
const hasProfileData = ['creditCode', 'province', 'city', 'address', 'contactName', 'contactIdCard', 'contactPhone', 'contactEmail', 'photoFileObjectId']
.some((key) => data[key as keyof (CreateTenantDto | UpdateTenantDto)] !== undefined);
if (!hasProfileData) {
return;
}
const latest = await this.prisma.enterpriseCertification.findFirst({
where: { tenantId },
orderBy: { submittedAt: 'desc' },
});
const materials = cleanObject({
...(latest?.materials && typeof latest.materials === 'object' && !Array.isArray(latest.materials) ? latest.materials as Record<string, unknown> : {}),
province: data.province,
city: data.city,
address: data.address,
contactIdCard: data.contactIdCard,
contactEmail: data.contactEmail,
photoFileObjectId: data.photoFileObjectId,
});
const profileData = {
companyName: data.name ?? latest?.companyName ?? tenantId,
licenseNo: data.creditCode,
contactName: data.contactName,
contactPhone: data.contactPhone,
materials: materials as Prisma.InputJsonValue,
status: 'approved',
};
if (latest) {
await this.prisma.enterpriseCertification.update({
where: { id: latest.id },
data: profileData,
});
return;
}
await this.prisma.enterpriseCertification.create({
data: { tenantId, ...profileData },
});
}
}
function cleanObject(value: Record<string, unknown>) {
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
}
function withEnterpriseProfile<T extends { enterpriseCertifications?: Array<{ licenseNo?: string | null; contactName?: string | null; contactPhone?: string | null; materials?: Prisma.JsonValue | null }> }>(tenant: T) {
const [profile] = tenant.enterpriseCertifications ?? [];
const materials = profile?.materials && typeof profile.materials === 'object' && !Array.isArray(profile.materials)
? profile.materials as Record<string, unknown>
: {};
const { enterpriseCertifications, ...rest } = tenant;
return {
...rest,
enterpriseProfile: profile ? {
creditCode: profile.licenseNo ?? '',
province: String(materials.province ?? ''),
city: String(materials.city ?? ''),
address: String(materials.address ?? ''),
contactName: profile.contactName ?? '',
contactIdCard: String(materials.contactIdCard ?? ''),
contactPhone: profile.contactPhone ?? '',
contactEmail: String(materials.contactEmail ?? ''),
photoFileObjectId: String(materials.photoFileObjectId ?? ''),
} : null,
};
}