diff --git a/controller/tenement.go b/controller/tenement.go new file mode 100644 index 0000000..b1bd0a8 --- /dev/null +++ b/controller/tenement.go @@ -0,0 +1,286 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "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" +) + +var tenementLog = logger.Named("Handler", "Tenement") + +func InitializeTenementHandler(router *fiber.App) { + router.Get("/tenement/choice", security.EnterpriseAuthorize, listTenementForChoice) + router.Get("/tenement/:pid", security.EnterpriseAuthorize, listTenement) + router.Put("/tenement/:pid/:tid", security.EnterpriseAuthorize, updateTenement) + router.Get("/tenement/:pid/:tid", security.EnterpriseAuthorize, getTenementDetail) + router.Get("/tenement/:pid/:tid/meter", security.EnterpriseAuthorize, listMeters) + router.Post("/tenement/:pid/:tid/move/out", security.EnterpriseAuthorize, moveOutTenement) + router.Post("/tenement/:pid", security.EnterpriseAuthorize, addTenement) + router.Post("/tenement/:pid/:tid/meter/binding", security.EnterpriseAuthorize, bindMeterToTenement) + router.Post("/tenement/:pid/:tid/meter/binding/:code/unbind", security.EnterpriseAuthorize, unbindMeterFromTenement) +} + +// 列出园区中的商户 +func listTenement(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + tenementLog.Info("列出园区中的商户", zap.String("Park", parkId)) + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + page := c.QueryInt("page", 1) + keyword := tools.EmptyToNil(c.Query("keyword")) + building := tools.EmptyToNil(c.Query("building")) + startDate, err := types.ParseDatep(c.Query("startDate")) + if err != nil { + tenementLog.Error("列出园区中的商户失败,未能解析查询开始日期", zap.Error(err)) + return result.BadRequest(err.Error()) + } + endDate, err := types.ParseDatep(c.Query("endDate")) + if err != nil { + tenementLog.Error("列出园区中的商户失败,未能解析查询结束日期", zap.Error(err)) + return result.BadRequest(err.Error()) + } + state := tools.EmptyToNil(c.Query("state")) + tenements, total, err := repository.TenementRepository.ListTenements(parkId, uint(page), keyword, building, startDate, endDate, state) + if err != nil { + tenementLog.Error("列出园区中的商户失败,未能获取商户列表", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, err.Error()) + } + var tenementsResponse []*vo.TenementQueryResponse + copier.Copy(&tenementsResponse, &tenements) + return result.Success( + "已经获取到要查询的商户。", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{ + "tenements": tenementsResponse, + }, + ) +} + +// 列出指定商户下所有的表计 +func listMeters(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + tenementId := c.Params("tid") + tenementLog.Info("列出指定商户下所有的表计", zap.String("Park", parkId), zap.String("Tenement", tenementId)) + meters, err := service.TenementService.ListMeter(parkId, tenementId) + if err != nil { + tenementLog.Error("列出指定商户下所有的表计失败,未能获取表计列表", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, err.Error()) + } + return result.Success( + "已经获取到要查询的表计。", + fiber.Map{ + "meters": meters, + }, + ) +} + +// 增加一个新的商户 +func addTenement(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + tenementLog.Info("增加一个新的商户", zap.String("Park", parkId)) + var form vo.TenementCreationForm + if err := c.BodyParser(&form); err != nil { + tenementLog.Error("增加一个新的商户失败,未能解析要添加的商户信息", zap.Error(err)) + return result.BadRequest(fmt.Sprintf("无法解析要添加的商户信息,%s", err.Error())) + } + err := service.TenementService.CreateTenementRecord(parkId, &form) + if err != nil { + tenementLog.Error("增加一个新的商户失败,未能添加商户记录", zap.Error(err)) + return result.NotAccept(fmt.Sprintf("无法添加商户记录,%s", err.Error())) + } + return result.Success("已经成功添加商户。") +} + +// 给指定商户绑定一个新的表计 +func bindMeterToTenement(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + tenementId := c.Params("tid") + if len(tenementId) == 0 { + tenementLog.Error("给指定商户绑定一个新的表计失败,未指定商户。") + return result.BadRequest("未指定商户。") + } + tenementLog.Info("向指定商户绑定一个表计。", zap.String("Park", parkId), zap.String("Tenement", tenementId)) + var form vo.MeterReadingFormWithCode + if err := c.BodyParser(&form); err != nil { + tenementLog.Error("给指定商户绑定一个新的表计失败,未能解析要绑定的表计信息", zap.Error(err)) + return result.BadRequest(fmt.Sprintf("无法解析要绑定的表计信息,%s", err.Error())) + } + if !form.MeterReadingForm.Validate() { + tenementLog.Error("给指定商户绑定一个新的表计失败,表计读数不能正确配平,尖锋电量、峰电量、谷电量之和超过总电量。") + return result.NotAccept("表计读数不能正确配平,尖锋电量、峰电量、谷电量之和超过总电量。") + } + err := service.TenementService.BindMeter(parkId, tenementId, form.Code, &form.MeterReadingForm) + if err != nil { + tenementLog.Error("给指定商户绑定一个新的表计失败,未能绑定表计", zap.Error(err)) + return result.NotAccept(fmt.Sprintf("无法绑定表计,%s", err.Error())) + } + return result.Success("已经成功绑定表计。") +} + +// 从指定商户下解除一个表计的绑定 +func unbindMeterFromTenement(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + tenementId := c.Params("tid") + if len(tenementId) == 0 { + tenementLog.Error("从指定商户下解除一个表计的绑定失败,未指定商户。") + return result.BadRequest("未指定商户。") + } + meterCode := c.Params("code") + if len(meterCode) == 0 { + tenementLog.Error("从指定商户下解除一个表计的绑定失败,未指定表计。") + return result.BadRequest("未指定表计。") + } + tenementLog.Info("从指定商户处解绑一个表计。", zap.String("Park", parkId), zap.String("Tenement", tenementId), zap.String("Meter", meterCode)) + var form vo.MeterReadingForm + if err := c.BodyParser(&form); err != nil { + tenementLog.Error("从指定商户下解除一个表计的绑定失败,未能解析要解除绑定的表计抄表数据。", zap.Error(err)) + return result.BadRequest(fmt.Sprintf("无法解析要解除绑定的表计抄表数据,%s", err.Error())) + } + err := service.TenementService.UnbindMeter(parkId, tenementId, meterCode, &form) + if err != nil { + tenementLog.Error("从指定商户下解除一个表计的绑定失败,未能解除绑定表计。", zap.Error(err)) + return result.NotAccept(fmt.Sprintf("无法解除绑定表计,%s", err.Error())) + } + return result.Success("已经成功解除表计绑定。") +} + +// 修改指定商户的详细信息 +func updateTenement(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + tenementId := c.Params("tid") + if len(tenementId) == 0 { + tenementLog.Error("修改指定商户的详细信息失败,未指定商户。") + return result.BadRequest("未指定商户。") + } + tenementLog.Info("修改指定商户的详细信息。", zap.String("Park", parkId), zap.String("Tenement", tenementId)) + var form vo.TenementCreationForm + if err := c.BodyParser(&form); err != nil { + tenementLog.Error("修改指定商户的详细信息失败,未能解析要修改的商户信息", zap.Error(err)) + return result.BadRequest(fmt.Sprintf("无法解析要修改的商户信息,%s", err.Error())) + } + err := repository.TenementRepository.UpdateTenement(parkId, tenementId, &form) + if err != nil { + tenementLog.Error("修改指定商户的详细信息失败,未能修改商户信息", zap.Error(err)) + return result.NotAccept(fmt.Sprintf("无法修改商户信息,%s", err.Error())) + } + return result.Success("商户信息修改成功。") +} + +// 迁出指定园区中的商户 +func moveOutTenement(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + tenementId := c.Params("tid") + if len(tenementId) == 0 { + tenementLog.Error("迁出指定园区中的商户失败,未指定商户。") + return result.BadRequest("未指定商户。") + } + tenementLog.Info("迁出指定园区中的商户。", zap.String("Park", parkId), zap.String("Tenement", tenementId)) + var readings []*vo.MeterReadingFormWithCode + if err := c.BodyParser(&readings); err != nil { + tenementLog.Error("迁出指定园区中的商户失败,未能解析要迁出商户的抄表数据。", zap.Error(err)) + return result.BadRequest(fmt.Sprintf("无法解析要迁出商户的抄表数据,%s", err.Error())) + } + err := service.TenementService.MoveOutTenement(parkId, tenementId, readings) + if err != nil { + tenementLog.Error("迁出指定园区中的商户失败,未能迁出商户。", zap.Error(err)) + return result.NotAccept(fmt.Sprintf("无法迁出商户,%s", err.Error())) + } + return result.Success("商户迁出成功。") +} + +// 列出园区中的商户列表,主要用于下拉列表 +func listTenementForChoice(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + tenementLog.Error("列出园区中的商户列表失败,未能获取当前用户会话信息", zap.Error(err)) + return result.Unauthorized("未能获取当前用户会话信息。") + } + parkId := tools.EmptyToNil(c.Params("pid")) + if parkId != nil && len(*parkId) > 0 { + if pass, err := checkParkBelongs(*parkId, tenementLog, c, &result); !pass { + return err + } + } + tenementLog.Info("列出园区中的商户列表,主要用于下拉列表。", zap.String("Ent", session.Uid), zap.Stringp("Park", parkId)) + keyword := tools.EmptyToNil(c.Query("keyword")) + limit := c.QueryInt("limit", 6) + tenements, err := repository.TenementRepository.ListForSelect(session.Uid, parkId, keyword, lo.ToPtr(uint(limit))) + if err != nil { + tenementLog.Error("列出园区中的商户列表失败,未能获取商户列表", zap.Error(err)) + return result.NotFound(fmt.Sprintf("未能获取商户列表,%s", err.Error())) + } + var tenementsResponse []*vo.SimplifiedTenementResponse + copier.Copy(&tenementsResponse, &tenements) + return result.Success( + "已经获取到要查询的商户。", + fiber.Map{ + "tenements": tenementsResponse, + }, + ) +} + +// 获取指定园区中指定商户的详细信息 +func getTenementDetail(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + if pass, err := checkParkBelongs(parkId, tenementLog, c, &result); !pass { + return err + } + tenementId := c.Params("tid") + if len(tenementId) == 0 { + tenementLog.Error("获取指定园区中指定商户的详细信息失败,未指定商户。") + return result.BadRequest("未指定商户。") + } + tenementLog.Info("获取指定园区中指定商户的详细信息。", zap.String("Park", parkId), zap.String("Tenement", tenementId)) + tenement, err := repository.TenementRepository.RetrieveTenementDetail(parkId, tenementId) + if err != nil { + tenementLog.Error("获取指定园区中指定商户的详细信息失败,未能获取商户信息", zap.Error(err)) + return result.NotFound(fmt.Sprintf("未能获取商户信息,%s", err.Error())) + } + var detail vo.TenementDetailResponse + copier.Copy(&detail, &tenement) + return result.Success( + "已经获取到要查询的商户。", + fiber.Map{ + "tenement": detail, + }, + ) +} diff --git a/service/tenement.go b/service/tenement.go index c0999bb..9103ca5 100644 --- a/service/tenement.go +++ b/service/tenement.go @@ -207,6 +207,11 @@ func (ts _TenementService) MoveOutTenement(pid, tid string, reading []*vo.MeterR tx.Rollback(ctx) return fmt.Errorf("找不到指定表计[%s]的抄表信息,%w", meterCode, err) } + if reading.Validate() { + ts.log.Error("迁出指定商户失败,表计读数不能正确配平,尖锋电量、峰电量、谷电量之和超过总电量。", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("表计[%s]读数不能正确配平,尖锋电量、峰电量、谷电量之和超过总电量。", meterCode) + } err = repository.TenementRepository.UnbindMeter(tx, ctx, pid, tid, meterCode) if err != nil { ts.log.Error("迁出指定商户失败,未能解除表计绑定", zap.Error(err)) diff --git a/vo/tenement.go b/vo/tenement.go index 5916612..e5524a4 100644 --- a/vo/tenement.go +++ b/vo/tenement.go @@ -1,5 +1,10 @@ package vo +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/types" +) + type TenementCreationForm struct { Name string `json:"name"` ShortName *string `json:"shortName"` @@ -14,3 +19,43 @@ type TenementCreationForm struct { Bank *string `json:"bank"` Account *string `json:"bankAccount"` } + +type TenementQueryResponse struct { + Id string `json:"id"` + Name string `json:"name"` + ShortName *string `json:"shortName"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + Building *string `json:"building"` + BuildingName *string `json:"buildingName"` + OnFloor *string `json:"onFloor"` + MovedInAt *types.Date `json:"movedInAt"` + MovedOutAt *types.Date `json:"movedOutAt"` + CreatedAt types.DateTime `json:"createdAt"` + LastModifiedAt *types.DateTime `json:"lastModifiedAt"` +} + +type SimplifiedTenementResponse struct { + Id string `json:"id"` + FullName string `json:"fullName"` + ShortName *string `json:"shortName"` + Park string `json:"park"` +} + +type TenementDetailResponse struct { + Id string `json:"id"` + FullName string `json:"fullName"` + ShortName *string `json:"shortName"` + Address string `json:"address"` + Contact string `json:"contact" copier:"ContactName"` + Phone string `json:"phone" copier:"ContactPhone"` + Building string `json:"building"` + BuildingName *string `json:"buildingName"` + OnFloor *string `json:"onFloor"` + InvoiceInfo *model.InvoiceTitle `json:"invoiceInfo"` + MovedInAt *types.Date `json:"movedInAt"` + MovedOutAt *types.Date `json:"movedOutAt"` + CreatedAt types.DateTime `json:"createdAt"` + LastModifiedAt *types.DateTime `json:"lastModifiedAt"` +}