diff --git a/controller/meter.go b/controller/meter.go index 92810b2..7f3c368 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -37,6 +37,9 @@ func InitializeMeterHandlers(router *fiber.App) { router.Get("/meter/choice", security.EnterpriseAuthorize, listUnboundMeters) router.Get("/meter/choice/tenement", security.EnterpriseAuthorize, listUnboundTenementMeters) router.Get("/reading/:pid", security.EnterpriseAuthorize, queryMeterReadings) + router.Post("/reading/:pid/:code", security.EnterpriseAuthorize, recordMeterReading) + router.Put("/reading/:pid/:code/:reading", security.EnterpriseAuthorize, updateMeterReading) + router.Get("/reading/:pid/template", security.EnterpriseAuthorize, downloadMeterReadingsTemplate) } // 查询指定园区下的表计信息 @@ -170,13 +173,19 @@ func downloadMeterArchiveTemplate(c *fiber.Ctx) error { meterLog.Error("无法下载指定的园区表计登记模板,无法生成表计登记模板", zap.Error(err)) return result.NotFound(fmt.Sprintf("无法生成表计登记模板,%s", err.Error())) } + + templateGenerator := excel.NewMeterArchiveExcelTemplateGenerator() + defer templateGenerator.Close() + err = templateGenerator.WriteTemplateData(buildings) + if err != nil { + meterLog.Error("无法下载指定的园区表计登记模板,无法生成表计登记模板", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, 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 } @@ -564,7 +573,44 @@ func updateMeterReading(c *fiber.Ctx) error { } // 下载指定园区的表计抄表模板 -func downlongMeterReadingTemplate(c *fiber.Ctx) error { +func downloadMeterReadingsTemplate(c *fiber.Ctx) error { + parkId := c.Params("pid") + meterLog.Info("下载指定的园区表计抄表模板", zap.String("park id", parkId)) + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + meterLog.Error("无法下载指定的园区表计抄表模板,无法获取当前用户会话", zap.Error(err)) + return result.Unauthorized(err.Error()) + } + if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + return err + } + parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId) + if err != nil { + meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区信息", zap.Error(err)) + return result.NotFound(err.Error()) + } + meterDocs, err := repository.MeterRepository.ListMeterDocForTemplate(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())) + } + templateGenerator := excel.NewMeterReadingsExcelTemplateGenerator() + defer templateGenerator.Close() + err = templateGenerator.WriteTemplateData(meterDocs) + if err != nil { + meterLog.Error("无法下载指定的园区表计抄表模板,无法生成表计抄表模板", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, 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.WriteTo(c.Response().BodyWriter()) return nil } diff --git a/excel/meter_reading.go b/excel/meter_reading.go index 40b75ec..69aaa40 100644 --- a/excel/meter_reading.go +++ b/excel/meter_reading.go @@ -1,10 +1,14 @@ package excel import ( + "electricity_bill_calc/logger" "electricity_bill_calc/model" + "electricity_bill_calc/tools" + "fmt" "io" "github.com/xuri/excelize/v2" + "go.uber.org/zap" ) var meterReadingsRecognizers = []*ColumnRecognizer{ @@ -22,10 +26,117 @@ func NewMeterReadingsExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.Reading type MeterReadingsExcelTemplateGenerator struct { file *excelize.File + log *zap.Logger } func NewMeterReadingsExcelTemplateGenerator() *MeterReadingsExcelTemplateGenerator { return &MeterReadingsExcelTemplateGenerator{ file: excelize.NewFile(), + log: logger.Named("Excel", "MeterReadings"), } } + +func (MeterReadingsExcelTemplateGenerator) titles() *[]interface{} { + return &[]interface{}{ + "抄表序号", + "抄表时间", + "表计编号", + "表计名称", + "商户名称", + "倍率", + "有功(总)", + "有功(尖)", + "有功(峰)", + "有功(谷)", + } +} + +func (g MeterReadingsExcelTemplateGenerator) Close() { + g.file.Close() +} + +func (g MeterReadingsExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) { + return g.file.WriteTo(w) +} + +func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.SimpleMeterDocument) error { + var err error + defaultSheet := g.file.GetSheetName(0) + g.log.Debug("选定默认输出表格", zap.String("sheet", defaultSheet)) + err = g.file.SetColWidth(defaultSheet, "A", "E", 30) + if err != nil { + g.log.Error("未能设定长型单元格的宽度。", zap.Error(err)) + return fmt.Errorf("未能设定长型单元格的宽度,%w", err) + } + err = g.file.SetColWidth(defaultSheet, "F", "F", 10) + if err != nil { + g.log.Error("未能设定倍率单元格的宽度。", zap.Error(err)) + return fmt.Errorf("未能设定倍率单元格的宽度,%w", err) + } + err = g.file.SetColWidth(defaultSheet, "G", "J", 20) + 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, 30) + if err != nil { + g.log.Error("未能设定标题行的高度。", zap.Error(err)) + return fmt.Errorf("未能设定标题行的高度,%w", err) + } + + dateTimeExp := "yyyy-mm-dd hh:mm;@" + dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{ + CustomNumFmt: &dateTimeExp, + }) + if err != nil { + g.log.Error("未能创建日期时间格式。", zap.Error(err)) + return fmt.Errorf("未能创建日期时间格式,%w", err) + } + g.file.SetCellStyle(defaultSheet, "B2", "B1048576", dateTimeColStyle) + + numExp := "0.0000;@" + numColStyle, err := g.file.NewStyle(&excelize.Style{ + CustomNumFmt: &numExp, + }) + if err != nil { + g.log.Error("未能创建抄表数字格式。", zap.Error(err)) + return fmt.Errorf("未能创建抄表数字格式,%w", err) + } + g.file.SetCellStyle(defaultSheet, "F2", "J1048576", numColStyle) + + stream, err := g.file.NewStreamWriter(defaultSheet) + if err != nil { + g.log.Error("未能创建流式写入器。", zap.Error(err)) + return fmt.Errorf("未能创建流式写入器,%w", err) + } + + for i, meter := range meters { + startCell, err := excelize.CoordinatesToCellName(1, i+2) + if err != nil { + g.log.Error("未能定位输出数据的起始单元格。", zap.Error(err)) + return fmt.Errorf("未能定位输出数据的起始单元格,%w", err) + } + if err := stream.SetRow(startCell, []interface{}{ + meter.Seq, + "", + meter.Code, + tools.DefaultTo(meter.Address, ""), + tools.DefaultTo(meter.TenementName, ""), + meter.Ratio, + }); err != nil { + g.log.Error("向模板写入数据出现错误。", zap.Error(err)) + return fmt.Errorf("向模板写入数据出现错误,%w", err) + } + } + if err = stream.Flush(); err != nil { + g.log.Error("未能刷新流式写入器。", zap.Error(err)) + return fmt.Errorf("未能刷新流式写入器,%w", err) + } + + return err +}