fix: harden real backend admin workflows and ui
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user