|
|
@@ -1,9 +1,219 @@
|
|
|
import { GenericCrudService } from '@/server/utils/generic-crud.service';
|
|
|
-import { DataSource } from 'typeorm';
|
|
|
+import { DataSource, SelectQueryBuilder } from 'typeorm';
|
|
|
import { Client } from './client.entity';
|
|
|
|
|
|
export class ClientService extends GenericCrudService<Client> {
|
|
|
constructor(dataSource: DataSource) {
|
|
|
super(dataSource, Client);
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重写getList方法,实现自定义客户列表查询
|
|
|
+ * @param page 页码
|
|
|
+ * @param pageSize 每页数量
|
|
|
+ * @param keyword 关键词搜索
|
|
|
+ * @param searchFields 搜索字段
|
|
|
+ * @param where 过滤条件
|
|
|
+ * @param relations 关联查询
|
|
|
+ * @param order 排序
|
|
|
+ */
|
|
|
+ async getList(
|
|
|
+ page: number = 1,
|
|
|
+ pageSize: number = 10,
|
|
|
+ keyword?: string,
|
|
|
+ searchFields: string[] = ['companyName', 'contactPerson', 'mobile'],
|
|
|
+ where: Partial<Client> = {},
|
|
|
+ relations: string[] = ['responsibleUser', 'salesPerson', 'operator'],
|
|
|
+ order: { [P in keyof Client]?: 'ASC' | 'DESC' } = { createdAt: 'DESC' }
|
|
|
+ ): Promise<[Client[], number]> {
|
|
|
+ const queryBuilder = this.repository.createQueryBuilder('client');
|
|
|
+
|
|
|
+ // 预加载关联数据
|
|
|
+ if (relations && relations.length > 0) {
|
|
|
+ relations.forEach(relation => {
|
|
|
+ queryBuilder.leftJoinAndSelect(`client.${relation}`, relation);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 过滤已删除的数据
|
|
|
+ queryBuilder.where('client.isDeleted = :isDeleted', { isDeleted: 0 });
|
|
|
+
|
|
|
+ // 应用where条件
|
|
|
+ Object.keys(where).forEach(key => {
|
|
|
+ const value = where[key as keyof Client];
|
|
|
+ if (value !== undefined && value !== null) {
|
|
|
+ queryBuilder.andWhere(`client.${key} = :${key}`, { [key]: value });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 关键词搜索
|
|
|
+ if (keyword && searchFields.length > 0) {
|
|
|
+ const conditions = searchFields.map(field =>
|
|
|
+ `client.${field} LIKE :keyword`
|
|
|
+ ).join(' OR ');
|
|
|
+ queryBuilder.andWhere(`(${conditions})`, { keyword: `%${keyword}%` });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理特殊过滤条件
|
|
|
+ this.applyAdvancedFilters(queryBuilder, where);
|
|
|
+
|
|
|
+ // 应用排序
|
|
|
+ Object.keys(order).forEach(key => {
|
|
|
+ const direction = order[key as keyof Client];
|
|
|
+ if (direction) {
|
|
|
+ queryBuilder.orderBy(`client.${key}`, direction);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 分页
|
|
|
+ const skip = (page - 1) * pageSize;
|
|
|
+ queryBuilder.skip(skip).take(pageSize);
|
|
|
+
|
|
|
+ const [data, total] = await queryBuilder.getManyAndCount();
|
|
|
+ return [data, total];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 应用高级过滤条件
|
|
|
+ */
|
|
|
+ private applyAdvancedFilters(
|
|
|
+ queryBuilder: SelectQueryBuilder<Client>,
|
|
|
+ where: Partial<Client & { dateRange?: { start?: Date; end?: Date } }>
|
|
|
+ ): void {
|
|
|
+ // 日期范围过滤
|
|
|
+ if (where['dateRange'] && typeof where['dateRange'] === 'object') {
|
|
|
+ const dateRange = where['dateRange'] as { start?: Date; end?: Date };
|
|
|
+ if (dateRange.start) {
|
|
|
+ queryBuilder.andWhere('client.createdAt >= :startDate', {
|
|
|
+ startDate: dateRange.start
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (dateRange.end) {
|
|
|
+ queryBuilder.andWhere('client.createdAt <= :endDate', {
|
|
|
+ endDate: dateRange.end
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下次联系时间过滤
|
|
|
+ if (where['nextContactTime']) {
|
|
|
+ const nextContactTime = where['nextContactTime'];
|
|
|
+ if (nextContactTime instanceof Date) {
|
|
|
+ const startOfDay = new Date(nextContactTime);
|
|
|
+ startOfDay.setHours(0, 0, 0, 0);
|
|
|
+
|
|
|
+ const endOfDay = new Date(nextContactTime);
|
|
|
+ endOfDay.setHours(23, 59, 59, 999);
|
|
|
+
|
|
|
+ queryBuilder.andWhere(
|
|
|
+ 'client.nextContactTime BETWEEN :startOfDay AND :endOfDay',
|
|
|
+ { startOfDay, endOfDay }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 合作起始日期过滤
|
|
|
+ if (where['startDate']) {
|
|
|
+ const startDate = where['startDate'];
|
|
|
+ if (startDate instanceof Date) {
|
|
|
+ queryBuilder.andWhere('client.startDate >= :startDate', { startDate });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 负责人过滤
|
|
|
+ if (where['responsibleUserId']) {
|
|
|
+ queryBuilder.andWhere('client.responsibleUserId = :responsibleUserId', {
|
|
|
+ responsibleUserId: where['responsibleUserId']
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 业务员过滤
|
|
|
+ if (where['salesPersonId']) {
|
|
|
+ queryBuilder.andWhere('client.salesPersonId = :salesPersonId', {
|
|
|
+ salesPersonId: where['salesPersonId']
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 操作员过滤
|
|
|
+ if (where['operatorId']) {
|
|
|
+ queryBuilder.andWhere('client.operatorId = :operatorId', {
|
|
|
+ operatorId: where['operatorId']
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 客户类型过滤
|
|
|
+ if (where['customerType']) {
|
|
|
+ queryBuilder.andWhere('client.customerType = :customerType', {
|
|
|
+ customerType: where['customerType']
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 行业过滤
|
|
|
+ if (where['industry']) {
|
|
|
+ queryBuilder.andWhere('client.industry = :industry', {
|
|
|
+ industry: where['industry']
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 客户来源过滤
|
|
|
+ if (where['source']) {
|
|
|
+ queryBuilder.andWhere('client.source = :source', {
|
|
|
+ source: where['source']
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取客户统计信息
|
|
|
+ */
|
|
|
+ async getClientStats(): Promise<{
|
|
|
+ total: number;
|
|
|
+ pendingAudit: number;
|
|
|
+ active: number;
|
|
|
+ inactive: number;
|
|
|
+ }> {
|
|
|
+ const queryBuilder = this.repository.createQueryBuilder('client');
|
|
|
+
|
|
|
+ const total = await queryBuilder
|
|
|
+ .where('client.isDeleted = 0')
|
|
|
+ .getCount();
|
|
|
+
|
|
|
+ const pendingAudit = await queryBuilder
|
|
|
+ .where('client.isDeleted = 0')
|
|
|
+ .andWhere('client.auditStatus = 0')
|
|
|
+ .getCount();
|
|
|
+
|
|
|
+ const active = await queryBuilder
|
|
|
+ .where('client.isDeleted = 0')
|
|
|
+ .andWhere('client.status = 1')
|
|
|
+ .getCount();
|
|
|
+
|
|
|
+ const inactive = await queryBuilder
|
|
|
+ .where('client.isDeleted = 0')
|
|
|
+ .andWhere('client.status = 0')
|
|
|
+ .getCount();
|
|
|
+
|
|
|
+ return { total, pendingAudit, active, inactive };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取即将联系的客户列表
|
|
|
+ */
|
|
|
+ async getUpcomingClients(days: number = 7): Promise<Client[]> {
|
|
|
+ const queryBuilder = this.repository.createQueryBuilder('client');
|
|
|
+
|
|
|
+ const today = new Date();
|
|
|
+ const futureDate = new Date();
|
|
|
+ futureDate.setDate(today.getDate() + days);
|
|
|
+
|
|
|
+ return queryBuilder
|
|
|
+ .leftJoinAndSelect('client.responsibleUser', 'responsibleUser')
|
|
|
+ .where('client.isDeleted = 0')
|
|
|
+ .andWhere('client.nextContactTime BETWEEN :today AND :futureDate', {
|
|
|
+ today,
|
|
|
+ futureDate
|
|
|
+ })
|
|
|
+ .orderBy('client.nextContactTime', 'ASC')
|
|
|
+ .getMany();
|
|
|
+ }
|
|
|
}
|