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的入库记录。 若有疑问,请联系管理员',
|
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:模板名已存在'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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 { 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 {}
|
||||||
|
|
|
@ -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({
|
@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 {}
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue