feat: sale_quotation
This commit is contained in:
parent
c815e5d561
commit
aaba8dc173
|
@ -64,5 +64,7 @@ export enum ErrorEnum {
|
|||
MATERIALS_IN_OUT_UNIT_PRICE_MUST_ZERO_WHEN_MODIFIED = '1411:只能修改初始单价为0的入库记录。 若有疑问,请联系管理员',
|
||||
|
||||
// SaleQuotation
|
||||
SALE_QUOTATION_COMPONENT_DUPLICATED = '1412:存在名称,价格,规格都相同的配件,请检查是否重复录入'
|
||||
SALE_QUOTATION_COMPONENT_DUPLICATED = '1412:存在名称,价格,规格都相同的配件,请检查是否重复录入',
|
||||
SALE_QUOTATION_TEMPLATE_NAME_DUPLICATE = '1413:模板名已存在'
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ export class SaleQuotationGroupEntity extends CommonEntity {
|
|||
@ApiProperty({ description: '删除状态:0未删除,1已删除' })
|
||||
isDelete: number;
|
||||
|
||||
items:any[];
|
||||
|
||||
@ManyToMany(() => SaleQuotationComponentEntity, component => component.groups)
|
||||
@JoinTable({
|
||||
name: 'sale_quotation_group_component',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { Controller, Get, Query, Res } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
|
||||
import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import { SaleQuotationService } from './sale_quotation.service';
|
||||
import { IdParam } from '~/common/decorators/id-param.decorator';
|
||||
export const permissions = definePermission('sale_quotation:sale_quotation', {
|
||||
EXPORT: 'export'
|
||||
} as const);
|
||||
|
||||
@ApiTags('SaleQuotation - 报价模块')
|
||||
@ApiSecurityAuth()
|
||||
@Controller('sale_quotation')
|
||||
export class SaleQuotationController {
|
||||
constructor(private saleQuotationService: SaleQuotationService) {}
|
||||
|
||||
@Get('export/:id')
|
||||
@ApiOperation({ summary: '导出报价配置明细' })
|
||||
@Perm(permissions.EXPORT)
|
||||
async export(@IdParam() id: number, @Res() res: FastifyReply): Promise<void> {
|
||||
await this.saleQuotationService.export(id, res);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,12 @@ import { SaleQuotationGroupModule } from './group/sale_quotation_group.module';
|
|||
import { SaleQuotationTemplateModule } from './template/sale_quotation_template.module';
|
||||
import { SaleQuotationComponentModule } from './component/sale_quotation_component.module';
|
||||
import { RouterModule } from '@nestjs/core';
|
||||
import { SaleQuotationController } from './sale_quotation.controller';
|
||||
import { SaleQuotationService } from './sale_quotation.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SaleQuotationTemplateEntity } from './template/sale_quotation_template.entity';
|
||||
import { SaleQuotationGroupEntity } from './group/sale_quotation_group.entity';
|
||||
import { SaleQuotationComponentEntity } from './component/sale_quotation_component.entity';
|
||||
const modules = [
|
||||
SaleQuotationComponentModule,
|
||||
SaleQuotationGroupModule,
|
||||
|
@ -11,6 +17,7 @@ const modules = [
|
|||
@Module({
|
||||
imports: [
|
||||
...modules,
|
||||
TypeOrmModule.forFeature([SaleQuotationTemplateEntity, SaleQuotationGroupEntity,SaleQuotationComponentEntity]),
|
||||
RouterModule.register([
|
||||
{
|
||||
path: 'sale_quotation',
|
||||
|
@ -19,7 +26,7 @@ const modules = [
|
|||
}
|
||||
])
|
||||
],
|
||||
controllers: [],
|
||||
providers: []
|
||||
controllers: [SaleQuotationController],
|
||||
providers: [SaleQuotationService]
|
||||
})
|
||||
export class SaleQuotationModule {}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
import { SaleQuotationTemplateEntity } from './template/sale_quotation_template.entity';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import * as ExcelJS from 'exceljs';
|
||||
import { SaleQuotationGroupEntity } from './group/sale_quotation_group.entity';
|
||||
@Injectable()
|
||||
export class SaleQuotationService {
|
||||
constructor(
|
||||
@InjectEntityManager() private entityManager: EntityManager,
|
||||
@InjectRepository(SaleQuotationTemplateEntity)
|
||||
private saleQuotationTemplateRepository: Repository<SaleQuotationTemplateEntity>
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 导出报价配置明细
|
||||
*/
|
||||
async export(templateId: number, res: FastifyReply): Promise<void> {
|
||||
const ROW_HEIGHT = 20;
|
||||
const HEADER_FONT_SIZE = 18;
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
let data = await this.saleQuotationTemplateRepository.findOneBy({ id: templateId });
|
||||
const template: JSON = data.template;
|
||||
const sheet = workbook.addWorksheet(data.name);
|
||||
if (template != null) {
|
||||
sheet.mergeCells('A1:H1');
|
||||
// 设置标题
|
||||
sheet.getCell('A1').value = '电液控部分配置明细';
|
||||
// 设置副标题
|
||||
sheet.mergeCells('A2:F2');
|
||||
sheet.getCell('A2').value = '支架电液控系统配置明细(中间131架+过渡架4架)';
|
||||
sheet.getCell(`G2`).value = '';
|
||||
sheet.getCell(`H2`).value = '';
|
||||
const groups = template['data'] as SaleQuotationGroupEntity[];
|
||||
const headers = [
|
||||
'过渡',
|
||||
'名称',
|
||||
'规格、型号及说明',
|
||||
'单位',
|
||||
'数量',
|
||||
'备 注',
|
||||
'单价',
|
||||
'总价'
|
||||
];
|
||||
|
||||
sheet.addRow(headers);
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
sheet.getCell(`${String.fromCharCode(64 + i)}1`).style.font = { bold: true };
|
||||
sheet.getCell(`${String.fromCharCode(64 + i)}2`).style.font = { bold: true };
|
||||
sheet.getCell(`${String.fromCharCode(64 + i)}3`).style.font = { bold: true };
|
||||
}
|
||||
|
||||
let rowIndex = 3;
|
||||
for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
||||
rowIndex++;
|
||||
const group = groups[groupIndex];
|
||||
sheet.mergeCells(`A${rowIndex}:F${rowIndex}`);
|
||||
sheet.getCell(`A${rowIndex}`).value = group.name;
|
||||
sheet.getCell(`A${rowIndex}`).style.font = { bold: true };
|
||||
sheet.getCell(`G${rowIndex}`).value = '';
|
||||
sheet.getCell(`H${rowIndex}`).value = '';
|
||||
for (let componentIndex = 0; componentIndex < group.items.length; componentIndex++) {
|
||||
const item = group.items[componentIndex];
|
||||
rowIndex++;
|
||||
sheet.addRow([
|
||||
`${componentIndex + 1}`,
|
||||
item.name ?? '',
|
||||
item.componentSpecification ?? '',
|
||||
item.unit ?? '',
|
||||
item.quantity ?? '',
|
||||
item.remark ?? '',
|
||||
item.unitPrice ?? '',
|
||||
item.amount ?? ''
|
||||
]);
|
||||
}
|
||||
}
|
||||
sheet.getCell(`I${rowIndex - 1}`).value = '总价';
|
||||
sheet.getCell(`I${rowIndex - 1}`).style.font = { bold: true };
|
||||
sheet.getCell(`I${rowIndex}`).value = template['totalPrice'];
|
||||
}
|
||||
sheet.eachRow((row, index) => {
|
||||
if (index >= 0) {
|
||||
row.alignment = { vertical: 'middle', horizontal: 'center' };
|
||||
row.height = ROW_HEIGHT;
|
||||
row.eachCell(cell => {
|
||||
cell.border = {
|
||||
top: { style: 'thin' },
|
||||
left: { style: 'thin' },
|
||||
bottom: { style: 'thin' },
|
||||
right: { style: 'thin' }
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
const columnWidthMap = { A: 8, B: 30, C: 25, F: 20 };
|
||||
sheet.columns.forEach((column, index: number) => {
|
||||
column.width = columnWidthMap[String.fromCharCode(65 + index)] ?? 10; // Minimum width of 10
|
||||
});
|
||||
|
||||
//读取buffer进行传输
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
res
|
||||
.header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
.header(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${encodeURIComponent('导出_excel' + new Date().getTime() + '.xls')}"`
|
||||
)
|
||||
.send(buffer);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,6 @@ import { SaleQuotationTemplateEntity } from './sale_quotation_template.entity';
|
|||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([SaleQuotationTemplateEntity]), DatabaseModule],
|
||||
controllers: [SaleQuotationTemplateController],
|
||||
providers: [SaleQuotationTemplateService]
|
||||
providers: [SaleQuotationTemplateService],
|
||||
})
|
||||
export class SaleQuotationTemplateModule {}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
import { EntityManager, Not, Repository } from 'typeorm';
|
||||
import { Pagination } from '~/helper/paginate/pagination';
|
||||
import { paginate } from '~/helper/paginate';
|
||||
import { BusinessException } from '~/common/exceptions/biz.exception';
|
||||
|
@ -33,7 +33,8 @@ export class SaleQuotationTemplateService {
|
|||
const queryBuilder = this.saleQuotationTemplateRepository
|
||||
.createQueryBuilder('saleQuotationTemplate')
|
||||
.where(fieldSearch(fields))
|
||||
.andWhere('saleQuotationTemplate.isDelete = 0').addOrderBy('saleQuotationTemplate.createdAt', 'DESC');
|
||||
.andWhere('saleQuotationTemplate.isDelete = 0')
|
||||
.addOrderBy('saleQuotationTemplate.createdAt', 'DESC');
|
||||
|
||||
return paginate<SaleQuotationTemplateEntity>(queryBuilder, {
|
||||
page,
|
||||
|
@ -45,6 +46,12 @@ export class SaleQuotationTemplateService {
|
|||
* 新增
|
||||
*/
|
||||
async create(dto: SaleQuotationTemplateDto): Promise<void> {
|
||||
const isDuplicated = await this.saleQuotationTemplateRepository.exist({
|
||||
where: { name: dto.name }
|
||||
});
|
||||
if (isDuplicated) {
|
||||
throw new BusinessException(ErrorEnum.SALE_QUOTATION_TEMPLATE_NAME_DUPLICATE);
|
||||
}
|
||||
await this.saleQuotationTemplateRepository.insert(dto);
|
||||
}
|
||||
|
||||
|
@ -53,6 +60,12 @@ export class SaleQuotationTemplateService {
|
|||
*/
|
||||
async update(id: number, data: Partial<SaleQuotationTemplateUpdateDto>): Promise<void> {
|
||||
await this.entityManager.transaction(async manager => {
|
||||
const isDuplicated = await this.saleQuotationTemplateRepository.exist({
|
||||
where: { name: data.name, id: Not(id) }
|
||||
});
|
||||
if (isDuplicated) {
|
||||
throw new BusinessException(ErrorEnum.SALE_QUOTATION_TEMPLATE_NAME_DUPLICATE);
|
||||
}
|
||||
await manager.update(SaleQuotationTemplateEntity, id, {
|
||||
...data
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue