diff --git a/assets/meter_04kv_template.xlsx b/assets/meter_04kv_template.xlsx deleted file mode 100644 index 687fafc..0000000 Binary files a/assets/meter_04kv_template.xlsx and /dev/null differ diff --git a/controller/meter.go b/controller/meter.go index f627863..92810b2 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -1,6 +1,7 @@ package controller import ( + "electricity_bill_calc/excel" "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/repository" @@ -160,10 +161,24 @@ func downloadMeterArchiveTemplate(c *fiber.Ctx) error { meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区信息", zap.Error(err)) return result.NotFound(err.Error()) } - return c.Download( - "./assets/meter_04kv_template.xlsx", - fmt.Sprintf("%s_表计档案.xlsx", parkDetail.Name), - ) + buildings, err := repository.ParkRepository.RetrieveParkBuildings(parkId) + if err != nil { + meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区建筑列表", zap.Error(err)) + return result.NotFound(fmt.Sprintf("无法获取园区建筑列表,%s", err.Error())) + } + if err != nil { + meterLog.Error("无法下载指定的园区表计登记模板,无法生成表计登记模板", zap.Error(err)) + return result.NotFound(fmt.Sprintf("无法生成表计登记模板,%s", err.Error())) + } + c.Status(fiber.StatusOK) + c.Set(fiber.HeaderContentType, fiber.MIMEOctetStream) + c.Set("Content-Transfer-Encoding", "binary") + c.Set(fiber.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s-表计登记模板.xlsx", parkDetail.Name)) + templateGenerator := excel.NewMeterArchiveExcelTemplateGenerator() + defer templateGenerator.Close() + err = templateGenerator.WriteTemplateData(buildings) + templateGenerator.WriteTo(c.Response().BodyWriter()) + return nil } // 从Excel文件中导入表计档案 diff --git a/excel/meter_archive.go b/excel/meter_archive.go index 8b80eaa..8992669 100644 --- a/excel/meter_archive.go +++ b/excel/meter_archive.go @@ -1,8 +1,14 @@ package excel import ( + "electricity_bill_calc/logger" "electricity_bill_calc/model" + "fmt" "io" + + "github.com/samber/lo" + "github.com/xuri/excelize/v2" + "go.uber.org/zap" ) var meterArchiveRecognizers = []*ColumnRecognizer{ @@ -25,3 +31,89 @@ var meterArchiveRecognizers = []*ColumnRecognizer{ func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) { return NewExcelAnalyzer[model.MeterImportRow](file, meterArchiveRecognizers) } + +type MeterArchiveExcelTemplateGenerator struct { + file *excelize.File + log *zap.Logger +} + +func NewMeterArchiveExcelTemplateGenerator() *MeterArchiveExcelTemplateGenerator { + return &MeterArchiveExcelTemplateGenerator{ + file: excelize.NewFile(), + log: logger.Named("Excel", "MeterArchive"), + } +} + +func (MeterArchiveExcelTemplateGenerator) titles() *[]interface{} { + return &[]interface{}{ + "序号", + "表址", + "表号", + "表计类型", + "倍率", + "所在建筑", + "所在楼层", + "辖盖面积", + "抄表时间", + "有功(总)", + "有功(尖)", + "有功(峰)", + "有功(平)", + "有功(谷)", + } +} + +func (g *MeterArchiveExcelTemplateGenerator) Close() { + g.file.Close() +} + +func (g MeterArchiveExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) { + return g.file.WriteTo(w) +} + +func (g *MeterArchiveExcelTemplateGenerator) WriteTemplateData(buildings []*model.ParkBuilding) error { + var err error + defaultSheet := g.file.GetSheetName(0) + g.log.Debug("Select default template sheet", zap.String("sheet", defaultSheet)) + err = g.file.SetColWidth(defaultSheet, "B", "I", 20) + if err != nil { + g.log.Error("未能设定长型列宽。", zap.Error(err)) + return fmt.Errorf("未能设定长型列宽,%w", err) + } + err = g.file.SetColWidth(defaultSheet, "J", "N", 15) + if err != nil { + g.log.Error("未能设定短型列宽。", zap.Error(err)) + return fmt.Errorf("未能设定短型列宽,%w", err) + } + err = g.file.SetSheetRow(defaultSheet, "A1", g.titles()) + if err != nil { + g.log.Error("未能输出模板标题。", zap.Error(err)) + return fmt.Errorf("未能输出模板标题,%w", err) + } + err = g.file.SetRowHeight(defaultSheet, 1, 20) + if err != nil { + g.log.Error("未能设定标题行高度。", zap.Error(err)) + return fmt.Errorf("未能设定标题行高度,%w", err) + } + + meterInstallationTypeValidation := excelize.NewDataValidation(false) + meterInstallationTypeValidation.SetDropList([]string{"商户表", "公共表", "楼道表"}) + meterInstallationTypeValidation.Sqref = "D2:D1048576" + err = g.file.AddDataValidation(defaultSheet, meterInstallationTypeValidation) + if err != nil { + g.log.Error("未能设定表计类型选择器。", zap.Error(err)) + return fmt.Errorf("未能设定表计类型选择器,%w", err) + } + buildingValidation := excelize.NewDataValidation(true) + buildingNames := lo.Map(buildings, func(b *model.ParkBuilding, _ int) string { + return b.Name + }) + buildingValidation.SetDropList(buildingNames) + buildingValidation.Sqref = "F2:F1048576" + err = g.file.AddDataValidation(defaultSheet, buildingValidation) + if err != nil { + g.log.Error("未能设定所在建筑选择器。", zap.Error(err)) + return fmt.Errorf("未能设定所在建筑选择器,%w", err) + } + return nil +} diff --git a/excel/meter_reading.go b/excel/meter_reading.go index 8815b49..40b75ec 100644 --- a/excel/meter_reading.go +++ b/excel/meter_reading.go @@ -3,6 +3,8 @@ package excel import ( "electricity_bill_calc/model" "io" + + "github.com/xuri/excelize/v2" ) var meterReadingsRecognizers = []*ColumnRecognizer{ @@ -17,3 +19,13 @@ var meterReadingsRecognizers = []*ColumnRecognizer{ func NewMeterReadingsExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.ReadingImportRow], error) { return NewExcelAnalyzer[model.ReadingImportRow](file, meterReadingsRecognizers) } + +type MeterReadingsExcelTemplateGenerator struct { + file *excelize.File +} + +func NewMeterReadingsExcelTemplateGenerator() *MeterReadingsExcelTemplateGenerator { + return &MeterReadingsExcelTemplateGenerator{ + file: excelize.NewFile(), + } +} diff --git a/service/meter.go b/service/meter.go index a6e6e06..c3c85bc 100644 --- a/service/meter.go +++ b/service/meter.go @@ -660,6 +660,12 @@ func (ms _MeterService) RecordReading(pid, meterCode string, form *vo.MeterReadi // 获取指定园区的全部待抄表计列表,并将其输出到Excel文件模板中,提供生成文件的二进制内容 func (ms _MeterService) GenerateParkMeterReadingTemplate(pid string, meters []*model.SimpleMeterDocument) ([]byte, error) { + // 步骤1:复制公用模板文件到临时文件夹,文件以园区ID命名 + + // 步骤2:打开复制后的园区文件 + // 步骤3:获取园区中需要抄表的表计列表 + // 步骤4:循环表计列表,将表计信息写入到Excel文件中 + // 步骤5:将生成的临时文件的具体二进制内容返回给调用者 return nil, nil }