Files
invest-mind-store/apps/api/src/modules/broker/broker.service.ts
2026-01-07 17:09:00 +08:00

325 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Injectable,
NotFoundException,
ConflictException,
Logger,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere } from 'typeorm';
import { Broker } from './broker.entity';
import { CreateBrokerDto } from './dto/create-broker.dto';
import { UpdateBrokerDto } from './dto/update-broker.dto';
import { QueryBrokerDto } from './dto/query-broker.dto';
import { BatchCreateBrokerDto } from './dto/batch-create-broker.dto';
import { PaginationInfo } from '@/common/dto/pagination.dto';
import { StorageService } from '../storage/storage.service';
@Injectable()
export class BrokerService {
private readonly logger = new Logger(BrokerService.name);
constructor(
@InjectRepository(Broker)
private readonly brokerRepository: Repository<Broker>,
private readonly storageService: StorageService,
) {}
/**
* 单独创建 broker
*/
async create(createBrokerDto: CreateBrokerDto): Promise<Broker> {
// 检查同一地区的 broker_code 是否已存在
const existingByCode = await this.brokerRepository.findOne({
where: {
brokerCode: createBrokerDto.brokerCode,
region: createBrokerDto.region,
},
});
if (existingByCode) {
throw new ConflictException(
`地区 "${createBrokerDto.region}" 中已存在代码为 "${createBrokerDto.brokerCode}" 的券商`,
);
}
// 检查同一地区的 broker_name 是否已存在
const existingByName = await this.brokerRepository.findOne({
where: {
brokerName: createBrokerDto.brokerName,
region: createBrokerDto.region,
},
});
if (existingByName) {
throw new ConflictException(
`地区 "${createBrokerDto.region}" 中已存在名称为 "${createBrokerDto.brokerName}" 的券商`,
);
}
const broker = this.brokerRepository.create({
...createBrokerDto,
sortOrder: createBrokerDto.sortOrder ?? 0,
isActive: createBrokerDto.isActive ?? true,
});
return this.brokerRepository.save(broker);
}
/**
* 批量创建 broker
*/
async batchCreate(
batchCreateBrokerDto: BatchCreateBrokerDto,
): Promise<Broker[]> {
const brokers = batchCreateBrokerDto.brokers.map((dto) =>
this.brokerRepository.create({
...dto,
sortOrder: dto.sortOrder ?? 0,
isActive: dto.isActive ?? true,
}),
);
// 检查是否有重复的 broker_code + region 组合
const codeRegionPairs = brokers.map((b) => ({
brokerCode: b.brokerCode,
region: b.region,
}));
const existingBrokers = await this.brokerRepository.find({
where: codeRegionPairs.map((pair) => ({
brokerCode: pair.brokerCode,
region: pair.region,
})),
});
if (existingBrokers.length > 0) {
const conflicts = existingBrokers.map(
(b) => `${b.brokerCode} (${b.region})`,
);
throw new ConflictException(
`以下券商已存在:${conflicts.join('、')}`,
);
}
// 检查批量数据内部是否有重复
const uniquePairs = new Set(
codeRegionPairs.map((p) => `${p.brokerCode}-${p.region}`),
);
if (uniquePairs.size !== codeRegionPairs.length) {
throw new ConflictException(
'批量数据中存在重复的券商代码和地区组合',
);
}
return this.brokerRepository.save(brokers);
}
/**
* 查询 broker支持多种查询条件和分页
*/
async findAll(queryDto: QueryBrokerDto): Promise<{
list: Broker[];
pagination: PaginationInfo;
}> {
const where: FindOptionsWhere<Broker> = {};
if (queryDto.brokerId) {
where.brokerId = queryDto.brokerId;
}
if (queryDto.brokerCode) {
where.brokerCode = queryDto.brokerCode;
}
if (queryDto.brokerName) {
where.brokerName = queryDto.brokerName;
}
if (queryDto.region) {
where.region = queryDto.region;
}
if (queryDto.isActive !== undefined) {
where.isActive = queryDto.isActive;
}
// 分页参数
const page = queryDto.page || 1;
const limit = queryDto.limit || 10;
const skip = (page - 1) * limit;
// 排序字段映射
const sortBy = queryDto.sortBy || 'createdAt';
const sortOrder = queryDto.sortOrder || 'DESC';
// 构建排序对象
const order: Record<string, 'ASC' | 'DESC'> = {};
if (sortBy === 'createdAt') {
order.createdAt = sortOrder;
} else if (sortBy === 'sortOrder') {
order.sortOrder = sortOrder;
} else {
order.createdAt = 'DESC';
}
// 添加默认排序
if (sortBy !== 'sortOrder') {
order.sortOrder = 'ASC';
}
order.brokerId = 'ASC';
// 查询总数
const total = await this.brokerRepository.count({ where });
// 查询分页数据
const list = await this.brokerRepository.find({
where,
order,
skip,
take: limit,
});
// 计算总页数
const total_page = Math.ceil(total / limit);
return {
list,
pagination: {
total,
total_page,
page_size: limit,
current_page: page,
},
};
}
/**
* 根据 ID 查询单个 broker
*/
async findOne(id: number): Promise<Broker> {
const broker = await this.brokerRepository.findOne({
where: { brokerId: id },
});
if (!broker) {
throw new NotFoundException(`未找到ID为 ${id} 的券商`);
}
return broker;
}
/**
* 根据条件查询单个 broker返回第一个匹配的
*/
async findOneByCondition(queryDto: QueryBrokerDto): Promise<Broker> {
const where: FindOptionsWhere<Broker> = {};
if (queryDto.brokerId) {
where.brokerId = queryDto.brokerId;
}
if (queryDto.brokerCode) {
where.brokerCode = queryDto.brokerCode;
}
if (queryDto.brokerName) {
where.brokerName = queryDto.brokerName;
}
if (queryDto.region) {
where.region = queryDto.region;
}
if (queryDto.isActive !== undefined) {
where.isActive = queryDto.isActive;
}
const broker = await this.brokerRepository.findOne({ where });
if (!broker) {
throw new NotFoundException('未找到符合给定条件的券商');
}
return broker;
}
/**
* 更新 broker
*/
async update(
id: number,
updateBrokerDto: UpdateBrokerDto,
): Promise<Broker> {
const broker = await this.findOne(id);
// 如果更新 broker_code 或 region检查是否冲突
if ('brokerCode' in updateBrokerDto || 'region' in updateBrokerDto) {
const newCode = updateBrokerDto.brokerCode ?? broker.brokerCode;
const newRegion = updateBrokerDto.region ?? broker.region;
const existing = await this.brokerRepository.findOne({
where: {
brokerCode: newCode,
region: newRegion,
},
});
if (existing && existing.brokerId !== id) {
throw new ConflictException(
`地区 "${newRegion}" 中已存在代码为 "${newCode}" 的券商`,
);
}
}
// 如果更新 broker_name 或 region检查是否冲突
if ('brokerName' in updateBrokerDto || 'region' in updateBrokerDto) {
const newName = updateBrokerDto.brokerName ?? broker.brokerName;
const newRegion = updateBrokerDto.region ?? broker.region;
const existing = await this.brokerRepository.findOne({
where: {
brokerName: newName,
region: newRegion,
},
});
if (existing && existing.brokerId !== id) {
throw new ConflictException(
`地区 "${newRegion}" 中已存在名称为 "${newName}" 的券商`,
);
}
}
Object.assign(broker, updateBrokerDto);
return this.brokerRepository.save(broker);
}
/**
* 删除 broker
* 同时删除券商Logo图片
*/
async remove(id: number): Promise<void> {
const broker = await this.findOne(id);
// 删除券商Logo图片
if (broker.brokerImage) {
try {
const imagePath = this.storageService.extractStoragePath(
broker.brokerImage,
);
if (imagePath) {
await this.storageService.delete(imagePath);
this.logger.log(`已删除券商Logo: ${imagePath}`);
}
} catch (error) {
// 图片删除失败不影响券商删除操作
this.logger.warn(
`删除券商Logo失败: ${broker.brokerImage}`,
error,
);
}
}
await this.brokerRepository.remove(broker);
}
}