325 lines
9.6 KiB
TypeScript
325 lines
9.6 KiB
TypeScript
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);
|
||
}
|
||
}
|