feat: 导出盘点表初步完成

This commit is contained in:
louis 2024-03-07 16:28:03 +08:00
parent 384b11bd4c
commit 96135fffd1
6 changed files with 222 additions and 31 deletions

View File

@ -32,4 +32,5 @@ SMTP_HOST = smtp.163.com
SMTP_PORT = 465 SMTP_PORT = 465
SMTP_USER = nest_admin@163.com SMTP_USER = nest_admin@163.com
SMTP_PASS = VIPLLOIPMETTROYU SMTP_PASS = VIPLLOIPMETTROYU

View File

@ -10,7 +10,7 @@ module.exports = {
autorestart: true, autorestart: true,
exec_mode: 'cluster', exec_mode: 'cluster',
watch: false, watch: false,
instances: cpuLen, instances: cpuLen,
max_memory_restart: '520M', max_memory_restart: '520M',
args: '', args: '',
env: { env: {

View File

@ -33,9 +33,9 @@ export class MaterialsInventoryController {
@ApiOperation({ summary: '导出原材料盘点表' }) @ApiOperation({ summary: '导出原材料盘点表' })
@Perm(permissions.EXPORT) @Perm(permissions.EXPORT)
async exportMaterialsInventoryCheck( async exportMaterialsInventoryCheck(
@Param() dto: MaterialsInventoryExportDto, @Query() dto: MaterialsInventoryExportDto,
@Res() res: FastifyReply @Res() res: FastifyReply
): Promise<any> { ): Promise<void> {
await this.miService.exportMaterialsInventoryCheck(dto, res); await this.miService.exportMaterialsInventoryCheck(dto, res);
} }

View File

@ -29,9 +29,10 @@ export class MaterialsInventoryExportDto {
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
projectId: number; projectId: number;
@ApiProperty({ description: '导出时间YYYY-MM-DD' }) @ApiProperty({ description: '导出时间YYYY-MM-DD' })
@IsOptional() @IsOptional()
@IsArray()
@Transform(params => { @Transform(params => {
// 开始和结束时间用的是一月的开始和一月的结束的时分秒 // 开始和结束时间用的是一月的开始和一月的结束的时分秒
const date = params.value; const date = params.value;

View File

@ -8,12 +8,15 @@ import { MaterialsInOutController } from './in_out/materials_in_out.controller';
import { MaterialsInOutService } from './in_out/materials_in_out.service'; import { MaterialsInOutService } from './in_out/materials_in_out.service';
import { MaterialsInOutEntity } from './in_out/materials_in_out.entity'; import { MaterialsInOutEntity } from './in_out/materials_in_out.entity';
import { ParamConfigModule } from '../system/param-config/param-config.module'; import { ParamConfigModule } from '../system/param-config/param-config.module';
import { ProjectModule } from '../project/project.module';
import { ProjectEntity } from '../project/project.entity';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([MaterialsInventoryEntity, MaterialsInOutEntity]), TypeOrmModule.forFeature([MaterialsInventoryEntity, MaterialsInOutEntity,ProjectEntity]),
ParamConfigModule, ParamConfigModule,
StorageModule StorageModule,
ProjectModule
], ],
controllers: [MaterialsInventoryController, MaterialsInOutController], controllers: [MaterialsInventoryController, MaterialsInOutController],
providers: [MaterialsInventoryService, MaterialsInOutService] providers: [MaterialsInventoryService, MaterialsInOutService]

View File

@ -12,48 +12,234 @@ import { Pagination } from '~/helper/paginate/pagination';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { paginate } from '~/helper/paginate'; import { paginate } from '~/helper/paginate';
import * as ExcelJS from 'exceljs'; import * as ExcelJS from 'exceljs';
import dayjs from 'dayjs';
import { MaterialsInOutEntity } from './in_out/materials_in_out.entity';
import { fieldSearch } from '~/shared/database/field-search';
import { groupBy, uniqBy } from 'lodash';
import { MaterialsInOrOutEnum } from '~/constants/enum';
import { ProjectEntity } from '../project/project.entity';
@Injectable() @Injectable()
export class MaterialsInventoryService { export class MaterialsInventoryService {
const;
constructor( constructor(
@InjectEntityManager() private entityManager: EntityManager, @InjectEntityManager() private entityManager: EntityManager,
@InjectRepository(MaterialsInventoryEntity) @InjectRepository(MaterialsInventoryEntity)
private materialsInventoryRepository: Repository<MaterialsInventoryEntity> private materialsInventoryRepository: Repository<MaterialsInventoryEntity>,
@InjectRepository(MaterialsInOutEntity)
private materialsInOutRepository: Repository<MaterialsInOutEntity>,
@InjectRepository(ProjectEntity)
private projectRepository: Repository<ProjectEntity>
) {} ) {}
/** /**
* *
*/ */
async exportMaterialsInventoryCheck(dto: MaterialsInventoryExportDto, res: FastifyReply) { async exportMaterialsInventoryCheck(
{ time, projectId }: MaterialsInventoryExportDto,
res: FastifyReply
): Promise<void> {
const ROW_HEIGHT = 20;
const HEADER_FONT_SIZE = 18;
const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook();
// 创建一个工作表 let projects: ProjectEntity[] = [];
const sheet = workbook.addWorksheet('CDKEY'); if (projectId) {
// 设置表头 projects = [await this.projectRepository.findOneBy({ id: projectId })];
sheet.columns = [ }
{ header: '名称', key: 'name', width: 32 }, // 生成数据
{ header: '创建人', key: 'create_user', width: 32 }, const sqb = this.materialsInOutRepository
{ header: '创建时间', key: 'create_time', width: 32 } .createQueryBuilder('mio')
]; .leftJoin('mio.project', 'project')
const data = [ .leftJoin('mio.product', 'product')
{ .leftJoin('product.unit', 'unit')
name: '名称', .leftJoin('product.company', 'company')
create_user: 'admin', .addSelect(['project.id','project.name', 'product.name', 'unit.label', 'company.name'])
create_time: '2023-05-18 12:00:00' .where(fieldSearch({ time }))
} .andWhere('mio.isDelete = 0');
];
data.forEach(item => {
sheet.addRow({
name: item.name,
create_user: item.create_user,
create_time: item.create_time
});
});
if (projectId) {
sqb.andWhere('project.id = :projectId', { projectId });
}
const data = await sqb.addOrderBy('mio.time', 'DESC').getMany();
if (!projectId) {
projects = uniqBy(
data.map(item => item.project),
'id'
);
}
for (const project of projects) {
const currentProjectData = data.filter(item => item.projectId === project.id);
const sheet = workbook.addWorksheet(project.name);
sheet.mergeCells('A1:T1');
// 设置标题
sheet.getCell('A1').value = '山东矿机华信智能科技有限公司原材料盘点表';
// 设置日期
sheet.mergeCells('A2:B2');
sheet.getCell('A2').value = `日期:${dayjs(time[0]).format('YYYY年M月')}`;
// 设置表头
const headers = [
'库存编号',
'公司名称',
'产品名称',
'单位',
'库存数量',
'单价',
'金额',
'',
'',
'',
'',
'',
'',
'',
'',
'结存数量',
'单价',
'金额',
'经办人',
'领料单号',
'备注'
];
sheet.addRow(headers);
sheet.addRow([
'',
'',
'',
'',
'',
'',
'',
'入库时间',
'数量',
'单价',
'金额',
'出库时间',
'数量',
'单价',
'金额',
'',
'',
'',
'',
'',
''
]);
for (let i = 1; i <= 7; i++) {
sheet.mergeCells(`${String.fromCharCode(64 + i)}3:${String.fromCharCode(64 + i)}4`);
}
// 入库
sheet.mergeCells('H3:K3');
sheet.getCell('H3').value = '入库';
// 出库
sheet.mergeCells('L3:O3');
sheet.getCell('L3').value = '出库';
for (let i = 8; i <= 15; i++) {
sheet.getCell(`${String.fromCharCode(64 + i)}4`).style.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFFC000' }
};
}
for (let i = 16; i <= 21; i++) {
sheet.mergeCells(`${String.fromCharCode(64 + i)}3:${String.fromCharCode(64 + i)}4`);
}
// 固定信息样式设定
sheet.eachRow((row, index) => {
row.alignment = { vertical: 'middle', horizontal: 'center' };
row.font = { bold: true };
row.height = ROW_HEIGHT;
if (index >= 3) {
row.eachCell(cell => {
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
}
});
const groupedData = groupBy<MaterialsInOutEntity>(currentProjectData, 'inventoryNumber');
let number = 0;
for (const key in groupedData) {
// 目前暂定逻辑出库只有一次或者没有出库。不会对一个入库的记录多次出库,故而用find。
const inRecord = groupedData[key].find(item => item.inOrOut === MaterialsInOrOutEnum.In);
const outRecord = groupedData[key].find(item => item.inOrOut === MaterialsInOrOutEnum.Out);
number++;
sheet.addRow([
`${inRecord.inventoryNumber}`,
inRecord.product.company.name,
inRecord.product.name,
inRecord.product.unit.label,
'0',
'0',
'0',
inRecord.time,
inRecord.quantity,
parseFloat(`${inRecord.unitPrice || 0}`),
parseFloat(`${inRecord.amount || 0}`),
outRecord?.time || '',
outRecord?.quantity || '',
parseFloat(`${outRecord.unitPrice || 0}`),
parseFloat(`${outRecord.amount || 0}`),
'0',
'0',
'0',
outRecord?.agent,
outRecord?.issuanceNumber,
outRecord?.remark
]);
}
sheet.getCell('A1').font = { size: HEADER_FONT_SIZE };
// 固定信息样式设定
sheet.eachRow((row, index) => {
if (index >= 5) {
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' }
};
});
}
});
sheet.columns.forEach((column, index: number) => {
let maxColumnLength = 0;
const autoWidth = ['B', 'C', 'U'];
if (String.fromCharCode(65 + index) === 'B') maxColumnLength = 20;
if (autoWidth.includes(String.fromCharCode(65 + index))) {
column.eachCell({ includeEmpty: true }, (cell, rowIndex) => {
if (rowIndex >= 5) {
const columnLength = `${cell.value || ''}`.length;
if (columnLength > maxColumnLength) {
maxColumnLength = columnLength;
}
}
});
column.width = maxColumnLength < 12 ? 12 : maxColumnLength; // Minimum width of 10
} else {
column.width = 12;
}
});
}
//读取buffer进行传输
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
res res
.header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') .header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
.header( .header(
'Content-Disposition', 'Content-Disposition',
`attachment; filename="${encodeURIComponent('导出_excel' + new Date().getTime() + '.xlsx')}"` `attachment; filename="${encodeURIComponent('导出_excel' + new Date().getTime() + '.xls')}"`
) )
.send(buffer); .send(buffer);
} }