package controller import ( "electricity_bill_calc/excel" "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/repository" "electricity_bill_calc/response" "electricity_bill_calc/security" "electricity_bill_calc/service" "electricity_bill_calc/tools" "electricity_bill_calc/types" "electricity_bill_calc/vo" "fmt" "github.com/gofiber/fiber/v2" "github.com/jinzhu/copier" "github.com/samber/lo" "go.uber.org/zap" "net/http" ) var meterLog = logger.Named("Handler", "Meter") func InitializeMeterHandlers(router *fiber.App) { router.Get("/meter/choice", security.EnterpriseAuthorize, listUnboundMeters) router.Get("/meter/choice/tenement", security.EnterpriseAuthorize, listUnboundTenementMeters) router.Get("/meter/:pid", security.EnterpriseAuthorize, searchMetersWithinPark) router.Post("/meter/:pid", security.EnterpriseAuthorize, createNewMeterManually) router.Get("/meter/:pid/template", security.EnterpriseAuthorize, downloadMeterArchiveTemplate) router.Post("/meter/:pid/batch", security.EnterpriseAuthorize, uploadMeterArchive) router.Get("/meter/:pid/pooled", security.EnterpriseAuthorize, listPooledMeters) router.Get("/meter/:pid/:code", security.EnterpriseAuthorize, retrieveSpecificMeterDetail) router.Put("/meter/:pid/:code", security.EnterpriseAuthorize, updateMeterManually) router.Patch("/meter/:pid/:code", security.EnterpriseAuthorize, replaceMeter) router.Get("/meter/:pid/:code/binding", security.EnterpriseAuthorize, listAssociatedMeters) router.Post("/meter/:pid/:code/binding", security.EnterpriseAuthorize, bindAssociatedMeters) router.Delete("/meter/:pid/:code/binding/:slave", security.EnterpriseAuthorize, unbindAssociatedMeters) router.Get("/reading/:pid", security.EnterpriseAuthorize, queryMeterReadings) router.Put("/reading/:pid/:code/:reading", security.EnterpriseAuthorize, updateMeterReading) router.Get("/reading/:pid/template", security.EnterpriseAuthorize, downloadMeterReadingsTemplate) router.Post("/reading/:pid/batch", security.EnterpriseAuthorize, uploadMeterReadings) router.Post("/reading/:pid/:code", security.EnterpriseAuthorize, recordMeterReading) router.Get("/reading/:pid/choice", security.EnterpriseAuthorize, listReadableMeters) } // 查询指定园区下的表计信息 func searchMetersWithinPark(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("查询指定园区下的表计信息", zap.String("park id", parkId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } keyword := c.Query("keyword") page := c.QueryInt("page", 1) mtype := c.QueryInt("type", 3) meters, total, err := repository.MeterRepository.MetersIn(parkId, uint(page), &keyword, mtype) if err != nil { meterLog.Error("无法查询指定园区下的表计信息,无法获取表计列表", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } return result.Success( "已经取得符合条件的0.4kV表计列表。", response.NewPagedResponse(page, total).ToMap(), fiber.Map{"meters": meters}, ) } // 查询指定园区中指定表计的详细信息 func retrieveSpecificMeterDetail(c *fiber.Ctx) error { parkId := c.Params("pid") meterId := c.Params("code") meterLog.Info("查询指定园区中指定表计的详细信息", zap.String("park id", parkId), zap.String("meter id", meterId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meter, err := repository.MeterRepository.FetchMeterDetail(parkId, meterId) if err != nil { meterLog.Error("无法查询指定园区中指定表计的详细信息,无法获取表计信息", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } if meter == nil { meterLog.Warn("无法查询指定园区中指定表计的详细信息,表计不存在") return result.NotFound("指定的表计不存在。") } return result.Success("指定表计信息已经找到。", fiber.Map{"meter": meter}) } // 手动添加一条0.4kV表计记录 func createNewMeterManually(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("手动添加一条0.4kV表计记录", zap.String("park id", parkId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } var creationForm vo.MeterCreationForm if err := c.BodyParser(&creationForm); err != nil { meterLog.Error("无法手动添加一条0.4kV表计记录,无法解析表计创建表单", zap.Error(err)) return result.NotAccept(err.Error()) } fmt.Println(creationForm) fmt.Println(creationForm.Reading.ReadAt) if err := service.MeterService.CreateMeterRecord(parkId, &creationForm); err != nil { meterLog.Error("无法手动添加一条0.4kV表计记录,无法创建表计记录", zap.Error(err)) return result.NotAccept(err.Error()) } return result.Created("新0.4kV表计已经添加完成。") } // 手动更新一条新的0.4kV表计记录 func updateMeterManually(c *fiber.Ctx) error { parkId := c.Params("pid") meterId := c.Params("code") meterLog.Info("手动更新一条新的0.4kV表计记录", zap.String("park id", parkId), zap.String("meter id", meterId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } var updateForm vo.MeterModificationForm if err := c.BodyParser(&updateForm); err != nil { meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法解析表计更新表单", zap.Error(err)) return result.NotAccept(err.Error()) } if err := service.MeterService.UpdateMeterRecord(parkId, meterId, &updateForm); err != nil { meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法更新表计记录", zap.Error(err)) return result.NotAccept(err.Error()) } return result.Updated("0.4kV表计已经更新完成。") } // 下载指定的园区表计登记模板 func downloadMeterArchiveTemplate(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("下载指定的园区表计登记模板", zap.String("park id", parkId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId) if err != nil { meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区信息", zap.Error(err)) return result.NotFound(err.Error()) } 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())) } 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.WriteTo(c.Response().BodyWriter()) return nil } // 从Excel文件中导入表计档案 func uploadMeterArchive(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } uploadFile, err := c.FormFile("data") if err != nil { meterLog.Error("无法从Excel文件中导入表计档案,无法获取上传的文件", zap.Error(err)) return result.NotAccept(fmt.Sprintf("没有接收到上传的文件,%s", err.Error())) } errs, err := service.MeterService.BatchImportMeters(parkId, uploadFile) if err != nil { meterLog.Error("无法从Excel文件中导入表计档案,无法导入表计档案", zap.Error(err)) return result.Json(fiber.StatusNotAcceptable, "上传的表计档案存在错误。", fiber.Map{"errors": errs}) } return result.Success("表计档案已经导入完成。", fiber.Map{"errors": errs}) } // 更换系统中的表计 func replaceMeter(c *fiber.Ctx) error { parkId := c.Params("pid") meterId := c.Params("code") meterLog.Info("更换系统中的表计", zap.String("park id", parkId), zap.String("meter id", meterId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } var replacementForm vo.MeterReplacingForm if err := c.BodyParser(&replacementForm); err != nil { meterLog.Error("无法更换系统中的表计,无法解析表计更换表单", zap.Error(err)) return result.NotAccept(err.Error()) } err := service.MeterService.ReplaceMeter(parkId, meterId, &replacementForm.OldReading, replacementForm.NewMeter.Code, replacementForm.NewMeter.Ratio, &replacementForm.NewMeter.Reading) if err != nil { meterLog.Error("更换表计出错", zap.Error(err)) return result.Error(500, err.Error()) } return result.Success("更换成功") } // 列出指定公摊表计下的所有关联表计 func listAssociatedMeters(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterId := c.Params("code") meterLog.Info("列出指定公摊表计下的所有关联表计", zap.String("park id", parkId), zap.String("meter id", meterId)) meters, err := service.MeterService.ListPooledMeterRelations(parkId, meterId) if err != nil { meterLog.Error("无法列出指定公摊表计下的所有关联表计,无法获取关联表计列表", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } return result.Success("已经取得指定公摊表计下的所有关联表计列表。", fiber.Map{"meters": meters}) } // 向指定表计绑定关联表计 func bindAssociatedMeters(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterId := c.Params("code") meterLog.Info("向指定表计绑定关联表计", zap.String("park id", parkId), zap.String("meter id", meterId)) var meters = make([]string, 0) if err := c.BodyParser(&meters); err != nil { meterLog.Error("无法向指定表计绑定关联表计,无法解析关联表计列表", zap.Error(err)) return result.NotAccept(err.Error()) } ok, err := service.MeterService.BindMeter(parkId, meterId, meters) if err != nil { meterLog.Error("无法向指定表计绑定关联表计,无法绑定关联表计", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } if !ok { meterLog.Warn("无法向指定表计绑定关联表计,表计关联失败。") return result.NotAccept("表计关联失败。") } return result.Created("已经向指定表计绑定关联表计。") } // 解除指定园区下两个表计之间的关联关系 func unbindAssociatedMeters(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } masterMeter := c.Params("code") slaveMeter := c.Params("slave") if len(masterMeter) == 0 || len(slaveMeter) == 0 { meterLog.Warn("无法解除指定园区下两个表计之间的关联关系,表计编号为空。") return result.NotAccept("存在未给定要操作的表计编号。") } ok, err := service.MeterService.UnbindMeter(parkId, masterMeter, []string{slaveMeter}) if err != nil { meterLog.Error("无法解除指定园区下两个表计之间的关联关系,无法解除关联关系", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } if !ok { meterLog.Warn("无法解除指定园区下两个表计之间的关联关系,表计关联解除失败。") return result.NotAccept("表计关联解除失败。") } return result.Created("已经解除指定园区下两个表计之间的关联关系。") } // 分页列出园区中的公摊表计 func listPooledMeters(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } page := c.QueryInt("page", 1) keyword := c.Query("keyword") meters, total, err := service.MeterService.SearchPooledMetersDetail(parkId, uint(page), &keyword) if err != nil { meterLog.Error("无法分页列出园区中的公摊表计,无法获取公摊表计列表", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } return result.Success( "已经取得符合条件的公摊表计列表。", response.NewPagedResponse(page, total).ToMap(), fiber.Map{"meters": meters}, ) } // 列出指定园区中尚未绑定公摊表计的表计 func listUnboundMeters(c *fiber.Ctx) error { result := response.NewResult(c) session, err := _retreiveSession(c) if err != nil { meterLog.Error("无法列出指定园区中尚未绑定公摊表计的表计,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } parkId := tools.EmptyToNil(c.Query("park")) if pass, err := checkParkBelongs(*parkId, meterLog, c, &result); !pass { return err } keyword := c.Query("keyword") limit := uint(c.QueryInt("limit", 6)) meters, err := repository.MeterRepository.ListUnboundMeters(session.Uid, parkId, &keyword, &limit) if err != nil { meterLog.Error("无法列出指定园区中尚未绑定公摊表计的表计,无法获取表计列表", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } var simplifiedMeters = make([]*vo.SimplifiedMeterQueryResponse, 0) copier.Copy(&simplifiedMeters, &meters) return result.Success("已经取得符合条件的表计列表。", fiber.Map{"meters": simplifiedMeters}) } // 列出指定园区中尚未绑定商户的表计 func listUnboundTenementMeters(c *fiber.Ctx) error { result := response.NewResult(c) session, err := _retreiveSession(c) if err != nil { meterLog.Error("无法列出指定园区中尚未绑定商户的表计,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } parkId := c.Query("park") if len(parkId) == 0 { meterLog.Error("无法列出指定园区中尚未绑定商户的表计,未指定要访问的园区ID") return result.NotAccept("未指定要访问的园区。") } if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } keyword := c.Query("keyword") limit := uint(c.QueryInt("limit", 6)) meters, err := repository.MeterRepository.ListUnboundTenementMeters(session.Uid, &parkId, &keyword, &limit) if err != nil { meterLog.Error("无法列出指定园区中尚未绑定商户的表计,无法获取表计列表", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } var simplifiedMeters = make([]*vo.SimplifiedMeterQueryResponse, 0) copier.Copy(&simplifiedMeters, &meters) return result.Success("已经取得符合条件的表计列表。", fiber.Map{"meters": simplifiedMeters}) } // 查询指定园区中的表计读数 func queryMeterReadings(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") mtype := c.QueryInt("type", 3) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } keyword := tools.EmptyToNil(c.Query("keyword")) page := c.QueryInt("page", 1) building := tools.EmptyToNil(c.Query("building")) start := c.Query("start_date") var startDate *types.Date = nil if len(start) > 0 { if parsedDate, err := types.ParseDate(start); err != nil { meterLog.Error("查询指定园区中的表计读数,无法解析开始日期", zap.Error(err)) } else { startDate = &parsedDate } } end := c.Query("end_date") var endDate *types.Date = nil if len(end) > 0 { if parsedDate, err := types.ParseDate(end); err != nil { meterLog.Error("查询指定园区中的表计读数,无法解析结束日期", zap.Error(err)) } else { endDate = &parsedDate } } readings, total, err := service.MeterService.SearchMeterReadings(parkId, building, startDate, endDate, uint(page), keyword, uint(mtype)) if err != nil { meterLog.Error("查询指定园区中的表计读数,无法获取表计读数列表", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } convertedReadings := lo.Map(readings, func(element *model.DetailedMeterReading, _ int) vo.MeterReadingDetailResponse { return vo.FromDetailedMeterReading(*element) }) return result.Success( "指定园区的表计读数已经列出。", response.NewPagedResponse(page, total).ToMap(), fiber.Map{"records": convertedReadings}, ) } // 记录一条新的表计抄表记录 func recordMeterReading(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterCode := c.Params("code") var readingForm vo.MeterReadingForm if err := c.BodyParser(&readingForm); err != nil { meterLog.Error("记录一条新的表计抄表记录,无法解析表计抄表表单", zap.Error(err)) return result.NotAccept(fmt.Sprintf("无法解析表计抄表表单,%s", err.Error())) } if !readingForm.Validate() { meterLog.Warn("记录一条新的表计抄表记录,表计读数不能正常配平,尖、峰、谷电量和超过总电量。") return result.NotAccept("表计读数不能正常配平,尖、峰、谷电量和超过总电量。") } err := service.MeterService.RecordReading(parkId, meterCode, &readingForm) if err != nil { meterLog.Error("记录一条新的表计抄表记录,无法记录表计抄表记录", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } return result.Created("表计抄表记录已经记录完成。") } // 更新指定园区中指定表计的抄表记录 func updateMeterReading(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Params("pid") if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterCode := c.Params("code") readingAtMicro, err := c.ParamsInt("reading") if err != nil { meterLog.Error("更新一条新的表计抄表记录,无法解析抄表时间", zap.Error(err)) return result.NotAccept(fmt.Sprintf("无法解析抄表时间,%s", err.Error())) } readingAt := types.FromUnixMicro(int64(readingAtMicro)) var readingForm vo.MeterReadingForm if err := c.BodyParser(&readingForm); err != nil { meterLog.Error("更新一条新的表计抄表记录,无法解析表计抄表表单", zap.Error(err)) return result.NotAccept(fmt.Sprintf("无法解析表计抄表表单,%s", err.Error())) } ok, err := repository.MeterRepository.UpdateMeterReading(parkId, meterCode, readingAt, &readingForm) if err != nil { meterLog.Error("更新一条新的表计抄表记录,无法更新表计抄表记录", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } if !ok { meterLog.Warn("更新一条新的表计抄表记录,表计抄表更新失败。") return result.NotAccept("表计抄表记录未能成功更新,可能指定抄表记录不存在。") } return result.Success("表计抄表记录已经更新完成。") } // 下载指定园区的表计抄表模板 func downloadMeterReadingsTemplate(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("下载指定的园区表计抄表模板", zap.String("park id", parkId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { 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 } // 处理上传的抄表记录文件 func uploadMeterReadings(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("从Excel文件中导入抄表档案", zap.String("park id", parkId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } uploadFile, err := c.FormFile("data") if err != nil { meterLog.Error("无法从Excel文件中导入抄表档案,无法获取上传的文件", zap.Error(err)) return result.NotAccept(fmt.Sprintf("没有接收到上传的文件,%s", err.Error())) } errs, err := service.MeterService.BatchImportReadings(parkId, uploadFile) if err != nil { meterLog.Error("无法从Excel文件中导入抄表档案,无法导入抄表档案", zap.Error(err)) return result.Json(fiber.StatusNotAcceptable, "上传的抄表档案存在错误。", fiber.Map{"errors": errs}) } return result.Success("表计档案已经导入完成。", fiber.Map{"errors": errs}) } // 列出可以作为抄表目标的表计,主要用于创建抄表记录时的下拉选择。 func listReadableMeters(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("列出可以作为抄表目标的表计", zap.String("parkId", parkId)) result := response.NewResult(c) if len(parkId) == 0 { meterLog.Error("无法列出指定园区中尚未绑定商户的表计,未指定要访问的园区ID") return result.NotAccept("未指定要访问的园区。") } if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } limit := c.QueryInt("limit", 10) keyword := tools.EmptyToNil(c.Query("keyword")) meters, err := repository.MeterRepository.ListReadableMeters(parkId, keyword, uint(limit)) if err != nil { meterLog.Error( "无法列出可以作为抄表目标的表计,无法获取表计列表", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } var simplifiedMeters = make([]*vo.ReadableMeterQueryResponse, 0) copier.Copy(&simplifiedMeters, &meters) return result.Success( "指定园区中的可作为抄表目标的表计已经列出。", fiber.Map{"meters": simplifiedMeters}, ) }