enhance(meter):完成大部分表计相关的接口。
This commit is contained in:
parent
e366888608
commit
2339e4c725
|
@ -2,15 +2,20 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"electricity_bill_calc/logger"
|
"electricity_bill_calc/logger"
|
||||||
|
"electricity_bill_calc/model"
|
||||||
"electricity_bill_calc/repository"
|
"electricity_bill_calc/repository"
|
||||||
"electricity_bill_calc/response"
|
"electricity_bill_calc/response"
|
||||||
"electricity_bill_calc/security"
|
"electricity_bill_calc/security"
|
||||||
"electricity_bill_calc/service"
|
"electricity_bill_calc/service"
|
||||||
|
"electricity_bill_calc/tools"
|
||||||
|
"electricity_bill_calc/types"
|
||||||
"electricity_bill_calc/vo"
|
"electricity_bill_calc/vo"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
"github.com/samber/lo"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,8 +25,17 @@ func InitializeMeterHandlers(router *fiber.App) {
|
||||||
router.Get("/meter/:pid", security.EnterpriseAuthorize, searchMetersWithinPark)
|
router.Get("/meter/:pid", security.EnterpriseAuthorize, searchMetersWithinPark)
|
||||||
router.Post("/meter/:pid", security.EnterpriseAuthorize, createNewMeterManually)
|
router.Post("/meter/:pid", security.EnterpriseAuthorize, createNewMeterManually)
|
||||||
router.Get("/meter/:pid/template", security.EnterpriseAuthorize, downloadMeterArchiveTemplate)
|
router.Get("/meter/:pid/template", security.EnterpriseAuthorize, downloadMeterArchiveTemplate)
|
||||||
|
router.Post("/meter/:pid/batch", security.EnterpriseAuthorize, uploadMeterArchive)
|
||||||
router.Get("/meter/:pid/:code", security.EnterpriseAuthorize, retrieveSpecificMeterDetail)
|
router.Get("/meter/:pid/:code", security.EnterpriseAuthorize, retrieveSpecificMeterDetail)
|
||||||
router.Put("/meter/:pid/:code", security.EnterpriseAuthorize, updateMeterManually)
|
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("/meter/:pid/pooled", security.EnterpriseAuthorize, listPooledMeters)
|
||||||
|
router.Get("/meter/choice", security.EnterpriseAuthorize, listUnboundMeters)
|
||||||
|
router.Get("/meter/choice/tenement", security.EnterpriseAuthorize, listUnboundTenementMeters)
|
||||||
|
router.Get("/reading/:pid", security.EnterpriseAuthorize, queryMeterReadings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询指定园区下的表计信息
|
// 查询指定园区下的表计信息
|
||||||
|
@ -154,7 +168,33 @@ func downloadMeterArchiveTemplate(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// 从Excel文件中导入表计档案
|
// 从Excel文件中导入表计档案
|
||||||
func uploadMeterArchive(c *fiber.Ctx) error {
|
func uploadMeterArchive(c *fiber.Ctx) error {
|
||||||
return nil
|
result := response.NewResult(c)
|
||||||
|
session, err := _retreiveSession(c)
|
||||||
|
if err != nil {
|
||||||
|
meterLog.Error("无法从Excel文件中导入表计档案,无法获取当前用户会话", zap.Error(err))
|
||||||
|
return result.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
parkId := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更换系统中的表计
|
// 更换系统中的表计
|
||||||
|
@ -178,3 +218,342 @@ func replaceMeter(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 列出指定公摊表计下的所有关联表计
|
||||||
|
func listAssociatedMeters(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.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
session, err := _retreiveSession(c)
|
||||||
|
if err != nil {
|
||||||
|
meterLog.Error("无法向指定表计绑定关联表计,无法获取当前用户会话", zap.Error(err))
|
||||||
|
return result.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
parkId := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
session, err := _retreiveSession(c)
|
||||||
|
if err != nil {
|
||||||
|
meterLog.Error("无法解除指定园区下两个表计之间的关联关系,无法获取当前用户会话", zap.Error(err))
|
||||||
|
return result.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
parkId := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
masterMeter := c.Params("master")
|
||||||
|
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)
|
||||||
|
session, err := _retreiveSession(c)
|
||||||
|
if err != nil {
|
||||||
|
meterLog.Error("无法分页列出园区中的公摊表计,无法获取当前用户会话", zap.Error(err))
|
||||||
|
return result.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
parkId := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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 := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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("未指定要访问的园区。")
|
||||||
|
}
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
meterLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
session, err := _retreiveSession(c)
|
||||||
|
if err != nil {
|
||||||
|
meterLog.Error("查询指定园区中的表计读数,无法获取当前用户会话", zap.Error(err))
|
||||||
|
return result.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
parkId := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
session, err := _retreiveSession(c)
|
||||||
|
if err != nil {
|
||||||
|
meterLog.Error("记录一条新的表计抄表记录,无法获取当前用户会话", zap.Error(err))
|
||||||
|
return result.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
parkId := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
session, err := _retreiveSession(c)
|
||||||
|
if err != nil {
|
||||||
|
meterLog.Error("更新一条新的表计抄表记录,无法获取当前用户会话", zap.Error(err))
|
||||||
|
return result.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
parkId := c.Params("pid")
|
||||||
|
ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
parkLog.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err))
|
||||||
|
return result.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
case err == nil && !ok:
|
||||||
|
parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid))
|
||||||
|
return result.Forbidden("您无权访问该园区。")
|
||||||
|
}
|
||||||
|
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 downlongMeterReadingTemplate(c *fiber.Ctx) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理上传的抄表记录文件
|
||||||
|
func uploadMeterReadings(c *fiber.Ctx) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"electricity_bill_calc/types"
|
"electricity_bill_calc/types"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
)
|
)
|
||||||
|
@ -19,10 +18,10 @@ type MeterDetail struct {
|
||||||
Ratio decimal.Decimal `json:"ratio" db:"ratio"`
|
Ratio decimal.Decimal `json:"ratio" db:"ratio"`
|
||||||
Seq int64 `json:"seq" db:"seq"`
|
Seq int64 `json:"seq" db:"seq"`
|
||||||
Enabled bool `json:"enabled" db:"enabled"`
|
Enabled bool `json:"enabled" db:"enabled"`
|
||||||
AttachedAt *time.Time `json:"attachedAt" db:"attached_at"`
|
AttachedAt *types.DateTime `json:"attachedAt" db:"attached_at"`
|
||||||
DetachedAt *time.Time `json:"detachedAt" db:"detached_at"`
|
DetachedAt *types.DateTime `json:"detachedAt" db:"detached_at"`
|
||||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
CreatedAt types.DateTime `json:"createdAt" db:"created_at"`
|
||||||
LastModifiedAt time.Time `json:"lastModifiedAt" db:"last_modified_at"`
|
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MeterRelation struct {
|
type MeterRelation struct {
|
||||||
|
@ -30,9 +29,9 @@ type MeterRelation struct {
|
||||||
Park string `json:"parkId" db:"park_id"`
|
Park string `json:"parkId" db:"park_id"`
|
||||||
MasterMeter string `json:"masterMeterId" db:"master_meter_id"`
|
MasterMeter string `json:"masterMeterId" db:"master_meter_id"`
|
||||||
SlaveMeter string `json:"slaveMeterId" db:"slave_meter_id"`
|
SlaveMeter string `json:"slaveMeterId" db:"slave_meter_id"`
|
||||||
EstablishedAt time.Time `json:"establishedAt"`
|
EstablishedAt types.DateTime `json:"establishedAt"`
|
||||||
SuspendedAt *time.Time `json:"suspendedAt"`
|
SuspendedAt *types.DateTime `json:"suspendedAt"`
|
||||||
RevokeAt *time.Time `json:"revokeAt"`
|
RevokeAt *types.DateTime `json:"revokeAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MeterSynchronization struct {
|
type MeterSynchronization struct {
|
||||||
|
@ -42,8 +41,8 @@ type MeterSynchronization struct {
|
||||||
SystemType string `json:"systemType"`
|
SystemType string `json:"systemType"`
|
||||||
SystemIdentity string `json:"systemIdentity"`
|
SystemIdentity string `json:"systemIdentity"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
LastSynchronizedAt time.Time `json:"lastSynchronizedAt" db:"last_synchronized_at"`
|
LastSynchronizedAt types.DateTime `json:"lastSynchronizedAt" db:"last_synchronized_at"`
|
||||||
RevokeAt *time.Time `json:"revokeAt" db:"revoke_at"`
|
RevokeAt *types.DateTime `json:"revokeAt" db:"revoke_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleMeterDocument struct {
|
type SimpleMeterDocument struct {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"electricity_bill_calc/types"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
)
|
)
|
||||||
|
@ -38,7 +38,7 @@ func NewUnitaryReading(ratio, overall decimal.Decimal) *Reading {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MeterReading struct {
|
type MeterReading struct {
|
||||||
ReadAt time.Time `json:"readAt"`
|
ReadAt types.DateTime `json:"readAt"`
|
||||||
Park string `json:"parkId" db:"park_id"`
|
Park string `json:"parkId" db:"park_id"`
|
||||||
Meter string `json:"meterId" db:"meter_id"`
|
Meter string `json:"meterId" db:"meter_id"`
|
||||||
MeterType int16 `json:"meterType"`
|
MeterType int16 `json:"meterType"`
|
||||||
|
|
|
@ -215,7 +215,7 @@ func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.M
|
||||||
pid,
|
pid,
|
||||||
strings.Join(ids, ","),
|
strings.Join(ids, ","),
|
||||||
}
|
}
|
||||||
if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("meter_slice", cacheConditions...); err == nil {
|
if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("meter_slice", cacheConditions...); err == nil && meters != nil {
|
||||||
mr.log.Info("从缓存中获取到了指定园区中所需的表计信息", zap.Int("count", len(*meters)))
|
mr.log.Info("从缓存中获取到了指定园区中所需的表计信息", zap.Int("count", len(*meters)))
|
||||||
return *meters, nil
|
return *meters, nil
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,7 @@ func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.M
|
||||||
).
|
).
|
||||||
Where(
|
Where(
|
||||||
goqu.I("m.park_id").Eq(pid),
|
goqu.I("m.park_id").Eq(pid),
|
||||||
goqu.I("m.id").Eq(goqu.Func("any", ids)),
|
goqu.I("m.code").In(ids),
|
||||||
).
|
).
|
||||||
Order(goqu.I("m.seq").Asc()).
|
Order(goqu.I("m.seq").Asc()).
|
||||||
Prepared(true).ToSQL()
|
Prepared(true).ToSQL()
|
||||||
|
@ -311,6 +311,43 @@ func (mr _MeterRepository) CreateMeter(tx pgx.Tx, ctx context.Context, pid strin
|
||||||
return ok.RowsAffected() > 0, nil
|
return ok.RowsAffected() > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建或者更新一条表计的信息
|
||||||
|
func (mr _MeterRepository) CreateOrUpdateMeter(tx pgx.Tx, ctx context.Context, pid string, meter vo.MeterCreationForm) (bool, error) {
|
||||||
|
mr.log.Info("创建或者更新一条表计的信息", zap.String("park id", pid), zap.String("meter code", meter.Code))
|
||||||
|
timeNow := types.Now()
|
||||||
|
meterSql, meterArgs, _ := mr.ds.
|
||||||
|
Insert(goqu.T("meter_04kv")).
|
||||||
|
Cols(
|
||||||
|
"park_id", "code", "address", "ratio", "seq", "meter_type", "building", "on_floor", "area", "enabled",
|
||||||
|
"attached_at", "created_at", "last_modified_at",
|
||||||
|
).
|
||||||
|
Vals(
|
||||||
|
goqu.Vals{pid, meter.Code, meter.Address, meter.Ratio, meter.Seq, meter.MeterType, meter.Building, meter.OnFloor, meter.Area, meter.Enabled,
|
||||||
|
timeNow, timeNow, timeNow,
|
||||||
|
},
|
||||||
|
).
|
||||||
|
OnConflict(
|
||||||
|
goqu.DoUpdate("meter_04kv_pkey",
|
||||||
|
goqu.Record{
|
||||||
|
"address": goqu.I("excluded.address"),
|
||||||
|
"seq": goqu.I("excluded.seq"),
|
||||||
|
"ratio": goqu.I("excluded.ratio"),
|
||||||
|
"meter_type": goqu.I("excluded.meter_type"),
|
||||||
|
"building": goqu.I("excluded.building"),
|
||||||
|
"on_floor": goqu.I("excluded.on_floor"),
|
||||||
|
"area": goqu.I("excluded.area"),
|
||||||
|
"last_modified_at": goqu.I("excluded.last_modified_at"),
|
||||||
|
}),
|
||||||
|
).
|
||||||
|
Prepared(true).ToSQL()
|
||||||
|
res, err := tx.Exec(ctx, meterSql, meterArgs...)
|
||||||
|
if err != nil {
|
||||||
|
mr.log.Error("创建或者更新表计信息失败", zap.Error(err))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return res.RowsAffected() > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 记录一条表计的抄表信息
|
// 记录一条表计的抄表信息
|
||||||
func (mr _MeterRepository) RecordReading(tx pgx.Tx, ctx context.Context, pid, code string, meterType int16, ratio decimal.Decimal, reading *vo.MeterReadingForm) (bool, error) {
|
func (mr _MeterRepository) RecordReading(tx pgx.Tx, ctx context.Context, pid, code string, meterType int16, ratio decimal.Decimal, reading *vo.MeterReadingForm) (bool, error) {
|
||||||
mr.log.Info("记录一条表计的抄表信息", zap.String("park id", pid), zap.String("meter code", code))
|
mr.log.Info("记录一条表计的抄表信息", zap.String("park id", pid), zap.String("meter code", code))
|
||||||
|
@ -848,19 +885,16 @@ func (mr _MeterRepository) ListUnboundTenementMeters(uid string, pid *string, ke
|
||||||
|
|
||||||
// 查询指定园区中的符合条件的抄表记录
|
// 查询指定园区中的符合条件的抄表记录
|
||||||
func (mr _MeterRepository) ListMeterReadings(pid string, keyword *string, page uint, start, end *types.Date, buidling *string) ([]*model.MeterReading, int64, error) {
|
func (mr _MeterRepository) ListMeterReadings(pid string, keyword *string, page uint, start, end *types.Date, buidling *string) ([]*model.MeterReading, int64, error) {
|
||||||
mr.log.Info("查询指定园区中的符合条件的抄表记录", zap.String("park id", pid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("page", page), zap.Any("start", start), zap.Any("end", end), zap.String("building", tools.DefaultTo(buidling, "")))
|
mr.log.Info("查询指定园区中的符合条件的抄表记录", zap.String("park id", pid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("page", page), logger.DateFieldp("start", start), logger.DateFieldp("end", end), zap.String("building", tools.DefaultTo(buidling, "")))
|
||||||
cacheConditions := []string{
|
cacheConditions := []string{
|
||||||
pid,
|
pid,
|
||||||
tools.DefaultOrEmptyStr(keyword, "UNDEF"),
|
cache.NullableStringKey(keyword),
|
||||||
fmt.Sprintf("%d", page),
|
fmt.Sprintf("%d", page),
|
||||||
tools.CondFn(func(val *types.Date) bool {
|
cache.NullableConditionKey(start),
|
||||||
return val != nil
|
cache.NullableConditionKey(end),
|
||||||
}, start, start.ToString(), "UNDEF"),
|
cache.NullableStringKey(buidling),
|
||||||
tools.CondFn(func(val *types.Date) bool {
|
|
||||||
return val != nil
|
|
||||||
}, end, end.ToString(), "UNDEF"),
|
|
||||||
}
|
}
|
||||||
if readings, total, err := cache.RetrievePagedSearch[[]*model.MeterReading]("meter_reading", cacheConditions...); err == nil {
|
if readings, total, err := cache.RetrievePagedSearch[[]*model.MeterReading]("meter_reading", cacheConditions...); err == nil && readings != nil && total != -1 {
|
||||||
mr.log.Info("从缓存中获取到了指定园区中的抄表记录", zap.Int("count", len(*readings)), zap.Int64("total", total))
|
mr.log.Info("从缓存中获取到了指定园区中的抄表记录", zap.Int("count", len(*readings)), zap.Int64("total", total))
|
||||||
return *readings, total, nil
|
return *readings, total, nil
|
||||||
}
|
}
|
||||||
|
@ -870,6 +904,7 @@ func (mr _MeterRepository) ListMeterReadings(pid string, keyword *string, page u
|
||||||
readingQuery := mr.ds.
|
readingQuery := mr.ds.
|
||||||
From(goqu.T("meter_reading").As("r")).
|
From(goqu.T("meter_reading").As("r")).
|
||||||
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("r.meter_id").Eq(goqu.I("m.code")))).
|
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("r.meter_id").Eq(goqu.I("m.code")))).
|
||||||
|
Select("r.*").
|
||||||
Where(
|
Where(
|
||||||
goqu.I("r.park_id").Eq(pid),
|
goqu.I("r.park_id").Eq(pid),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"electricity_bill_calc/cache"
|
"electricity_bill_calc/cache"
|
||||||
"electricity_bill_calc/global"
|
"electricity_bill_calc/global"
|
||||||
"electricity_bill_calc/logger"
|
"electricity_bill_calc/logger"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/doug-martin/goqu/v9"
|
"github.com/doug-martin/goqu/v9"
|
||||||
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
|
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
"github.com/georgysavva/scany/v2/pgxscan"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -388,6 +390,36 @@ func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (b
|
||||||
return rs.RowsAffected() > 0, nil
|
return rs.RowsAffected() > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在指定园区中创建一个建筑,这个方法会使用事务
|
||||||
|
func (pr _ParkRepository) CreateParkBuildingWithTransaction(tx pgx.Tx, ctx context.Context, pid, name string, floor *string) (bool, error) {
|
||||||
|
timeNow := types.Now()
|
||||||
|
serial.StringSerialRequestChan <- 1
|
||||||
|
code := serial.Prefix("B", <-serial.StringSerialResponseChan)
|
||||||
|
createSql, createArgs, _ := pr.ds.
|
||||||
|
Insert("park_building").
|
||||||
|
Cols(
|
||||||
|
"id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at",
|
||||||
|
).
|
||||||
|
Vals(goqu.Vals{
|
||||||
|
code,
|
||||||
|
pid, name, floor, true, timeNow, timeNow,
|
||||||
|
}).
|
||||||
|
Prepared(true).ToSQL()
|
||||||
|
|
||||||
|
rs, err := tx.Exec(ctx, createSql, createArgs...)
|
||||||
|
if err != nil {
|
||||||
|
pr.log.Error("在指定园区中创建一个新建筑失败!", zap.Error(err))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.RowsAffected() > 0 {
|
||||||
|
cache.AbolishRelation("park_building")
|
||||||
|
cache.AbolishRelation(fmt.Sprintf("park_building:%s", pid))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs.RowsAffected() > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 修改指定园区中指定建筑的信息
|
// 修改指定园区中指定建筑的信息
|
||||||
func (pr _ParkRepository) ModifyParkBuilding(id, pid, name string, floor *string) (bool, error) {
|
func (pr _ParkRepository) ModifyParkBuilding(id, pid, name string, floor *string) (bool, error) {
|
||||||
pr.log.Info("修改指定园区中指定建筑的信息", zap.String("id", id), zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
|
pr.log.Info("修改指定园区中指定建筑的信息", zap.String("id", id), zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
|
||||||
|
|
144
service/meter.go
144
service/meter.go
|
@ -2,6 +2,7 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"electricity_bill_calc/cache"
|
"electricity_bill_calc/cache"
|
||||||
|
"electricity_bill_calc/excel"
|
||||||
"electricity_bill_calc/global"
|
"electricity_bill_calc/global"
|
||||||
"electricity_bill_calc/logger"
|
"electricity_bill_calc/logger"
|
||||||
"electricity_bill_calc/model"
|
"electricity_bill_calc/model"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"electricity_bill_calc/vo"
|
"electricity_bill_calc/vo"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
|
@ -108,8 +110,146 @@ func (ms _MeterService) UpdateMeterRecord(pid string, code string, form *vo.Mete
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理上传的Excel格式表计档案文件,根据表号自动更新数据库
|
// 处理上传的Excel格式表计档案文件,根据表号自动更新数据库
|
||||||
func (ms _MeterService) BatchImportMeters(pid string, file multipart.FileHeader) error {
|
func (ms _MeterService) BatchImportMeters(pid string, file *multipart.FileHeader) ([]excel.ExcelAnalysisError, error) {
|
||||||
return nil
|
ms.log.Info("处理上传的Excel格式表计档案文件", zap.String("park id", pid))
|
||||||
|
ctx, cancel := global.TimeoutContext(10)
|
||||||
|
defer cancel()
|
||||||
|
archiveFile, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法打开上传的Excel格式表计档案文件。", zap.Error(err))
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法打开上传的文件,%w", err)
|
||||||
|
}
|
||||||
|
analyzer, err := excel.NewMeterArchiveExcelAnalyzer(archiveFile)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法根据上传的 Excel 文件创建表计档案分析器。", zap.Error(err))
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法创建表计档案解析器,%w", err)
|
||||||
|
}
|
||||||
|
records, errs := analyzer.Analysis(*new(model.MeterImportRow))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
ms.log.Error("表计档案分析器在解析上传的 Excel 文件时发生错误。", zap.Int("error count", len(errs)))
|
||||||
|
return errs, fmt.Errorf("表计档案分析器在解析上传的 Excel 文件时发生错误。")
|
||||||
|
}
|
||||||
|
// 步骤1:对目前已经解析到的数据进行重复检测,记录重复内容并直接返回
|
||||||
|
var codeStat = make(map[string]int, 0)
|
||||||
|
for _, record := range records {
|
||||||
|
if _, ok := codeStat[record.Code]; !ok {
|
||||||
|
codeStat[record.Code] = 0
|
||||||
|
}
|
||||||
|
codeStat[record.Code]++
|
||||||
|
}
|
||||||
|
duplicatedCodes := make([]string, 0)
|
||||||
|
for code, count := range codeStat {
|
||||||
|
if count > 1 {
|
||||||
|
duplicatedCodes = append(duplicatedCodes, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(duplicatedCodes) > 0 {
|
||||||
|
ms.log.Error("表计档案分析器在解析上传的 Excel 文件时发现重复的表计编号。", zap.Strings("duplicated codes", duplicatedCodes))
|
||||||
|
return []excel.ExcelAnalysisError{
|
||||||
|
{Row: 0, Col: 0, Err: excel.AnalysisError{Err: fmt.Errorf("表计档案分析器在解析上传的 Excel 文件时发现重复的表计编号。(%s)", strings.Join(duplicatedCodes, ", "))}},
|
||||||
|
}, fmt.Errorf("表计档案分析器在解析上传的 Excel 文件时发现重复的表计编号。(%s)", strings.Join(duplicatedCodes, ", "))
|
||||||
|
}
|
||||||
|
// 步骤2:获取指定园区下的所有建筑信息
|
||||||
|
buildings, err := repository.ParkRepository.RetrieveParkBuildings(pid)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法获取指定园区下的所有建筑信息。", zap.Error(err))
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法获取指定园区下的所有建筑信息,%w", err)
|
||||||
|
}
|
||||||
|
buildingNames := lo.Map(buildings, func(element *model.ParkBuilding, _ int) string {
|
||||||
|
return element.Name
|
||||||
|
})
|
||||||
|
// 步骤2.1:获取表计档案中出现的所有建筑,并对档案中新出现的建筑进行创建操作
|
||||||
|
unexistsBuildingNames := make([]string, 0)
|
||||||
|
for _, record := range records {
|
||||||
|
if !lo.Contains(buildingNames, *record.Building) {
|
||||||
|
unexistsBuildingNames = append(unexistsBuildingNames, *record.Building)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx, err := global.DB.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法在自动导入建筑阶段启动数据库事务。", zap.Error(err))
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法在自动导入建筑阶段启动数据库事务,%w", err)
|
||||||
|
}
|
||||||
|
for _, name := range unexistsBuildingNames {
|
||||||
|
_, err := repository.ParkRepository.CreateParkBuildingWithTransaction(tx, ctx, pid, name, nil)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法在自动导入建筑阶段创建新的建筑。", zap.String("building name", name), zap.Error(err))
|
||||||
|
tx.Rollback(ctx)
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法在自动导入建筑阶段创建新的建筑,%w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Commit(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法在自动导入建筑阶段提交数据库事务。", zap.Error(err))
|
||||||
|
tx.Rollback(ctx)
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法在自动导入建筑阶段提交数据库事务,%w", err)
|
||||||
|
}
|
||||||
|
buildings, err = repository.ParkRepository.RetrieveParkBuildings(pid)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法重新获取指定园区下的所有建筑信息。", zap.Error(err))
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法重新获取指定园区下的所有建筑信息,%w", err)
|
||||||
|
}
|
||||||
|
// 步骤2.3:检测并替换表计档案中的建筑ID
|
||||||
|
for _, record := range records {
|
||||||
|
for _, building := range buildings {
|
||||||
|
if building.Name == *record.Building {
|
||||||
|
record.Building = &building.Id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 步骤3:启动数据库事务,直接构建表计插入语句,但提供On Conflict Do Update功能
|
||||||
|
tx, err = global.DB.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法启动数据插入阶段的数据库事务。", zap.Error(err))
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法启动数据插入阶段的数据库事务,%w", err)
|
||||||
|
}
|
||||||
|
meterCreationForms := lo.Map(records, func(element model.MeterImportRow, _ int) vo.MeterCreationForm {
|
||||||
|
return vo.MeterCreationForm{
|
||||||
|
Code: element.Code,
|
||||||
|
Address: element.Address,
|
||||||
|
MeterType: element.MeterType,
|
||||||
|
Ratio: element.Ratio,
|
||||||
|
Seq: element.Seq,
|
||||||
|
Enabled: true,
|
||||||
|
Building: element.Building,
|
||||||
|
OnFloor: element.OnFloor,
|
||||||
|
Area: element.Area,
|
||||||
|
MeterReadingForm: vo.MeterReadingForm{
|
||||||
|
ReadAt: &element.ReadAt,
|
||||||
|
Overall: element.Overall,
|
||||||
|
Critical: element.Critical.Decimal,
|
||||||
|
Peak: element.Peak.Decimal,
|
||||||
|
Flat: element.Flat.Decimal,
|
||||||
|
Valley: element.Valley.Decimal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for _, record := range meterCreationForms {
|
||||||
|
_, err := repository.MeterRepository.CreateOrUpdateMeter(tx, ctx, pid, record)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法在数据插入阶段创建或更新表计。", zap.String("meter code", record.Code), zap.Error(err))
|
||||||
|
tx.Rollback(ctx)
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法在数据插入阶段创建或更新表计,%w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 步骤5:将全部抄表信息保存进入数据库
|
||||||
|
for _, record := range meterCreationForms {
|
||||||
|
_, err := repository.MeterRepository.RecordReading(tx, ctx, pid, record.Code, record.MeterType, record.Ratio, &record.MeterReadingForm)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法在数据插入阶段保存抄表信息。", zap.String("meter code", record.Code), zap.Error(err))
|
||||||
|
tx.Rollback(ctx)
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法在数据插入阶段保存抄表信息,%w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 步骤6:执行事务,更新数据库
|
||||||
|
err = tx.Commit(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ms.log.Error("无法在数据插入阶段提交数据库事务。", zap.Error(err))
|
||||||
|
tx.Rollback(ctx)
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法在数据插入阶段提交数据库事务,%w", err)
|
||||||
|
}
|
||||||
|
return make([]excel.ExcelAnalysisError, 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更换系统中的表计
|
// 更换系统中的表计
|
||||||
|
|
|
@ -91,6 +91,14 @@ func CondFn[T, R any](exprFn func(val T) bool, value T, trueValue, falseValue R)
|
||||||
return Cond(exprFn(value), trueValue, falseValue)
|
return Cond(exprFn(value), trueValue, falseValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用给定的函数对指定的值进行判断,根据表达式的值返回指定的值。本函数为惰性求值。
|
||||||
|
func CondFnElse[T, R any](exprFn func(val T) bool, value T, trueValueFn func(val T) R, falseValueFn func(val T) R) R {
|
||||||
|
if exprFn(value) {
|
||||||
|
return trueValueFn(value)
|
||||||
|
}
|
||||||
|
return falseValueFn(value)
|
||||||
|
}
|
||||||
|
|
||||||
// 使用给定的函数对指定的值进行判断,如果表达式为真,则返回指定的值,否则返回另一个值。
|
// 使用给定的函数对指定的值进行判断,如果表达式为真,则返回指定的值,否则返回另一个值。
|
||||||
func CondOr[T any](exprFn func(val T) bool, value, elseValue T) T {
|
func CondOr[T any](exprFn func(val T) bool, value, elseValue T) T {
|
||||||
return CondFn(exprFn, value, value, elseValue)
|
return CondFn(exprFn, value, value, elseValue)
|
||||||
|
|
|
@ -45,6 +45,12 @@ func FromTime(t time.Time) DateTime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FromUnixMicro(sec int64) DateTime {
|
||||||
|
return DateTime{
|
||||||
|
Time: time.UnixMicro(sec).In(loc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ParseDateTime(t string) (DateTime, error) {
|
func ParseDateTime(t string) (DateTime, error) {
|
||||||
if len(t) == 0 {
|
if len(t) == 0 {
|
||||||
return NewEmptyDateTime(), fmt.Errorf("不能解析空白的日期时间。")
|
return NewEmptyDateTime(), fmt.Errorf("不能解析空白的日期时间。")
|
||||||
|
|
|
@ -36,3 +36,9 @@ type MeterReplacingForm struct {
|
||||||
OldReading MeterReadingForm `json:"oldReading"`
|
OldReading MeterReadingForm `json:"oldReading"`
|
||||||
NewMeter NewMeterForReplacingForm `json:"newMeter"`
|
NewMeter NewMeterForReplacingForm `json:"newMeter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SimplifiedMeterQueryResponse struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Address *string `json:"address"`
|
||||||
|
Park string `json:"parkId"`
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package vo
|
package vo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"electricity_bill_calc/model"
|
||||||
"electricity_bill_calc/types"
|
"electricity_bill_calc/types"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
)
|
)
|
||||||
|
@ -14,3 +16,60 @@ type MeterReadingForm struct {
|
||||||
Valley decimal.Decimal `json:"valley"`
|
Valley decimal.Decimal `json:"valley"`
|
||||||
ReadAt *types.DateTime `json:"readAt"`
|
ReadAt *types.DateTime `json:"readAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r MeterReadingForm) Validate() bool {
|
||||||
|
flat := r.Overall.Sub(r.Critical).Sub(r.Peak).Sub(r.Valley)
|
||||||
|
return flat.LessThan(decimal.Zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MeterReadingDetailResponse struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Park string `json:"parkId"`
|
||||||
|
Address *string `json:"address"`
|
||||||
|
Seq int64 `json:"seq"`
|
||||||
|
Ratio decimal.Decimal `json:"ratio"`
|
||||||
|
MeterType int16 `json:"type"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Building *string `json:"building"`
|
||||||
|
BuildingName *string `json:"buildingName"`
|
||||||
|
OnFloor *string `json:"onFloor"`
|
||||||
|
Area decimal.Decimal `json:"area"`
|
||||||
|
AttachedAt *types.DateTime `json:"attachedAt"`
|
||||||
|
DetachedAt *types.DateTime `json:"detachedAt"`
|
||||||
|
CreatedAt types.DateTime `json:"createdAt"`
|
||||||
|
LastModifiedAt *types.DateTime `json:"lastModifiedAt"`
|
||||||
|
ReadAt types.DateTime `json:"readAt"`
|
||||||
|
ReadAtTimestamp string `json:"readAtTimestamp"`
|
||||||
|
Overall decimal.Decimal `json:"overall"`
|
||||||
|
Critical decimal.Decimal `json:"critical"`
|
||||||
|
Peak decimal.Decimal `json:"peak"`
|
||||||
|
Flat decimal.Decimal `json:"flat"`
|
||||||
|
Valley decimal.Decimal `json:"valley"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromDetailedMeterReading(reading model.DetailedMeterReading) MeterReadingDetailResponse {
|
||||||
|
return MeterReadingDetailResponse{
|
||||||
|
Code: reading.Detail.Code,
|
||||||
|
Park: reading.Detail.Park,
|
||||||
|
Address: reading.Detail.Address,
|
||||||
|
Seq: reading.Detail.Seq,
|
||||||
|
Ratio: reading.Detail.Ratio,
|
||||||
|
MeterType: reading.Detail.MeterType,
|
||||||
|
Enabled: reading.Detail.Enabled,
|
||||||
|
Building: reading.Detail.Building,
|
||||||
|
BuildingName: reading.Detail.BuildingName,
|
||||||
|
OnFloor: reading.Detail.OnFloor,
|
||||||
|
Area: reading.Detail.Area.Decimal,
|
||||||
|
AttachedAt: reading.Detail.AttachedAt,
|
||||||
|
DetachedAt: (*types.DateTime)(reading.Detail.DetachedAt),
|
||||||
|
CreatedAt: types.DateTime(reading.Detail.CreatedAt),
|
||||||
|
LastModifiedAt: (*types.DateTime)(&reading.Detail.LastModifiedAt),
|
||||||
|
ReadAt: reading.Reading.ReadAt,
|
||||||
|
ReadAtTimestamp: fmt.Sprintf("%d", reading.Reading.ReadAt.UnixMicro()),
|
||||||
|
Overall: reading.Reading.Overall,
|
||||||
|
Critical: reading.Reading.Critical,
|
||||||
|
Peak: reading.Reading.Peak,
|
||||||
|
Flat: reading.Reading.Flat,
|
||||||
|
Valley: reading.Reading.Valley,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user