feat: sale_quotation

This commit is contained in:
louis 2024-04-15 13:50:13 +08:00
parent c815e5d561
commit aaba8dc173
7 changed files with 165 additions and 6 deletions

View File

@ -64,5 +64,7 @@ export enum ErrorEnum {
MATERIALS_IN_OUT_UNIT_PRICE_MUST_ZERO_WHEN_MODIFIED = '1411:只能修改初始单价为0的入库记录。 若有疑问,请联系管理员', MATERIALS_IN_OUT_UNIT_PRICE_MUST_ZERO_WHEN_MODIFIED = '1411:只能修改初始单价为0的入库记录。 若有疑问,请联系管理员',
// SaleQuotation // SaleQuotation
SALE_QUOTATION_COMPONENT_DUPLICATED = '1412:存在名称,价格,规格都相同的配件,请检查是否重复录入' SALE_QUOTATION_COMPONENT_DUPLICATED = '1412:存在名称,价格,规格都相同的配件,请检查是否重复录入',
SALE_QUOTATION_TEMPLATE_NAME_DUPLICATE = '1413:模板名已存在'
} }

View File

@ -23,6 +23,8 @@ export class SaleQuotationGroupEntity extends CommonEntity {
@ApiProperty({ description: '删除状态0未删除1已删除' }) @ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number; isDelete: number;
items:any[];
@ManyToMany(() => SaleQuotationComponentEntity, component => component.groups) @ManyToMany(() => SaleQuotationComponentEntity, component => component.groups)
@JoinTable({ @JoinTable({
name: 'sale_quotation_group_component', name: 'sale_quotation_group_component',

View File

@ -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);
}
}

View File

@ -3,6 +3,12 @@ import { SaleQuotationGroupModule } from './group/sale_quotation_group.module';
import { SaleQuotationTemplateModule } from './template/sale_quotation_template.module'; import { SaleQuotationTemplateModule } from './template/sale_quotation_template.module';
import { SaleQuotationComponentModule } from './component/sale_quotation_component.module'; import { SaleQuotationComponentModule } from './component/sale_quotation_component.module';
import { RouterModule } from '@nestjs/core'; 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 = [ const modules = [
SaleQuotationComponentModule, SaleQuotationComponentModule,
SaleQuotationGroupModule, SaleQuotationGroupModule,
@ -11,6 +17,7 @@ const modules = [
@Module({ @Module({
imports: [ imports: [
...modules, ...modules,
TypeOrmModule.forFeature([SaleQuotationTemplateEntity, SaleQuotationGroupEntity,SaleQuotationComponentEntity]),
RouterModule.register([ RouterModule.register([
{ {
path: 'sale_quotation', path: 'sale_quotation',
@ -19,7 +26,7 @@ const modules = [
} }
]) ])
], ],
controllers: [], controllers: [SaleQuotationController],
providers: [] providers: [SaleQuotationService]
}) })
export class SaleQuotationModule {} export class SaleQuotationModule {}

View File

@ -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);
}
}

View File

@ -9,6 +9,6 @@ import { SaleQuotationTemplateEntity } from './sale_quotation_template.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([SaleQuotationTemplateEntity]), DatabaseModule], imports: [TypeOrmModule.forFeature([SaleQuotationTemplateEntity]), DatabaseModule],
controllers: [SaleQuotationTemplateController], controllers: [SaleQuotationTemplateController],
providers: [SaleQuotationTemplateService] providers: [SaleQuotationTemplateService],
}) })
export class SaleQuotationTemplateModule {} export class SaleQuotationTemplateModule {}

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm'; import { EntityManager, Not, Repository } from 'typeorm';
import { Pagination } from '~/helper/paginate/pagination'; import { Pagination } from '~/helper/paginate/pagination';
import { paginate } from '~/helper/paginate'; import { paginate } from '~/helper/paginate';
import { BusinessException } from '~/common/exceptions/biz.exception'; import { BusinessException } from '~/common/exceptions/biz.exception';
@ -33,7 +33,8 @@ export class SaleQuotationTemplateService {
const queryBuilder = this.saleQuotationTemplateRepository const queryBuilder = this.saleQuotationTemplateRepository
.createQueryBuilder('saleQuotationTemplate') .createQueryBuilder('saleQuotationTemplate')
.where(fieldSearch(fields)) .where(fieldSearch(fields))
.andWhere('saleQuotationTemplate.isDelete = 0').addOrderBy('saleQuotationTemplate.createdAt', 'DESC'); .andWhere('saleQuotationTemplate.isDelete = 0')
.addOrderBy('saleQuotationTemplate.createdAt', 'DESC');
return paginate<SaleQuotationTemplateEntity>(queryBuilder, { return paginate<SaleQuotationTemplateEntity>(queryBuilder, {
page, page,
@ -45,6 +46,12 @@ export class SaleQuotationTemplateService {
* *
*/ */
async create(dto: SaleQuotationTemplateDto): Promise<void> { 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); await this.saleQuotationTemplateRepository.insert(dto);
} }
@ -53,6 +60,12 @@ export class SaleQuotationTemplateService {
*/ */
async update(id: number, data: Partial<SaleQuotationTemplateUpdateDto>): Promise<void> { async update(id: number, data: Partial<SaleQuotationTemplateUpdateDto>): Promise<void> {
await this.entityManager.transaction(async manager => { 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, { await manager.update(SaleQuotationTemplateEntity, id, {
...data ...data
}); });