From ac94c578d6fe8a065042c302b0f81eefb27c0101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 30 May 2023 14:55:39 +0800 Subject: [PATCH 001/141] =?UTF-8?q?refactor(changes):=E6=9A=82=E6=97=B6?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=85=A8=E9=83=A8=E5=86=85=E5=AE=B9=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=AE=8C=E6=88=90=E5=9F=BA=E6=9C=AC=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=BF=9E=E6=8E=A5=E7=9A=84=E5=88=9B=E5=BB=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/charge.go | 93 ---- controller/end_user.go | 237 --------- controller/god_mode.go | 176 ------- controller/maintenance_fee.go | 202 -------- controller/meter04kv.go | 188 ------- controller/park.go | 185 ------- controller/region.go | 40 -- controller/report.go | 301 ------------ controller/statistics.go | 73 --- controller/user.go | 357 -------------- controller/withdraw.go | 81 --- excel/abstract.go | 2 - excel/end_user.go | 35 -- excel/meter_archive.go | 21 - excel/meter_non_pv_template.go | 90 ---- excel/meter_pv_template.go | 108 ---- global/db.go | 116 +++-- go.mod | 17 +- go.sum | 30 ++ logger/bunhook.go | 163 ------ main.go | 99 +--- model/end_user_detail.go | 133 ----- model/maintenance_fee.go | 48 -- model/meter_04kv.go | 39 -- model/park.go | 89 ---- model/publicity.go | 116 ----- model/region.go | 11 - model/report.go | 99 ---- model/report_summary.go | 119 ----- model/shared.go | 32 -- model/user.go | 41 -- model/user_charges.go | 43 -- model/user_detail.go | 69 --- model/will_diluted_fee.go | 35 -- router/router.go | 13 - service/calculate.go | 262 ---------- service/charge.go | 284 ----------- service/end_user.go | 479 ------------------ service/god_mode.go | 875 --------------------------------- service/maintenance_fee.go | 295 ----------- service/meter04kv.go | 231 --------- service/park.go | 171 ------- service/region.go | 70 --- service/report.go | 742 ---------------------------- service/statistics.go | 94 ---- service/user.go | 447 ----------------- service/withdraw.go | 160 ------ 47 files changed, 127 insertions(+), 7484 deletions(-) delete mode 100644 controller/charge.go delete mode 100644 controller/end_user.go delete mode 100644 controller/god_mode.go delete mode 100644 controller/maintenance_fee.go delete mode 100644 controller/meter04kv.go delete mode 100644 controller/park.go delete mode 100644 controller/region.go delete mode 100644 controller/report.go delete mode 100644 controller/statistics.go delete mode 100644 controller/user.go delete mode 100644 controller/withdraw.go delete mode 100644 excel/end_user.go delete mode 100644 excel/meter_archive.go delete mode 100644 excel/meter_non_pv_template.go delete mode 100644 excel/meter_pv_template.go delete mode 100644 logger/bunhook.go delete mode 100644 model/end_user_detail.go delete mode 100644 model/maintenance_fee.go delete mode 100644 model/meter_04kv.go delete mode 100644 model/park.go delete mode 100644 model/publicity.go delete mode 100644 model/region.go delete mode 100644 model/report.go delete mode 100644 model/report_summary.go delete mode 100644 model/shared.go delete mode 100644 model/user_charges.go delete mode 100644 model/user_detail.go delete mode 100644 model/will_diluted_fee.go delete mode 100644 service/calculate.go delete mode 100644 service/charge.go delete mode 100644 service/end_user.go delete mode 100644 service/god_mode.go delete mode 100644 service/maintenance_fee.go delete mode 100644 service/meter04kv.go delete mode 100644 service/park.go delete mode 100644 service/region.go delete mode 100644 service/report.go delete mode 100644 service/statistics.go delete mode 100644 service/user.go delete mode 100644 service/withdraw.go diff --git a/controller/charge.go b/controller/charge.go deleted file mode 100644 index a2b8d43..0000000 --- a/controller/charge.go +++ /dev/null @@ -1,93 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "net/http" - "strconv" - "time" - - "github.com/gofiber/fiber/v2" - "github.com/shopspring/decimal" -) - -func InitializeChargesController(app *fiber.App) { - app.Get("/charges", security.OPSAuthorize, listAllCharges) - app.Post("/charge", security.OPSAuthorize, recordNewCharge) - app.Put("/charge/:uid/:seq", security.OPSAuthorize, modifyChargeState) -} - -func listAllCharges(c *fiber.Ctx) error { - result := response.NewResult(c) - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.NotAccept("查询参数[page]格式不正确。") - } - requestKeyword := c.Query("keyword", "") - requestBeginDate := c.Query("begin", "") - requestEndDate := c.Query("end", "") - charges, total, err := service.ChargeService.ListPagedChargeRecord(requestKeyword, requestBeginDate, requestEndDate, requestPage) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Json( - http.StatusOK, "已获取到符合条件的计费记录。", - response.NewPagedResponse(requestPage, total).ToMap(), - fiber.Map{"records": charges}, - ) -} - -type _NewChargeFormData struct { - UserId string `json:"userId" form:"userId"` - Fee decimal.NullDecimal `json:"fee" form:"fee"` - Discount decimal.NullDecimal `json:"discount" form:"discount"` - Amount decimal.NullDecimal `json:"amount" form:"amount"` - ChargeTo model.Date `json:"chargeTo" form:"chargeTo"` -} - -func recordNewCharge(c *fiber.Ctx) error { - result := response.NewResult(c) - formData := new(_NewChargeFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - currentTime := time.Now() - newRecord := &model.UserCharge{ - UserId: formData.UserId, - Fee: formData.Fee, - Discount: formData.Discount, - Amount: formData.Amount, - Settled: true, - SettledAt: ¤tTime, - ChargeTo: formData.ChargeTo, - } - err := service.ChargeService.CreateChargeRecord(newRecord, true) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Created("指定用户的服务已延期。") -} - -type _StateChangeFormData struct { - Cancelled bool `json:"cancelled"` -} - -func modifyChargeState(c *fiber.Ctx) error { - result := response.NewResult(c) - formData := new(_StateChangeFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - requestUserID := c.Params("uid") - requestChargeSeq, err := strconv.Atoi(c.Params("seq", "-1")) - if err != nil || requestChargeSeq == -1 { - return result.Error(http.StatusNotAcceptable, "参数[记录流水号]解析错误。") - } - err = service.ChargeService.CancelCharge(int64(requestChargeSeq), requestUserID) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Updated("指定用户服务延期记录状态已经更新。") -} diff --git a/controller/end_user.go b/controller/end_user.go deleted file mode 100644 index 9bb6449..0000000 --- a/controller/end_user.go +++ /dev/null @@ -1,237 +0,0 @@ -package controller - -import ( - "database/sql" - "electricity_bill_calc/excel" - "electricity_bill_calc/global" - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "fmt" - "net/http" - "strconv" - - "github.com/gofiber/fiber/v2" - "github.com/samber/lo" - "github.com/shopspring/decimal" -) - -func InitializeEndUserController(router *fiber.App) { - router.Get("/report/:rid/submeter", security.EnterpriseAuthorize, fetchEndUserInReport) - router.Get("/report/:rid/meter/template", downloadEndUserRegisterTemplate) - router.Post("/report/:rid/meter/batch", security.EnterpriseAuthorize, uploadEndUserRegisterTemplate) - router.Put("/report/:rid/submeter/:pid/:mid", security.EnterpriseAuthorize, modifyEndUserRegisterRecord) - router.Get("/end/user/adjusts", security.MustAuthenticated, statEndUserInPeriod) -} - -func fetchEndUserInReport(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - keyword := c.Query("keyword") - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.NotAccept("查询参数[page]格式不正确。") - } - endUsers, totalItem, err := service.EndUserService.SearchEndUserRecord(requestReportId, keyword, requestPage) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Json( - http.StatusOK, - "已获取到符合条件的终端用户集合", - response.NewPagedResponse(requestPage, totalItem).ToMap(), - fiber.Map{"meters": endUsers}, - ) -} - -func downloadEndUserRegisterTemplate(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - users, err := service.EndUserService.AllEndUserRecord(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - reportIndex, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - park, err := service.ParkService.FetchParkDetail(reportIndex.ParkId) - if err != nil { - return result.NotFound(err.Error()) - } - meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - - if meterType == -1 { - return result.NotFound("未能确定用户表计类型。") - } - - c.Status(http.StatusOK) - c.Set("Content-Type", "application/octet-stream") - c.Set("Content-Transfer-Encoding", "binary") - c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=抄表记录-%s-%s.xlsx", park.Name, reportIndex.Period.Format("2006-01"))) - - gen := lo.Ternary[excel.ExcelTemplateGenerator]( - meterType == 0, - excel.NewMeterNonPVExcelTemplateGenerator(), - excel.NewMeterPVExcelTemplateGenerator(), - ) - defer gen.Close() - gen.WriteMeterData(users) - gen.WriteTo(c.Response().BodyWriter()) - return nil -} - -func uploadEndUserRegisterTemplate(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if meterType == -1 { - return result.NotFound("未能确定用户表计类型。") - } - - uploadedFile, err := c.FormFile("data") - if err != nil { - return result.NotAccept("没有接收到上传的档案文件。") - } - archiveFile, err := uploadedFile.Open() - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - - if meterType == 0 { - errs := service.EndUserService.BatchImportNonPVRegister(requestReportId, archiveFile) - if errs.Len() > 0 { - return result.Json(http.StatusInternalServerError, "上传抄表文件存在解析错误", fiber.Map{"errors": errs.Errs}) - } - } else { - errs := service.EndUserService.BatchImportPVRegister(requestReportId, archiveFile) - if errs.Len() > 0 { - return result.Json(http.StatusInternalServerError, "上传抄表文件存在解析错误", fiber.Map{"errors": errs.Errs}) - } - } - return result.Json(http.StatusOK, "已经成功完成抄表记录的导入。", fiber.Map{"errors": make([]error, 0)}) -} - -type ModifyEndUserRegisterFormData struct { - CurrentPeriodOverall decimal.NullDecimal `json:"currentPeriodOverall" form:"currentPeriodOverall"` - CurrentPeriodCritical decimal.NullDecimal `json:"currentPeriodCritical" form:"currentPeriodCritical"` - CurrentPeriodPeak decimal.NullDecimal `json:"currentPeriodPeak" form:"currentPeriodPeak"` - CurrentPeriodValley decimal.NullDecimal `json:"currentPeriodValley" form:"currentPeriodValley"` - AdjustOverall decimal.NullDecimal `json:"adjustOverall" form:"adjustOverall"` - AdjustCritical decimal.NullDecimal `json:"adjustCritical" form:"adjustCritical"` - AdjustPeak decimal.NullDecimal `json:"adjustPeak" form:"adjustPeak"` - AdjustValley decimal.NullDecimal `json:"adjustValley" form:"adjustValley"` -} - -func modifyEndUserRegisterRecord(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - meterType, err := service.ReportService.RetreiveParkEndUserMeterType(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if meterType == -1 { - return result.NotFound("未能确定用户表计类型。") - } - requestParkId := c.Params("pid") - requestMeterId := c.Params("mid") - formData := new(ModifyEndUserRegisterFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - meter, err := service.EndUserService.FetchSpecificEndUserRecord(requestReportId, requestParkId, requestMeterId) - if err != nil { - return result.NotFound(err.Error()) - } - if formData.CurrentPeriodOverall.Valid { - meter.CurrentPeriodOverall = formData.CurrentPeriodOverall.Decimal - } - if formData.CurrentPeriodCritical.Valid { - meter.CurrentPeriodCritical = formData.CurrentPeriodCritical.Decimal - } - if formData.CurrentPeriodPeak.Valid { - meter.CurrentPeriodPeak = formData.CurrentPeriodPeak.Decimal - } - if formData.CurrentPeriodValley.Valid { - meter.CurrentPeriodValley = formData.CurrentPeriodValley.Decimal - } - if formData.AdjustOverall.Valid { - meter.AdjustOverall = formData.AdjustOverall.Decimal - } - if formData.AdjustCritical.Valid { - meter.AdjustCritical = formData.AdjustCritical.Decimal - } - if formData.AdjustPeak.Valid { - meter.AdjustPeak = formData.AdjustPeak.Decimal - } - if formData.AdjustValley.Valid { - meter.AdjustValley = formData.AdjustValley.Decimal - } - valid, err := meter.Validate() - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !valid { - return result.NotAccept("抄表数据合法性验证失败。") - } - ctx, cancel := global.TimeoutContext() - defer cancel() - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - err = service.EndUserService.UpdateEndUserRegisterRecord(&tx, &ctx, *meter) - if err != nil { - tx.Rollback() - return result.Error(http.StatusInternalServerError, err.Error()) - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Success("指定终端用户抄表记录已经更新。") -} - -func statEndUserInPeriod(c *fiber.Ctx) error { - result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - requestUser := lo. - If(session.Type == model.USER_TYPE_ENT, session.Uid). - Else(c.Query("user")) - requestPark := c.Query("park") - if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT { - if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure { - return err - } - } - startDate := c.Query("start") - endDate := c.Query("end") - stat, err := service.EndUserService.StatEndUserRecordInPeriod(requestUser, requestPark, startDate, endDate) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Success( - "已经完成终端用户的费用统计", - fiber.Map{"details": stat}, - ) -} diff --git a/controller/god_mode.go b/controller/god_mode.go deleted file mode 100644 index e431348..0000000 --- a/controller/god_mode.go +++ /dev/null @@ -1,176 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/exceptions" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "net/http" - - "github.com/gofiber/fiber/v2" -) - -func InitializeGodModeController(router *fiber.App) { - gmR := router.Group("/gm") - { - gmR.Delete("/report/:rid/summary", security.SingularityAuthorize, gmResetReportSummary) - gmR.Delete("/report/:rid/maintenance", security.SingularityAuthorize, gmResetReportMaintenance) - gmR.Delete("/report/:rid/meters", security.SingularityAuthorize, gmResetReportEndUserRecord) - gmR.Post("/report/:rid/meters", security.SingularityAuthorize, gmResynchronizeReportEndUserRecord) - gmR.Delete("/report/:rid", security.SingularityAuthorize, gmResetReport) - gmR.Delete("/report/:rid/force", security.SingularityAuthorize, gmDeleteReport) - gmR.Delete("/park/:pid/maintenance/:mid", security.SingularityAuthorize, gmDeleteSpecificMaintenance) - gmR.Delete("/park/:pid/maintenance", security.SingularityAuthorize, gmDeleteAllMaintenance) - gmR.Delete("/park/:pid/meters", security.SingularityAuthorize, gmDeleteAllMeters) - gmR.Delete("/park/:pid/force", security.SingularityAuthorize, gmDeletePark) - gmR.Delete("/enterprise/:uid/force", security.SingularityAuthorize, gmDeleteUser) - } -} - -func gmResetReportSummary(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - done, err := service.GodModeService.ClearReportSummary(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的园区总览部分。") - } - return result.Success("指定报表的园区总览已经重置。") -} - -func gmResetReportMaintenance(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - done, err := service.GodModeService.ClearReportMaintenances(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的配电维护费部分。") - } - return result.Success("指定报表的配电维护费已经重置。") -} - -func gmResynchronizeReportEndUserRecord(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - done, err := service.GodModeService.ResynchronizeEndUser(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的抄表记录基本档案。") - } - return result.Success("指定报表的抄表记录基本档案已经重新同步。") -} - -func gmResetReportEndUserRecord(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - done, err := service.GodModeService.ResetEndUserRegisterRecords(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功重置指定报表的抄表记录部分。") - } - return result.Success("指定报表的抄表记录已经重置。") -} - -func gmResetReport(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - done, err := service.GodModeService.ResetReport(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功重置指定报表。") - } - return result.Success("指定报表已经重置。") -} - -func gmDeleteReport(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - done, err := service.GodModeService.DeleteReport(requestReportId) - if err != nil { - if ipErr, ok := err.(exceptions.ImproperOperateError); ok { - return result.NotAccept(ipErr.Message) - } else { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功删除指定报表。") - } - return result.Success("指定报表已经删除。") -} - -func gmDeleteSpecificMaintenance(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - requestMaintenanceId := c.Params("mid") - done, err := service.GodModeService.RemoveSpecificMaintenance(requestParkId, requestMaintenanceId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功删除指定的维护费用记录。") - } - return result.Success("指定维护费用记录已经删除。") -} - -func gmDeleteAllMaintenance(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - done, err := service.GodModeService.RemoveAllMaintenance(requestParkId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功删除全部维护费用记录。") - } - return result.Success("全部维护费用记录已经删除。") -} - -func gmDeleteAllMeters(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - done, err := service.GodModeService.RemoveAllMeters(requestParkId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功删除全部终端表计档案记录。") - } - return result.Success("全部终端表计档案记录已经删除。") -} - -func gmDeletePark(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - done, err := service.GodModeService.RemovePark(requestParkId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功删除指定的园区。") - } - return result.Success("指定的园区已经删除。") -} - -func gmDeleteUser(c *fiber.Ctx) error { - result := response.NewResult(c) - requestUserId := c.Params("uid") - done, err := service.GodModeService.DeleteUser(requestUserId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !done { - return result.Error(http.StatusInternalServerError, "未能成功删除指定的用户。") - } - return result.Success("指定的用户及其关联信息已经删除。") -} diff --git a/controller/maintenance_fee.go b/controller/maintenance_fee.go deleted file mode 100644 index 9249ae6..0000000 --- a/controller/maintenance_fee.go +++ /dev/null @@ -1,202 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "net/http" - "strconv" - - "github.com/gofiber/fiber/v2" - "github.com/jinzhu/copier" - "github.com/samber/lo" - "github.com/shopspring/decimal" -) - -func InitializeMaintenanceFeeController(router *fiber.App) { - router.Get("/maintenance/fee", security.MustAuthenticated, listMaintenanceFees) - router.Post("/maintenance/fee", security.EnterpriseAuthorize, createMaintenanceFeeRecord) - router.Put("/maintenance/fee/:mid", security.EnterpriseAuthorize, modifyMaintenanceFeeRecord) - router.Put("/maintenance/fee/:mid/enabled", security.EnterpriseAuthorize, changeMaintenanceFeeState) - router.Delete("/maintenance/fee/:mid", security.EnterpriseAuthorize, deleteMaintenanceFee) - router.Get("/additional/charges", security.MustAuthenticated, statAdditionalCharges) -} - -func ensureMaintenanceFeeBelongs(c *fiber.Ctx, result *response.Result, requestMaintenanceFeeId string) (bool, error) { - userSession, err := _retreiveSession(c) - if err != nil { - return false, result.Unauthorized(err.Error()) - } - sure, err := service.MaintenanceFeeService.EnsureFeeBelongs(userSession.Uid, requestMaintenanceFeeId) - if err != nil { - return false, result.Error(http.StatusInternalServerError, err.Error()) - } - if !sure { - return false, result.Unauthorized("所操作维护费记录不属于当前用户。") - } - return true, nil -} - -func listMaintenanceFees(c *fiber.Ctx) error { - result := response.NewResult(c) - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - requestPark := c.Query("park") - requestPeriod := c.Query("period") - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。") - } - if len(requestPark) > 0 { - if userSession.Type == model.USER_TYPE_ENT { - if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure { - return err - } - } - fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{requestPark}, requestPeriod, requestPage) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json( - http.StatusOK, - "已获取指定园区下的维护费记录", - response.NewPagedResponse(requestPage, total).ToMap(), - fiber.Map{"fees": fees}, - ) - } else { - parkIds, err := service.ParkService.AllParkIds(userSession.Uid) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees(parkIds, requestPeriod, requestPage) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json( - http.StatusOK, - "已获取指定用户下的所有维护费记录。", - response.NewPagedResponse(requestPage, total).ToMap(), - fiber.Map{"fees": fees}, - ) - } -} - -type _FeeCreationFormData struct { - ParkId string `json:"parkId" form:"parkId"` - Name string `json:"name" form:"name"` - Period string `json:"period" form:"period"` - Fee decimal.Decimal `json:"fee" form:"fee"` - Memo *string `json:"memo" form:"memo"` -} - -func createMaintenanceFeeRecord(c *fiber.Ctx) error { - result := response.NewResult(c) - formData := new(_FeeCreationFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - if ensure, err := ensureParkBelongs(c, &result, formData.ParkId); !ensure { - return err - } - newMaintenanceFee := &model.MaintenanceFee{} - copier.Copy(newMaintenanceFee, formData) - err := service.MaintenanceFeeService.CreateMaintenanceFeeRecord(*newMaintenanceFee) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Created("新维护费记录已经创建。") -} - -type _FeeModificationFormData struct { - Fee decimal.Decimal `json:"fee" form:"fee"` - Memo *string `json:"memo" form:"memo"` -} - -func modifyMaintenanceFeeRecord(c *fiber.Ctx) error { - result := response.NewResult(c) - requestFee := c.Params("mid") - formData := new(_FeeModificationFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure { - return err - } - newFeeState := new(model.MaintenanceFee) - copier.Copy(newFeeState, formData) - newFeeState.Id = requestFee - err := service.MaintenanceFeeService.ModifyMaintenanceFee(*newFeeState) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Updated("指定维护费条目已更新。") -} - -type _FeeStateFormData struct { - Enabled bool `json:"enabled" form:"enabled"` -} - -func changeMaintenanceFeeState(c *fiber.Ctx) error { - result := response.NewResult(c) - requestFee := c.Params("mid") - formData := new(_FeeStateFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure { - return err - } - err := service.MaintenanceFeeService.ChangeMaintenanceFeeState(requestFee, formData.Enabled) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Updated("指定维护费条目状态已更新。") -} - -func deleteMaintenanceFee(c *fiber.Ctx) error { - result := response.NewResult(c) - requestFee := c.Params("mid") - if ensure, err := ensureMaintenanceFeeBelongs(c, &result, requestFee); !ensure { - return err - } - err := service.MaintenanceFeeService.DeleteMaintenanceFee(requestFee) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Deleted("指定维护费条目已删除。") -} - -func statAdditionalCharges(c *fiber.Ctx) error { - result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - requestUser := lo. - If(session.Type == model.USER_TYPE_ENT, session.Uid). - Else(c.Query("user")) - requestPark := c.Query("park") - if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT { - if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure { - return err - } - } - period := c.Query("period", "") - keyword := c.Query("keyword", "") - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。") - } - fees, total, err := service.MaintenanceFeeService.QueryAdditionalCharges(requestUser, requestPark, period, keyword, requestPage) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Success( - "已经成功获取到物业附加费的统计记录。", - response.NewPagedResponse(requestPage, total).ToMap(), - fiber.Map{"charges": fees}, - ) -} diff --git a/controller/meter04kv.go b/controller/meter04kv.go deleted file mode 100644 index b3fd7f4..0000000 --- a/controller/meter04kv.go +++ /dev/null @@ -1,188 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/excel" - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "fmt" - "net/http" - "strconv" - - "github.com/gofiber/fiber/v2" - "github.com/jinzhu/copier" - "github.com/samber/lo" - "github.com/shopspring/decimal" -) - -func InitializeMeter04kVController(router *fiber.App) { - router.Get("/park/:pid/meter/template", download04kvMeterArchiveTemplate) - router.Get("/park/:pid/meters", security.EnterpriseAuthorize, ListPaged04kVMeter) - router.Get("/park/:pid/meter/:code", security.EnterpriseAuthorize, fetch04kVMeterDetail) - router.Post("/park/:pid/meter", security.EnterpriseAuthorize, createSingle04kVMeter) - router.Post("/park/:pid/meter/batch", security.EnterpriseAuthorize, batchImport04kVMeterArchive) - router.Put("/park/:pid/meter/:code", security.EnterpriseAuthorize, modifySingle04kVMeter) -} - -func download04kvMeterArchiveTemplate(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - parkDetail, err := service.ParkService.FetchParkDetail(requestParkId) - if err != nil { - return result.NotFound("未找到指定的园区信息。") - } - return c.Download("./assets/meter_04kv_template.xlsx", fmt.Sprintf("%s-户表档案.xlsx", parkDetail.Name)) -} - -func ListPaged04kVMeter(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.NotAccept("查询参数[page]格式不正确。") - } - requestKeyword := c.Query("keyword", "") - meters, totalItem, err := service.Meter04kVService.ListMeterDetail(requestParkId, requestKeyword, requestPage) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Json( - http.StatusOK, - "已获取到符合条件的0.4kV表计集合。", - response.NewPagedResponse(requestPage, totalItem).ToMap(), - fiber.Map{"meters": meters}, - ) -} - -func fetch04kVMeterDetail(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - requestMeterCode := c.Params("code") - meter, err := service.Meter04kVService.Get04kVMeterDetail(requestParkId, requestMeterCode) - if err != nil { - return result.NotFound(err.Error()) - } - if meter == nil { - return result.Json(http.StatusNotFound, "指定的表计信息未能找到。", fiber.Map{"meter": nil}) - } - return result.Json(http.StatusOK, "指定的表计信息已找到。", fiber.Map{"meter": meter}) -} - -type _MeterModificationFormData struct { - Address *string `json:"address" form:"address"` - CustomerName *string `json:"customerName" form:"customerName"` - ContactName *string `json:"contactName" form:"contactName"` - ContactPhone *string `json:"contactPhone" form:"contactPhone"` - Ratio decimal.Decimal `json:"ratio" form:"ratio"` - Seq int `json:"seq" form:"seq"` - IsPublicMeter bool `json:"isPublicMeter" form:"isPublicMeter"` - Enabled bool `json:"enabled" form:"enabled"` -} - -type _MeterCreationFormData struct { - Code string `json:"code" form:"code"` - Address *string `json:"address" form:"address"` - CustomerName *string `json:"customerName" form:"customerName"` - ContactName *string `json:"contactName" form:"contactName"` - ContactPhone *string `json:"contactPhone" form:"contactPhone"` - Ratio decimal.Decimal `json:"ratio" form:"ratio"` - Seq int `json:"seq" form:"seq"` - IsPublicMeter bool `json:"isPublicMeter" form:"isPublicMeter"` - Enabled bool `json:"enabled" form:"enabled"` -} - -func createSingle04kVMeter(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - formData := new(_MeterCreationFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - newMeter := new(model.Meter04KV) - copier.Copy(newMeter, formData) - newMeter.ParkId = requestParkId - err := service.Meter04kVService.CreateSingleMeter(*newMeter) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Created("新0.4kV表计已经添加完成。") -} - -func modifySingle04kVMeter(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - requestMeterCode := c.Params("code") - meterDetail, err := service.Meter04kVService.Get04kVMeterDetail(requestParkId, requestMeterCode) - if err != nil { - return result.NotFound(err.Error()) - } - if meterDetail == nil { - return result.NotFound("指定表计的信息为找到,不能修改。") - } - formData := new(_MeterModificationFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - copier.Copy(meterDetail, formData) - err = service.Meter04kVService.UpdateSingleMeter(meterDetail) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Updated("指定0.4kV表计信息已经更新。") -} - -func batchImport04kVMeterArchive(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - uploadedFile, err := c.FormFile("data") - if err != nil { - return result.NotAccept("没有接收到上传的档案文件。") - } - archiveFile, err := uploadedFile.Open() - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - analyzer, err := excel.NewMeterArchiveExcelAnalyzer(archiveFile) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - records, errs := analyzer.Analysis(*new(model.Meter04KV)) - if len(errs) > 0 { - return result.Json(http.StatusNotAcceptable, "上传的表计档案文件存在错误。", fiber.Map{"errors": errs}) - } - - mergedMeters := lo.Map(records, func(meter model.Meter04KV, index int) model.Meter04KV { - meter.ParkId = requestParkId - meter.Enabled = true - return meter - }) - errs = service.Meter04kVService.DuplicateMeterCodeValidate(mergedMeters) - if len(errs) > 0 { - return result.Json(http.StatusNotAcceptable, "上传的表计档案文件存在错误。", fiber.Map{"errors": errs}) - } - err = service.Meter04kVService.BatchCreateMeter(mergedMeters) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json( - http.StatusOK, - "上传的表计档案已经全部导入。", - fiber.Map{"errors": make([]excel.ExcelAnalysisError, 0)}, - ) -} diff --git a/controller/park.go b/controller/park.go deleted file mode 100644 index b7973a0..0000000 --- a/controller/park.go +++ /dev/null @@ -1,185 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "electricity_bill_calc/tools" - "net/http" - - "github.com/gofiber/fiber/v2" - "github.com/google/uuid" - "github.com/jinzhu/copier" - "github.com/shopspring/decimal" -) - -func InitializeParkController(router *fiber.App) { - router.Get("/parks", security.EnterpriseAuthorize, listAllParksUnderSessionUser) - router.Get("/parks/:uid", security.MustAuthenticated, listAllParksUnderSpecificUser) - router.Post("/park", security.EnterpriseAuthorize, createNewPark) - router.Put("/park/:pid", security.EnterpriseAuthorize, modifyPark) - router.Get("/park/:pid", security.EnterpriseAuthorize, fetchParkDetail) - router.Put("/park/:pid/enabled", security.EnterpriseAuthorize, changeParkEnableState) - router.Delete("/park/:pid", security.EnterpriseAuthorize, deleteSpecificPark) -} - -func ensureParkBelongs(c *fiber.Ctx, result *response.Result, requestParkId string) (bool, error) { - userSession, err := _retreiveSession(c) - if err != nil { - return false, result.Unauthorized(err.Error()) - } - sure, err := service.ParkService.EnsurePark(userSession.Uid, requestParkId) - if err != nil { - return false, result.Error(http.StatusInternalServerError, err.Error()) - } - if !sure { - return false, result.Unauthorized("不能访问不属于自己的园区。") - } - return true, nil -} - -func listAllParksUnderSessionUser(c *fiber.Ctx) error { - result := response.NewResult(c) - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - keyword := c.Query("keyword") - parks, err := service.ParkService.ListAllParkBelongsTo(userSession.Uid, keyword) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusOK, "已获取到指定用户下的园区。", fiber.Map{"parks": parks}) -} - -func listAllParksUnderSpecificUser(c *fiber.Ctx) error { - result := response.NewResult(c) - requestUserId := c.Params("uid") - keyword := c.Query("keyword") - parks, err := service.ParkService.ListAllParkBelongsTo(requestUserId, keyword) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusOK, "已获取到指定用户下的园区。", fiber.Map{"parks": parks}) -} - -type _ParkInfoFormData struct { - Name string `json:"name" form:"name"` - Region *string `json:"region" form:"region"` - Address *string `json:"address" form:"address"` - Contact *string `json:"contact" form:"contact"` - Phone *string `json:"phone" from:"phone"` - Area decimal.NullDecimal `json:"area" from:"area"` - Capacity decimal.NullDecimal `json:"capacity" from:"capacity"` - TenementQuantity decimal.NullDecimal `json:"tenement" from:"tenement"` - Category int8 `json:"category" form:"category"` - SubmeterType int8 `json:"submeter" form:"submeter"` -} - -func createNewPark(c *fiber.Ctx) error { - result := response.NewResult(c) - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - formData := new(_ParkInfoFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - newPark := new(model.Park) - copier.Copy(newPark, formData) - newPark.Id = uuid.New().String() - newPark.UserId = userSession.Uid - nameAbbr := tools.PinyinAbbr(newPark.Name) - newPark.Abbr = &nameAbbr - newPark.Enabled = true - err = service.ParkService.SaveNewPark(*newPark) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Created("新园区完成创建。") -} - -func modifyPark(c *fiber.Ctx) error { - result := response.NewResult(c) - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - requestParkId := c.Params("pid") - formData := new(_ParkInfoFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - park, err := service.ParkService.FetchParkDetail(requestParkId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if userSession.Uid != park.UserId { - return result.Unauthorized("不能修改不属于自己的园区。") - } - copier.Copy(park, formData) - nameAbbr := tools.PinyinAbbr(formData.Name) - park.Abbr = &nameAbbr - err = service.ParkService.UpdateParkInfo(park) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Updated("指定园区资料已更新。") -} - -func fetchParkDetail(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - park, err := service.ParkService.FetchParkDetail(requestParkId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusOK, "已经获取到指定园区的信息。", fiber.Map{"park": park}) -} - -type _ParkStateFormData struct { - Enabled bool `json:"enabled" form:"enabled"` -} - -func changeParkEnableState(c *fiber.Ctx) error { - result := response.NewResult(c) - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - formData := new(_ParkStateFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - err = service.ParkService.ChangeParkState(userSession.Uid, requestParkId, formData.Enabled) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Updated("指定园区的可用性状态已成功更新。") -} - -func deleteSpecificPark(c *fiber.Ctx) error { - result := response.NewResult(c) - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - requestParkId := c.Params("pid") - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - err = service.ParkService.DeletePark(userSession.Uid, requestParkId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Deleted("指定园区已成功删除。") -} diff --git a/controller/region.go b/controller/region.go deleted file mode 100644 index f509c54..0000000 --- a/controller/region.go +++ /dev/null @@ -1,40 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/response" - "electricity_bill_calc/service" - "net/http" - - "github.com/gofiber/fiber/v2" -) - -func InitializeRegionController(router *fiber.App) { - router.Get("/region/:rid", fetchRegions) - router.Get("/regions/:rid", fetchAllLeveledRegions) -} - -func fetchRegions(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParentId := c.Params("rid") - regions, err := service.RegionService.FetchSubRegions(requestParentId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if len(regions) == 0 { - return result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", fiber.Map{"regions": make([]string, 0)}) - } - return result.Json(http.StatusOK, "已经获取到相关的行政区划。", fiber.Map{"regions": regions}) -} - -func fetchAllLeveledRegions(c *fiber.Ctx) error { - result := response.NewResult(c) - requestRegionCode := c.Params("rid") - regions, err := service.RegionService.FetchAllParentRegions(requestRegionCode) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if len(regions) == 0 { - return result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", fiber.Map{"regions": make([]string, 0)}) - } - return result.Json(http.StatusOK, "以及获取到相关的行政区划。", fiber.Map{"regions": regions}) -} diff --git a/controller/report.go b/controller/report.go deleted file mode 100644 index 6cf63c0..0000000 --- a/controller/report.go +++ /dev/null @@ -1,301 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/exceptions" - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "electricity_bill_calc/tools" - "net/http" - "strconv" - "time" - - "github.com/gofiber/fiber/v2" - "github.com/jinzhu/copier" - "github.com/samber/lo" - "github.com/shopspring/decimal" -) - -func InitializeReportController(router *fiber.App) { - router.Get("/reports/with/drafts", security.EnterpriseAuthorize, fetchNewestReportOfParkWithDraft) - router.Post("/park/:pid/report", security.EnterpriseAuthorize, initializeNewReport) - router.Get("/report/:rid/step/state", security.EnterpriseAuthorize, fetchReportStepStates) - router.Get("/report/:rid/summary", security.EnterpriseAuthorize, fetchReportParkSummary) - router.Put("/report/:rid/summary", security.EnterpriseAuthorize, fillReportSummary) - router.Get("/report/:rid/summary/calculate", security.EnterpriseAuthorize, testCalculateReportSummary) - router.Post("/report/:rid/summary/calculate", security.EnterpriseAuthorize, progressReportSummary) - router.Put("/report/:rid/step/meter/register", security.EnterpriseAuthorize, progressEndUserRegister) - router.Post("/report/:rid/publish", security.EnterpriseAuthorize, publishReport) - router.Get("/reports", security.MustAuthenticated, searchReports) - router.Get("/report/:rid", security.MustAuthenticated, fetchReportPublicity) - router.Post("/report/:rid/calculate", security.EnterpriseAuthorize, calculateReport) -} - -func ensureReportBelongs(c *fiber.Ctx, result *response.Result, requestReportId string) (bool, error) { - _, err := _retreiveSession(c) - if err != nil { - return false, result.Unauthorized(err.Error()) - } - requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - return false, result.NotFound(err.Error()) - } - if requestReport == nil { - return false, result.NotFound("指定报表未能找到。") - } - return ensureParkBelongs(c, result, requestReport.ParkId) -} - -func fetchNewestReportOfParkWithDraft(c *fiber.Ctx) error { - result := response.NewResult(c) - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - parks, err := service.ReportService.FetchParksWithNewestReport(userSession.Uid) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusOK, "已获取到指定用户下所有园区的最新报表记录。", fiber.Map{"parks": parks}) -} - -func initializeNewReport(c *fiber.Ctx) error { - result := response.NewResult(c) - requestParkId := c.Params("pid") - userSession, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - if ensure, err := ensureParkBelongs(c, &result, requestParkId); !ensure { - return err - } - requestPeriod := c.Query("period") - reportPeriod, err := time.Parse("2006-01", requestPeriod) - if err != nil { - return result.NotAccept("提供的初始化期数格式不正确。") - } - valid, err := service.ReportService.IsNewPeriodValid(userSession.Uid, requestParkId, reportPeriod) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !valid { - return result.NotAccept("只能初始化已发布报表下一个月份的新报表。") - } - newId, err := service.ReportService.InitializeNewReport(requestParkId, reportPeriod) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Created("新一期报表初始化成功。", fiber.Map{"reportId": newId}) -} - -func fetchReportStepStates(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - requestReport, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Json(http.StatusOK, "已经获取到指定报表的填写状态。", fiber.Map{"steps": requestReport.StepState}) -} - -func fetchReportParkSummary(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - summary, err := service.ReportService.RetreiveReportSummary(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - if summary == nil { - return result.NotFound("指定报表未能找到。") - } - return result.Json(http.StatusOK, "已经获取到指定报表中的园区概况。", fiber.Map{"summary": summary}) -} - -type ReportSummaryFormData struct { - Overall decimal.Decimal `json:"overall" form:"overall"` - OverallFee decimal.Decimal `json:"overallFee" form:"overallFee"` - Critical decimal.Decimal `json:"critical" form:"critical"` - CriticalFee decimal.Decimal `json:"criticalFee" form:"criticalFee"` - Peak decimal.Decimal `json:"peak" form:"peak"` - PeakFee decimal.Decimal `json:"peakFee" form:"peakFee"` - Valley decimal.Decimal `json:"valley" form:"valley"` - ValleyFee decimal.Decimal `json:"valleyFee" form:"valleyFee"` - BasicFee decimal.Decimal `json:"basicFee" form:"basicFee"` - AdjustFee decimal.Decimal `json:"adjustFee" from:"adjustFee"` -} - -func fillReportSummary(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - formData := new(ReportSummaryFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - originSummary, err := service.ReportService.RetreiveReportSummary(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - copier.Copy(originSummary, formData) - originSummary.ReportId = requestReportId - err = service.ReportService.UpdateReportSummary(originSummary) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Updated("指定电费公示报表中的园区概况基本数据已经完成更新。") -} - -func testCalculateReportSummary(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - summary, err := service.ReportService.RetreiveReportSummary(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - summary.CalculatePrices() - calcResults := tools.ConvertStructToMap(summary) - return result.Json( - http.StatusOK, - "已完成园区概况的试计算。", - fiber.Map{ - "result": lo.PickByKeys( - calcResults, - []string{"overallPrice", "criticalPrice", "peakPrice", "flat", "flatFee", "flatPrice", "valleyPrice", "consumptionFee"}, - ), - }, - ) -} - -func progressReportSummary(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - err := service.ReportService.CalculateSummaryAndFinishStep(requestReportId) - if err != nil { - if nfErr, ok := err.(exceptions.NotFoundError); ok { - return result.NotFound(nfErr.Error()) - } else { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } - return result.Success("已经完成园区概况的计算,并可以进行到下一步骤。") -} - -func progressEndUserRegister(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - report, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - err = service.ReportService.ProgressReportRegisterEndUser(*report) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Success("终端用户抄表编辑步骤已经完成。") -} - -func publishReport(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - report, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - return result.NotFound(err.Error()) - } - err = service.ReportService.PublishReport(*report) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Success("指定的公示报表已经发布。") -} - -func searchReports(c *fiber.Ctx) error { - result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - requestUser := lo. - If(session.Type == model.USER_TYPE_ENT, session.Uid). - Else(c.Query("user")) - requestPark := c.Query("park") - if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT { - if ensure, err := ensureParkBelongs(c, &result, requestPark); !ensure { - return err - } - } - requestPeriodString := c.Query("period") - var requestPeriod *time.Time = nil - if len(requestPeriodString) > 0 { - parsedPeriod, err := time.Parse("2006-01", requestPeriodString) - if err != nil { - return result.NotAccept("参数[period]的格式不正确。") - } - requestPeriod = lo.ToPtr(parsedPeriod) - } - requestKeyword := c.Query("keyword") - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.NotAccept("查询参数[page]格式不正确。") - } - requestAllReports, err := strconv.ParseBool(c.Query("all", "false")) - if err != nil { - return result.NotAccept("查询参数[all]格式不正确。") - } - records, totalItems, err := service.ReportService.SearchReport(requestUser, requestPark, requestKeyword, requestPeriod, requestPage, !requestAllReports) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Success( - "已经取得符合条件的公示报表记录。", - response.NewPagedResponse(requestPage, totalItems).ToMap(), - fiber.Map{"reports": records}, - ) -} - -func fetchReportPublicity(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - publicity, err := service.ReportService.AssembleReportPublicity(requestReportId) - if err != nil { - if nfErr, ok := err.(exceptions.NotFoundError); ok { - return result.NotFound(nfErr.Error()) - } else { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } - return result.Success("已经取得指定公示报表的发布版本。", tools.ConvertStructToMap(publicity)) -} - -func calculateReport(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - err := service.CalculateService.ComprehensivelyCalculateReport(requestReportId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Success("指定公示报表中的数据已经计算完毕。") -} diff --git a/controller/statistics.go b/controller/statistics.go deleted file mode 100644 index 413efeb..0000000 --- a/controller/statistics.go +++ /dev/null @@ -1,73 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "net/http" - - "github.com/gofiber/fiber/v2" -) - -func InitializeStatisticsController(router *fiber.App) { - router.Get("/audits", security.OPSAuthorize, currentAuditAmount) - router.Get("/stat/reports", security.MustAuthenticated, statReports) -} - -func currentAuditAmount(c *fiber.Ctx) error { - result := response.NewResult(c) - amount, err := service.WithdrawService.AuditWaits() - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusOK, "已经获取到指定的统计信息。", fiber.Map{ - "amounts": map[string]int64{ - "withdraw": amount, - }, - }) -} - -func statReports(c *fiber.Ctx) error { - result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - var ( - enterprises int64 = 0 - parks int64 = 0 - reports []model.ParkPeriodStatistics - ) - if session.Type != 0 { - enterprises, err = service.StatisticsService.EnabledEnterprises() - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - parks, err = service.StatisticsService.EnabledParks() - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - reports, err = service.StatisticsService.ParksNewestState() - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } else { - parks, err = service.StatisticsService.EnabledParks(session.Uid) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - reports, err = service.StatisticsService.ParksNewestState(session.Uid) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } - - return result.Json(http.StatusOK, "已经完成园区报告的统计。", fiber.Map{ - "statistics": fiber.Map{ - "enterprises": enterprises, - "parks": parks, - "reports": reports, - }, - }) -} diff --git a/controller/user.go b/controller/user.go deleted file mode 100644 index 0689cfc..0000000 --- a/controller/user.go +++ /dev/null @@ -1,357 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/cache" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/model" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "electricity_bill_calc/tools" - "fmt" - "net/http" - "strconv" - - "github.com/gofiber/fiber/v2" - "github.com/shopspring/decimal" -) - -func InitializeUserController(router *fiber.App) { - router.Delete("/password/:uid", security.OPSAuthorize, invalidUserPassword) - router.Delete("/login", security.MustAuthenticated, logout) - router.Put("/password", resetUserPassword) - router.Get("/accounts", security.ManagementAuthorize, listPagedUser) - router.Post("/login", login) - router.Put("/account/enabled/state", security.OPSAuthorize, switchUserEnabling) - router.Post("/account", security.OPSAuthorize, createOPSAndManagementAccount) - router.Get("/account/:uid", security.MustAuthenticated, getUserDetail) - router.Post("/enterprise", security.OPSAuthorize, createEnterpriseAccount) - router.Put("/account/:uid", security.OPSAuthorize, modifyAccountDetail) - router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise) - router.Get("/expiration", security.EnterpriseAuthorize, fetchExpiration) -} - -type _LoginFormData struct { - Username string `json:"uname"` - Password string `json:"upass"` - Type int8 `json:"type"` -} - -func login(c *fiber.Ctx) error { - result := response.NewResult(c) - loginData := new(_LoginFormData) - if err := c.BodyParser(loginData); err != nil { - return result.Error(http.StatusInternalServerError, "表单解析失败。") - } - var ( - session *model.Session - err error - ) - if loginData.Type == model.USER_TYPE_ENT { - session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) - } else { - session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password) - } - if err != nil { - if authError, ok := err.(*exceptions.AuthenticationError); ok { - if authError.NeedReset { - return result.LoginNeedReset() - } - return result.Error(int(authError.Code), authError.Message) - } else { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } - return result.LoginSuccess(session) -} - -func logout(c *fiber.Ctx) error { - result := response.NewResult(c) - session := c.Locals("session") - if session == nil { - return result.Success("用户会话已结束。") - } - _, err := cache.ClearSession(session.(*model.Session).Token) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Success("用户已成功登出系统。") -} - -func invalidUserPassword(c *fiber.Ctx) error { - result := response.NewResult(c) - targetUserId := c.Params("uid") - verifyCode, err := service.UserService.InvalidUserPassword(targetUserId) - if _, ok := err.(exceptions.NotFoundError); ok { - return result.NotFound("未找到指定用户。") - } - if _, ok := err.(exceptions.UnsuccessfulOperationError); ok { - return result.NotAccept("未能成功更新用户的密码。") - } - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusAccepted, "用户密码已经失效", fiber.Map{"verify": verifyCode}) -} - -type _ResetPasswordFormData struct { - VerifyCode string `json:"verifyCode"` - Username string `json:"uname"` - NewPassword string `json:"newPass"` -} - -func resetUserPassword(c *fiber.Ctx) error { - result := response.NewResult(c) - resetForm := new(_ResetPasswordFormData) - if err := c.BodyParser(resetForm); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - verified, err := service.UserService.VerifyUserPassword(resetForm.Username, resetForm.VerifyCode) - if _, ok := err.(exceptions.NotFoundError); ok { - return result.NotFound("指定的用户不存在。") - } - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if !verified { - return result.Error(http.StatusUnauthorized, "验证码不正确。") - } - completed, err := service.UserService.ResetUserPassword(resetForm.Username, resetForm.NewPassword) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - if completed { - return result.Updated("用户凭据已更新。") - } - return result.NotAccept("用户凭据未能成功更新。") -} - -func listPagedUser(c *fiber.Ctx) error { - result := response.NewResult(c) - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.NotAccept("查询参数[page]格式不正确。") - } - requestKeyword := c.Query("keyword") - requestUserType, err := strconv.Atoi(c.Query("type", "-1")) - if err != nil { - return result.NotAccept("查询参数[type]格式不正确。") - } - var requestUserStat *bool - state, err := strconv.ParseBool(c.Query("state")) - if err != nil { - requestUserStat = nil - } else { - requestUserStat = &state - } - users, total, err := service.UserService.ListUserDetail(requestKeyword, requestUserType, requestUserStat, requestPage) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Json( - http.StatusOK, - "已取得符合条件的用户集合。", - response.NewPagedResponse(requestPage, total).ToMap(), - fiber.Map{"accounts": users}, - ) -} - -type _UserStateChangeFormData struct { - UserID string `json:"uid" form:"uid"` - Enabled bool `json:"enabled" form:"enabled"` -} - -func switchUserEnabling(c *fiber.Ctx) error { - result := response.NewResult(c) - switchForm := new(_UserStateChangeFormData) - if err := c.BodyParser(switchForm); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - err := service.UserService.SwitchUserState(switchForm.UserID, switchForm.Enabled) - if err != nil { - if nfErr, ok := err.(*exceptions.NotFoundError); ok { - return result.NotFound(nfErr.Message) - } else { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } - return result.Updated("用户状态已经更新。") -} - -type _OPSAccountCreationFormData struct { - Username string `json:"username" form:"username"` - Name string `json:"name" form:"name"` - Contact *string `json:"contact" form:"contact"` - Phone *string `json:"phone" form:"phone"` - Type int `json:"type" form:"type"` -} - -func createOPSAndManagementAccount(c *fiber.Ctx) error { - result := response.NewResult(c) - creationForm := new(_OPSAccountCreationFormData) - if err := c.BodyParser(creationForm); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - exists, err := service.UserService.IsUsernameExists(creationForm.Username) - if exists { - return result.Conflict("指定的用户名已经被使用了。") - } - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - newUser := new(model.User) - newUser.Username = creationForm.Username - newUser.Type = int8(creationForm.Type) - newUser.Enabled = true - newUserDetail := new(model.UserDetail) - newUserDetail.Name = &creationForm.Name - newUserDetail.Contact = creationForm.Contact - newUserDetail.Phone = creationForm.Phone - newUserDetail.UnitServiceFee = decimal.Zero - newUserDetail.ServiceExpiration, _ = model.ParseDate("2099-12-31") - verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - cache.AbolishRelation("user") - return result.Json(http.StatusCreated, "用户已经成功创建。", fiber.Map{"verify": verifyCode}) -} - -func getUserDetail(c *fiber.Ctx) error { - result := response.NewResult(c) - targetUserId := c.Params("uid") - exists, err := service.UserService.IsUserExists(targetUserId) - if !exists { - return result.NotFound("指定的用户不存在。") - } - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - userDetail, err := service.UserService.FetchUserDetail(targetUserId) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusOK, "用户详细信息已获取到。", fiber.Map{"user": userDetail}) -} - -type _EnterpriseCreationFormData struct { - Username string `json:"username" form:"username"` - Name string `json:"name" form:"name"` - Region *string `json:"region" form:"region"` - Address *string `json:"address" form:"address"` - Contact *string `json:"contact" form:"contact"` - Phone *string `json:"phone" form:"phone"` - UnitServiceFee *string `json:"unitServiceFee" form:"unitServiceFee"` -} - -func createEnterpriseAccount(c *fiber.Ctx) error { - result := response.NewResult(c) - creationForm := new(_EnterpriseCreationFormData) - if err := c.BodyParser(creationForm); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - exists, err := service.UserService.IsUsernameExists(creationForm.Username) - if exists { - return result.Conflict("指定的用户名已经被使用了。") - } - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - newUser := new(model.User) - newUser.Username = creationForm.Username - newUser.Type = model.USER_TYPE_ENT - newUser.Enabled = true - newUserDetail := new(model.UserDetail) - newUserDetail.Name = &creationForm.Name - newUserDetail.Contact = creationForm.Contact - newUserDetail.Phone = creationForm.Phone - newUserDetail.UnitServiceFee, err = decimal.NewFromString(*creationForm.UnitServiceFee) - if err != nil { - return result.BadRequest("用户月服务费无法解析。") - } - newUserDetail.ServiceExpiration = model.NewEmptyDate() - - verifyCode, err := service.UserService.CreateUser(newUser, newUserDetail) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - cache.AbolishRelation("user") - return result.Json(http.StatusCreated, "用户已经成功创建。", fiber.Map{"verify": verifyCode}) -} - -type _AccountModificationFormData struct { - Name string `json:"name" form:"name"` - Region *string `json:"region" form:"region"` - Address *string `json:"address" form:"address"` - Contact *string `json:"contact" form:"contact"` - Phone *string `json:"phone" form:"phone"` - UnitServiceFee *string `json:"unitServiceFee" form:"unitServiceFee"` -} - -func modifyAccountDetail(c *fiber.Ctx) error { - result := response.NewResult(c) - targetUserId := c.Params("uid") - modForm := new(_AccountModificationFormData) - if err := c.BodyParser(modForm); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - exists, err := service.UserService.IsUserExists(targetUserId) - if !exists { - return result.NotFound("指定的用户不存在。") - } - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - newUserInfo := new(model.UserDetail) - newUserInfo.Id = targetUserId - newUserInfo.Name = &modForm.Name - if len(modForm.Name) > 0 { - abbr := tools.PinyinAbbr(modForm.Name) - newUserInfo.Abbr = &abbr - } - newUserInfo.Region = modForm.Region - newUserInfo.Address = modForm.Address - newUserInfo.Contact = modForm.Contact - newUserInfo.Phone = modForm.Phone - newUserInfo.UnitServiceFee, err = decimal.NewFromString(*modForm.UnitServiceFee) - if err != nil { - return result.BadRequest("用户月服务费无法解析。") - } - _, err = global.DB.NewUpdate().Model(newUserInfo). - WherePK(). - Column("name", "abbr", "region", "address", "contact", "phone", "unit_service_fee"). - Exec(c.Context()) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - cache.AbolishRelation(fmt.Sprintf("user:%s", targetUserId)) - return result.Updated("指定用户的信息已经更新。") -} - -func quickSearchEnterprise(c *fiber.Ctx) error { - result := response.NewResult(c) - keyword := c.Query("keyword") - searchResult, err := service.UserService.SearchLimitUsers(keyword, 6) - if err != nil { - return result.Error(http.StatusInternalServerError, err.Error()) - } - return result.Json(http.StatusOK, "已查询到存在符合条件的企业", fiber.Map{"users": searchResult}) -} - -func fetchExpiration(c *fiber.Ctx) error { - result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - return result.Unauthorized(err.Error()) - } - user, err := service.UserService.FetchUserDetail(session.Uid) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Json( - http.StatusOK, - "已经取得用户的服务期限信息", - fiber.Map{"expiration": user.ServiceExpiration.Format("2006-01-02")}, - ) -} diff --git a/controller/withdraw.go b/controller/withdraw.go deleted file mode 100644 index 4021a93..0000000 --- a/controller/withdraw.go +++ /dev/null @@ -1,81 +0,0 @@ -package controller - -import ( - "electricity_bill_calc/exceptions" - "electricity_bill_calc/response" - "electricity_bill_calc/security" - "electricity_bill_calc/service" - "net/http" - "strconv" - - "github.com/gofiber/fiber/v2" -) - -func InitializeWithdrawController(router *fiber.App) { - router.Delete("/publicity/:pid", security.EnterpriseAuthorize, applyReportWithdraw) - router.Get("/withdraws", security.OPSAuthorize, fetchWithdrawsWaitingAutdit) - router.Put("/withdraw/:rid", security.OPSAuthorize, auditWithdraw) -} - -func applyReportWithdraw(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("pid") - if ensure, err := ensureReportBelongs(c, &result, requestReportId); !ensure { - return err - } - deleted, err := service.WithdrawService.ApplyWithdraw(requestReportId) - if err != nil { - if nfErr, ok := err.(exceptions.NotFoundError); ok { - return result.NotFound(nfErr.Error()) - } else if ioErr, ok := err.(exceptions.ImproperOperateError); ok { - return result.NotAccept(ioErr.Error()) - } else { - return result.Error(http.StatusInternalServerError, err.Error()) - } - } - if !deleted { - return result.Error(http.StatusInternalServerError, "未能完成公示报表的申请撤回操作。") - } - return result.Success("指定的公示报表已经申请撤回。") -} - -func fetchWithdrawsWaitingAutdit(c *fiber.Ctx) error { - result := response.NewResult(c) - keyword := c.Query("keyword") - requestPage, err := strconv.Atoi(c.Query("page", "1")) - if err != nil { - return result.NotAccept("查询参数[page]格式不正确。") - } - reports, totalitems, err := service.WithdrawService.FetchPagedWithdrawApplies(requestPage, keyword) - if err != nil { - return result.NotFound(err.Error()) - } - return result.Json( - http.StatusOK, - "已经取得符合条件的等待审核的撤回申请。", - response.NewPagedResponse(requestPage, totalitems).ToMap(), - fiber.Map{"records": reports}, - ) -} - -type WithdrawAuditFormData struct { - Audit bool `json:"audit" form:"audit"` -} - -func auditWithdraw(c *fiber.Ctx) error { - result := response.NewResult(c) - requestReportId := c.Params("rid") - formData := new(WithdrawAuditFormData) - if err := c.BodyParser(formData); err != nil { - return result.UnableToParse("无法解析提交的数据。") - } - err := service.WithdrawService.AuditWithdraw(requestReportId, formData.Audit) - if err != nil { - if nfErr, ok := err.(exceptions.NotFoundError); ok { - return result.NotFound(nfErr.Error()) - } else { - return result.NotAccept(err.Error()) - } - } - return result.Success("指定公示报表的撤回申请已经完成审核") -} diff --git a/excel/abstract.go b/excel/abstract.go index 7d5c27a..2d98237 100644 --- a/excel/abstract.go +++ b/excel/abstract.go @@ -1,7 +1,6 @@ package excel import ( - "electricity_bill_calc/model" "electricity_bill_calc/tools" "encoding/json" "errors" @@ -19,7 +18,6 @@ import ( type ExcelTemplateGenerator interface { Close() WriteTo(w io.Writer) (int64, error) - WriteMeterData(meters []model.EndUserDetail) error } type ColumnRecognizer struct { diff --git a/excel/end_user.go b/excel/end_user.go deleted file mode 100644 index c051a33..0000000 --- a/excel/end_user.go +++ /dev/null @@ -1,35 +0,0 @@ -package excel - -import ( - "electricity_bill_calc/model" - "io" -) - -var ( - endUserNonPVRecognizers = []*ColumnRecognizer{ - {Pattern: []string{"电表编号"}, Tag: "meterId", MatchIndex: -1, MustFill: true}, - {Pattern: []string{"上期", "(总)"}, Tag: "lastPeriodOverall", MatchIndex: -1, MustFill: true}, - {Pattern: []string{"本期", "(总)"}, Tag: "currentPeriodOverall", MatchIndex: -1, MustFill: true}, - {Pattern: []string{"退补", "(总)"}, Tag: "adjustOverall", MatchIndex: -1, MustFill: true}, - } - endUserPVRecognizers = append( - endUserNonPVRecognizers, - &ColumnRecognizer{Pattern: []string{"上期", "(尖峰)"}, Tag: "lastPeriodCritical", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"上期", "(峰)"}, Tag: "lastPeriodPeak", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"上期", "(谷)"}, Tag: "lastPeriodValley", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"本期", "(尖峰)"}, Tag: "currentPeriodCritical", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"本期", "(峰)"}, Tag: "currentPeriodPeak", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"本期", "(谷)"}, Tag: "currentPeriodValley", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"退补", "(尖峰)"}, Tag: "adjustCritical", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"退补", "(峰)"}, Tag: "adjustPeak", MatchIndex: -1}, - &ColumnRecognizer{Pattern: []string{"退补", "(谷)"}, Tag: "adjustValley", MatchIndex: -1}, - ) -) - -func NewEndUserNonPVExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.EndUserImport], error) { - return NewExcelAnalyzer[model.EndUserImport](file, endUserNonPVRecognizers) -} - -func NewEndUserPVExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.EndUserImport], error) { - return NewExcelAnalyzer[model.EndUserImport](file, endUserPVRecognizers) -} diff --git a/excel/meter_archive.go b/excel/meter_archive.go deleted file mode 100644 index a8d8cd6..0000000 --- a/excel/meter_archive.go +++ /dev/null @@ -1,21 +0,0 @@ -package excel - -import ( - "electricity_bill_calc/model" - "io" -) - -var meter04kVExcelRecognizers = []*ColumnRecognizer{ - {Pattern: []string{"表号"}, Tag: "code", MatchIndex: -1, MustFill: true}, - {Pattern: []string{"户名"}, Tag: "name", MatchIndex: -1}, - {Pattern: []string{"户址"}, Tag: "address", MatchIndex: -1}, - {Pattern: []string{"联系人"}, Tag: "contact", MatchIndex: -1}, - {Pattern: []string{"电话"}, Tag: "phone", MatchIndex: -1}, - {Pattern: []string{"倍率"}, Tag: "ratio", MatchIndex: -1, MustFill: true}, - {Pattern: []string{"序号"}, Tag: "seq", MatchIndex: -1, MustFill: true}, - {Pattern: []string{"公用设备"}, Tag: "public", MatchIndex: -1, MustFill: true}, -} - -func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.Meter04KV], error) { - return NewExcelAnalyzer[model.Meter04KV](file, meter04kVExcelRecognizers) -} diff --git a/excel/meter_non_pv_template.go b/excel/meter_non_pv_template.go deleted file mode 100644 index 33392fd..0000000 --- a/excel/meter_non_pv_template.go +++ /dev/null @@ -1,90 +0,0 @@ -package excel - -import ( - "electricity_bill_calc/model" - "io" - - "github.com/xuri/excelize/v2" -) - -type MeterNonPVExcelTemplateGenerator struct { - file *excelize.File -} - -// 生成峰谷计量抄表Excel模板 -func NewMeterNonPVExcelTemplateGenerator() *MeterNonPVExcelTemplateGenerator { - return &MeterNonPVExcelTemplateGenerator{ - file: excelize.NewFile(), - } -} - -func (MeterNonPVExcelTemplateGenerator) titles() []interface{} { - return []interface{}{ - "序号", - "用户名称", - "户址", - "电表编号", - "倍率", - "上期表底(总)", - "本期表底(总)", - "退补电量(总)", - } -} - -func (t *MeterNonPVExcelTemplateGenerator) Close() { - t.file.Close() -} - -func (t MeterNonPVExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) { - return t.file.WriteTo(w) -} - -func (t *MeterNonPVExcelTemplateGenerator) WriteMeterData(meters []model.EndUserDetail) error { - defaultSheet := t.file.GetSheetName(0) - stream, err := t.file.NewStreamWriter(defaultSheet) - if err != nil { - return err - } - firstCell, err := excelize.CoordinatesToCellName(1, 1) - if err != nil { - return err - } - stream.SetColWidth(2, 4, 20) - stream.SetColWidth(6, 8, 15) - stream.SetRow(firstCell, t.titles(), excelize.RowOpts{Height: 20}) - - for index, meter := range meters { - firstCell, err := excelize.CoordinatesToCellName(1, index+2) - if err != nil { - return err - } - customerName := "" - if meter.CustomerName != nil { - customerName = *meter.CustomerName - } - customerAddress := "" - if meter.Address != nil { - customerAddress = *meter.Address - } - if err = stream.SetRow( - firstCell, - []interface{}{ - meter.Seq, - customerName, - customerAddress, - meter.MeterId, - meter.Ratio, - meter.LastPeriodOverall, - meter.CurrentPeriodOverall, - meter.AdjustOverall, - }, - excelize.RowOpts{Height: 15}, - ); err != nil { - return err - } - } - if err = stream.Flush(); err != nil { - return err - } - return nil -} diff --git a/excel/meter_pv_template.go b/excel/meter_pv_template.go deleted file mode 100644 index 0121699..0000000 --- a/excel/meter_pv_template.go +++ /dev/null @@ -1,108 +0,0 @@ -package excel - -import ( - "electricity_bill_calc/model" - "io" - - "github.com/xuri/excelize/v2" -) - -type MeterPVExcelTemplateGenerator struct { - file *excelize.File -} - -// 生成峰谷计量抄表Excel模板 -func NewMeterPVExcelTemplateGenerator() *MeterPVExcelTemplateGenerator { - return &MeterPVExcelTemplateGenerator{ - file: excelize.NewFile(), - } -} - -func (MeterPVExcelTemplateGenerator) titles() []interface{} { - return []interface{}{ - "序号", - "用户名称", - "户址", - "电表编号", - "倍率", - "上期表底(总)", - "本期表底(总)", - "上期表底(尖峰)", - "本期表底(尖峰)", - "上期表底(峰)", - "本期表底(峰)", - "上期表底(谷)", - "本期表底(谷)", - "退补电量(总)", - "退补电量(尖峰)", - "退补电量(峰)", - "退补电量(谷)", - } -} - -func (t *MeterPVExcelTemplateGenerator) Close() { - t.file.Close() -} - -func (t MeterPVExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) { - return t.file.WriteTo(w) -} - -func (t *MeterPVExcelTemplateGenerator) WriteMeterData(meters []model.EndUserDetail) error { - defaultSheet := t.file.GetSheetName(0) - stream, err := t.file.NewStreamWriter(defaultSheet) - if err != nil { - return err - } - firstCell, err := excelize.CoordinatesToCellName(1, 1) - if err != nil { - return err - } - stream.SetColWidth(2, 4, 20) - stream.SetColWidth(6, 17, 15) - stream.SetRow(firstCell, t.titles(), excelize.RowOpts{Height: 20}) - - for index, meter := range meters { - firstCell, err := excelize.CoordinatesToCellName(1, index+2) - if err != nil { - return err - } - customerName := "" - if meter.CustomerName != nil { - customerName = *meter.CustomerName - } - customerAddress := "" - if meter.Address != nil { - customerAddress = *meter.Address - } - if err = stream.SetRow( - firstCell, - []interface{}{ - meter.Seq, - customerName, - customerAddress, - meter.MeterId, - meter.Ratio, - meter.LastPeriodOverall, - meter.CurrentPeriodOverall, - meter.LastPeriodCritical, - meter.CurrentPeriodCritical, - meter.LastPeriodPeak, - meter.CurrentPeriodPeak, - meter.LastPeriodValley, - meter.CurrentPeriodValley, - meter.AdjustOverall, - meter.AdjustCritical, - meter.AdjustPeak, - meter.AdjustValley, - }, - excelize.RowOpts{Height: 15}, - ); err != nil { - return err - } - } - if err = stream.Flush(); err != nil { - return err - } - return nil -} diff --git a/global/db.go b/global/db.go index 17d01a3..6ed62cd 100644 --- a/global/db.go +++ b/global/db.go @@ -1,60 +1,92 @@ package global import ( - "database/sql" + "context" "fmt" "time" "electricity_bill_calc/config" "electricity_bill_calc/logger" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" - "go.uber.org/zap/zapcore" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/samber/lo" + "go.uber.org/zap" ) var ( - DB *bun.DB + DB *pgxpool.Pool ) func SetupDatabaseConnection() error { - // connStr := fmt.Sprintf( - // "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai connect_timeout=0 tcp_user_timeout=180000", - // config.DatabaseSettings.Host, - // config.DatabaseSettings.User, - // config.DatabaseSettings.Pass, - // config.DatabaseSettings.DB, - // config.DatabaseSettings.Port, - // ) - pgconn := pgdriver.NewConnector( - pgdriver.WithNetwork("tcp"), - pgdriver.WithAddr(fmt.Sprintf("%s:%d", config.DatabaseSettings.Host, - config.DatabaseSettings.Port)), - pgdriver.WithUser(config.DatabaseSettings.User), - pgdriver.WithInsecure(true), - pgdriver.WithPassword(config.DatabaseSettings.Pass), - pgdriver.WithDatabase(config.DatabaseSettings.DB), - pgdriver.WithDialTimeout(30*time.Second), - pgdriver.WithReadTimeout(3*time.Minute), - pgdriver.WithWriteTimeout(10*time.Minute), - ) - sqldb := sql.OpenDB(pgconn) - DB = bun.NewDB(sqldb, pgdialect.New()) - DB.AddQueryHook(logger.NewQueryHook(logger.QueryHookOptions{ - LogSlow: 3 * time.Second, - Logger: logger.Named("PG"), - QueryLevel: zapcore.DebugLevel, - ErrorLevel: zapcore.ErrorLevel, - SlowLevel: zapcore.WarnLevel, - ErrorTemplate: "{{.Operation}}[{{.Duration}}]: {{.Query}}: {{.Error}}", - MessageTemplate: "{{.Operation}}[{{.Duration}}]: {{.Query}}", - })) - DB.SetMaxIdleConns(config.DatabaseSettings.MaxIdleConns) - DB.SetMaxOpenConns(config.DatabaseSettings.MaxOpenConns) - DB.SetConnMaxIdleTime(10 * time.Minute) - DB.SetConnMaxLifetime(60 * time.Minute) - DB.Ping() + connConfig := &pgx.ConnConfig{ + Config: pgconn.Config{ + Host: config.DatabaseSettings.Host, + Port: uint16(config.DatabaseSettings.Port), + User: config.DatabaseSettings.User, + Password: config.DatabaseSettings.Pass, + Database: config.DatabaseSettings.DB, + TLSConfig: nil, + ConnectTimeout: 0 * time.Second, + RuntimeParams: map[string]string{"application_name": "elec_service_go"}, + }, + Tracer: QueryLogger{ + logger: logger.Named("PG"), + }, + } + poolConfig := &pgxpool.Config{ + ConnConfig: connConfig, + MaxConnLifetime: 60 * time.Minute, + MaxConnIdleTime: 10 * time.Minute, + HealthCheckPeriod: 10 * time.Second, + MaxConns: int32(config.DatabaseSettings.MaxOpenConns), + MinConns: int32(config.DatabaseSettings.MaxIdleConns), + } + DB, _ = pgxpool.NewWithConfig(context.Background(), poolConfig) return nil } + +type QueryLogger struct { + logger *zap.Logger +} + +func (ql QueryLogger) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context { + ql.logger.Info(fmt.Sprintf("将要执行查询: %s", data.SQL)) + ql.logger.Info("查询参数", lo.Map(data.Args, func(elem any, index int) zap.Field { + return zap.Any(fmt.Sprintf("[Arg %d]: ", index), elem) + })...) + return ctx +} + +func (ql QueryLogger) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) { + var logFunc func(string, ...zap.Field) + var templateString string + if data.Err != nil { + logFunc = ql.logger.Error + templateString = "命令 [%s] 执行失败。" + } else { + logFunc = ql.logger.Info + templateString = "命令 [%s] 执行成功。" + } + switch { + case data.CommandTag.Update(): + fallthrough + case data.CommandTag.Delete(): + fallthrough + case data.CommandTag.Insert(): + logFunc( + fmt.Sprintf(templateString, data.CommandTag.String()), + zap.Error(data.Err), + zap.Any("affected", data.CommandTag.RowsAffected())) + case data.CommandTag.Select(): + logFunc( + fmt.Sprintf(templateString, data.CommandTag.String()), + zap.Error(data.Err)) + default: + logFunc( + fmt.Sprintf(templateString, data.CommandTag.String()), + zap.Error(data.Err)) + } +} diff --git a/go.mod b/go.mod index 9abd945..09bfea7 100644 --- a/go.mod +++ b/go.mod @@ -22,14 +22,21 @@ require ( require ( github.com/andybalholm/brotli v1.0.4 // indirect + github.com/georgysavva/scany/v2 v2.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jackc/puddle/v2 v2.2.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/klauspost/compress v1.15.0 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sync v0.1.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect mellium.im/sasl v0.3.0 // indirect ) @@ -59,11 +66,11 @@ require ( github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect - golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect + golang.org/x/crypto v0.6.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect - golang.org/x/sys v0.0.0-20220907062415-87db552b00fd // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/net v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 846c8ed..f0bfce6 100644 --- a/go.sum +++ b/go.sum @@ -66,9 +66,12 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fufuok/utils v0.7.13 h1:FGx8Mnfg0ZB8HdVz1X60JJ2kFu1rtcsFDYUxUTzNKkU= github.com/fufuok/utils v0.7.13/go.mod h1:ztIaorPqZGdbvmW3YlwQp80K8rKJmEy6xa1KwpJSsmk= +github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU= +github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -135,10 +138,20 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk= +github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -156,8 +169,10 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/liamylian/jsontime/v2 v2.0.0 h1:3if2kDW/boymUdO+4Qj/m4uaXMBSF6np9KEgg90cwH0= github.com/liamylian/jsontime/v2 v2.0.0/go.mod h1:UHp1oAPqCBfspokvGmaGe0IAl2IgOpgOgDaKPcvcGGY= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -190,6 +205,8 @@ github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rueian/rueidis v0.0.73 h1:+r0Z6C6HMnkquPgY3zaHVpTqmCyJL56Z36GSlyBrufk= github.com/rueian/rueidis v0.0.73/go.mod h1:FwnfDILF2GETrvXcYFlhIiru/7NmSIm1f+7C5kutO0I= github.com/samber/lo v1.27.0 h1:GOyDWxsblvqYobqsmUuMddPa2/mMzkKyojlXol4+LaQ= @@ -216,6 +233,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= @@ -272,6 +290,10 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -344,6 +366,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -363,6 +387,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -404,6 +430,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U= golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -415,6 +443,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/logger/bunhook.go b/logger/bunhook.go deleted file mode 100644 index e29ce1a..0000000 --- a/logger/bunhook.go +++ /dev/null @@ -1,163 +0,0 @@ -package logger - -import ( - "bytes" - "context" - "database/sql" - "fmt" - "strings" - "text/template" - "time" - - "github.com/uptrace/bun" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -// QueryHookOptions logging options -type QueryHookOptions struct { - LogSlow time.Duration - Logger *zap.Logger - QueryLevel zapcore.Level - SlowLevel zapcore.Level - ErrorLevel zapcore.Level - MessageTemplate string - ErrorTemplate string -} - -// QueryHook wraps query hook -type QueryHook struct { - opts QueryHookOptions - errorTemplate *template.Template - messageTemplate *template.Template -} - -// LogEntryVars variables made available t otemplate -type LogEntryVars struct { - Timestamp time.Time - Query string - Operation string - Duration time.Duration - Error error -} - -// NewQueryHook returns new instance -func NewQueryHook(opts QueryHookOptions) *QueryHook { - h := new(QueryHook) - - if opts.ErrorTemplate == "" { - opts.ErrorTemplate = "{{.Operation}}[{{.Duration}}]: {{.Query}}: {{.Error}}" - } - if opts.MessageTemplate == "" { - opts.MessageTemplate = "{{.Operation}}[{{.Duration}}]: {{.Query}}" - } - h.opts = opts - errorTemplate, err := template.New("ErrorTemplate").Parse(h.opts.ErrorTemplate) - if err != nil { - panic(err) - } - messageTemplate, err := template.New("MessageTemplate").Parse(h.opts.MessageTemplate) - if err != nil { - panic(err) - } - - h.errorTemplate = errorTemplate - h.messageTemplate = messageTemplate - return h -} - -// BeforeQuery does nothing tbh -func (h *QueryHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context { - return ctx -} - -// AfterQuery convert a bun QueryEvent into a logrus message -func (h *QueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) { - var level zapcore.Level - var isError bool - var msg bytes.Buffer - - now := time.Now() - dur := now.Sub(event.StartTime) - - switch event.Err { - case nil, sql.ErrNoRows: - isError = false - if h.opts.LogSlow > 0 && dur >= h.opts.LogSlow { - level = h.opts.SlowLevel - } else { - level = h.opts.QueryLevel - } - default: - isError = true - level = h.opts.ErrorLevel - } - if level == 0 { - return - } - - args := &LogEntryVars{ - Timestamp: now, - Query: string(event.Query), - Operation: eventOperation(event), - Duration: dur, - Error: event.Err, - } - - if isError { - if err := h.errorTemplate.Execute(&msg, args); err != nil { - panic(err) - } - } else { - if err := h.messageTemplate.Execute(&msg, args); err != nil { - panic(err) - } - } - - switch level { - case zapcore.DebugLevel: - h.opts.Logger.Debug(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs)) - case zapcore.InfoLevel: - h.opts.Logger.Info(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs)) - case zapcore.WarnLevel: - h.opts.Logger.Warn(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs)) - case zapcore.ErrorLevel: - h.opts.Logger.Error(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs), zap.Error(event.Err)) - case zapcore.FatalLevel: - h.opts.Logger.Fatal(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs), zap.Error(event.Err)) - case zapcore.PanicLevel: - h.opts.Logger.Panic(msg.String(), zap.String("query", event.Query), zap.Any("args", event.QueryArgs), zap.Error(event.Err)) - default: - panic(fmt.Errorf("unsupported level: %v", level)) - } -} - -// taken from bun -func eventOperation(event *bun.QueryEvent) string { - switch event.QueryAppender.(type) { - case *bun.SelectQuery: - return "SELECT" - case *bun.InsertQuery: - return "INSERT" - case *bun.UpdateQuery: - return "UPDATE" - case *bun.DeleteQuery: - return "DELETE" - case *bun.CreateTableQuery: - return "CREATE TABLE" - case *bun.DropTableQuery: - return "DROP TABLE" - } - return queryOperation(event.Query) -} - -// taken from bun -func queryOperation(name string) string { - if idx := strings.Index(name, " "); idx > 0 { - name = name[:idx] - } - if len(name) > 16 { - name = name[:16] - } - return string(name) -} diff --git a/main.go b/main.go index cb7965f..37628ab 100644 --- a/main.go +++ b/main.go @@ -1,25 +1,17 @@ package main import ( - "database/sql" "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" - "electricity_bill_calc/migration" "electricity_bill_calc/model" "electricity_bill_calc/router" "electricity_bill_calc/service" - "encoding/csv" "fmt" - "io" - "os" - "strconv" "time" - "github.com/samber/lo" "github.com/shopspring/decimal" - "github.com/uptrace/bun/migrate" "go.uber.org/zap" ) @@ -27,98 +19,27 @@ func init() { l := logger.Named("Init") err := config.SetupSetting() if err != nil { - l.Fatal("Configuration load failed.", zap.Error(err)) + l.Fatal("服务配置文件加载失败!", zap.Error(err)) } - l.Info("Configuration loaded!") + l.Info("服务配置已经完成加载。") err = global.SetupDatabaseConnection() if err != nil { - l.Fatal("Main Database connect failed.", zap.Error(err)) - } - l.Info("Main Database connected!") - - migrator := migrate.NewMigrator(global.DB, migration.Migrations) - ctx, cancel := global.TimeoutContext(12) - defer cancel() - err = migrator.Init(ctx) - if err != nil { - l.Fatal("Database migration unable to initialized.", zap.Error(err)) - } - group, err := migrator.Migrate(ctx) - if err != nil { - l.Fatal("Database migrate failed.", zap.Error(err)) - } - if group.IsZero() { - l.Info("There are no new migrations to run (database is up to date)") + l.Fatal("主数据库连接失败!", zap.Error(err)) } + l.Info("主数据库已经连接。") err = global.SetupRedisConnection() if err != nil { - l.Fatal("Main Cache Database connect failed.", zap.Error(err)) + l.Fatal("主缓存数据库连接失败!", zap.Error(err)) } - l.Info("Main Cache Database connected!") - - err = initializeRegions() - if err != nil { - l.Fatal("Regions initialize failed.", zap.Error(err)) - } - l.Info("Regions synchronized.") + l.Info("主缓存数据库已经连接。") err = intializeSingularity() if err != nil { - l.Fatal("Singularity account intialize failed.", zap.Error(err)) + l.Fatal("奇点账号初始化失败。", zap.Error(err)) } - l.Info("Singularity account intialized.") -} - -func initializeRegions() error { - ctx, cancel := global.TimeoutContext() - defer cancel() - logger.Info("Synchronize regions...") - regionCsvFile, err := os.Open("regions.csv") - if err != nil { - return fmt.Errorf("region initialize file is not found: %w", err) - } - defer regionCsvFile.Close() - - var existRegions = make([]string, 0) - err = global.DB.NewSelect().Model((*model.Region)(nil)). - Column("code"). - Scan(ctx, &existRegions) - if err != nil { - return fmt.Errorf("unable to retreive regions from database: %w", err) - } - - regionCsv := csv.NewReader(regionCsvFile) - transaction, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return fmt.Errorf("unable to intiate database transaction: %w", err) - } - for { - record, err := regionCsv.Read() - if err == io.EOF { - break - } - if lo.Contains(existRegions, record[0]) { - continue - } - level, err := strconv.Atoi(record[2]) - if err != nil { - continue - } - if _, err := transaction.NewInsert().Model(&model.Region{ - Code: record[0], - Name: record[1], - Level: level, - Parent: record[3], - }).Exec(ctx); err != nil { - return fmt.Errorf("region synchronize in failed: %v, %w", record, err) - } - } - if err = transaction.Commit(); err != nil { - return fmt.Errorf("synchronize regions to database failed: %w", err) - } - return nil + l.Info("奇点账号已经完成初始化。") } func intializeSingularity() error { @@ -159,7 +80,9 @@ func intializeSingularity() error { func DBConnectionKeepLive() { for range time.Tick(30 * time.Second) { - err := global.DB.Ping() + ctx, cancel := global.TimeoutContext() + defer cancel() + err := global.DB.Ping(ctx) if err != nil { continue } diff --git a/model/end_user_detail.go b/model/end_user_detail.go deleted file mode 100644 index 4c93e52..0000000 --- a/model/end_user_detail.go +++ /dev/null @@ -1,133 +0,0 @@ -package model - -import ( - "context" - "errors" - "time" - - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -type EndUserDetail struct { - bun.BaseModel `bun:"table:end_user_detail,alias:eud"` - CreatedAndModified `bun:"extend"` - ReportId string `bun:",pk,notnull" json:"reportId"` - ParkId string `bun:",pk,notnull" json:"parkId"` - MeterId string `bun:"meter_04kv_id,pk,notnull" json:"meterId"` - Seq int64 `bun:"type:bigint,notnull" json:"seq"` - Ratio decimal.Decimal `bun:"type:numeric,notnull" json:"ratio"` - Address *string `json:"address"` - CustomerName *string `json:"customerName"` - ContactName *string `json:"contactName"` - ContactPhone *string `json:"contactPhone"` - IsPublicMeter bool `bun:"public_meter,notnull" json:"isPublicMeter"` - LastPeriodOverall decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodOverall"` - LastPeriodCritical decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodCritical"` - LastPeriodPeak decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodPeak"` - LastPeriodFlat decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodFlat"` - LastPeriodValley decimal.Decimal `bun:"type:numeric,notnull" json:"lastPeriodValley"` - CurrentPeriodOverall decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodOverall"` - CurrentPeriodCritical decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodCritical"` - CurrentPeriodPeak decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodPeak"` - CurrentPeriodFlat decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodFlat"` - CurrentPeriodValley decimal.Decimal `bun:"type:numeric,notnull" json:"currentPeriodValley"` - AdjustOverall decimal.Decimal `bun:"type:numeric,notnull" json:"adjustOverall"` - AdjustCritical decimal.Decimal `bun:"type:numeric,notnull" json:"adjustCritical"` - AdjustPeak decimal.Decimal `bun:"type:numeric,notnull" json:"adjustPeak"` - AdjustFlat decimal.Decimal `bun:"type:numeric,notnull" json:"adjustFlat"` - AdjustValley decimal.Decimal `bun:"type:numeric,notnull" json:"adjustValley"` - Overall decimal.NullDecimal `bun:"type:numeric" json:"overall"` - OverallFee decimal.NullDecimal `bun:"type:numeric" json:"overallFee"` - OverallProportion decimal.Decimal `bun:"type:numeric,notnull" json:"-"` - Critical decimal.NullDecimal `bun:"type:numeric" json:"critical"` - CriticalFee decimal.NullDecimal `bun:"type:numeric" json:"criticalFee"` - Peak decimal.NullDecimal `bun:"type:numeric" json:"peak"` - PeakFee decimal.NullDecimal `bun:"type:numeric" json:"peakFee"` - Flat decimal.NullDecimal `bun:"type:numeric" json:"flat"` - FlatFee decimal.NullDecimal `bun:"type:numeric" json:"flatFee"` - Valley decimal.NullDecimal `bun:"type:numeric" json:"valley"` - ValleyFee decimal.NullDecimal `bun:"type:numeric" json:"valleyFee"` - BasicFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"basicFeeDiluted"` - AdjustFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"adjustFeeDiluted"` - LossDiluted decimal.NullDecimal `bun:"type:numeric" json:"lossDiluted"` - LossFeeDiluted decimal.NullDecimal `bun:"type:numeric" json:"lossFeeDiluted"` - FinalDiluted decimal.NullDecimal `bun:"type:numeric" json:"finalDiluted"` - FinalCharge decimal.NullDecimal `bun:"type:numeric" json:"finalCharge"` - Initialize bool `bun:"-" json:"-"` - Origin *Meter04KV `bun:"rel:belongs-to,join:park_id=park_id,join:meter_04kv_id=code" json:"-"` - Report *Report `bun:"rel:belongs-to,join:report_id=id" json:"-"` - Park *Park `bun:"rel:belongs-to,join:park_id=id" json:"-"` -} - -var _ bun.BeforeAppendModelHook = (*EndUserDetail)(nil) - -func (d *EndUserDetail) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - d.CreatedAt = oprTime - d.LastModifiedAt = &oprTime - case *bun.UpdateQuery: - d.LastModifiedAt = &oprTime - } - return nil -} - -func (d EndUserDetail) Validate() (bool, error) { - lastPeriodSum := decimal.Sum(d.LastPeriodCritical, d.LastPeriodPeak, d.LastPeriodValley) - if lastPeriodSum.GreaterThan(d.LastPeriodOverall) { - return false, errors.New("上期峰谷计量总量大于上期总计电量") - } - currentPeriodSum := decimal.Sum(d.CurrentPeriodCritical, d.CurrentPeriodPeak, d.CurrentPeriodValley) - if currentPeriodSum.GreaterThan(d.CurrentPeriodOverall) { - return false, errors.New("本期峰谷计量总量大于本期总计电量") - } - return true, nil -} - -func (d *EndUserDetail) CalculatePeriod() { - d.LastPeriodFlat = d.LastPeriodOverall.Sub(d.LastPeriodCritical).Sub(d.LastPeriodPeak).Sub(d.LastPeriodValley) - d.CurrentPeriodFlat = d.CurrentPeriodOverall.Sub(d.CurrentPeriodCritical).Sub(d.CurrentPeriodPeak).Sub(d.CurrentPeriodValley) - d.Overall = decimal.NewNullDecimal(d.CurrentPeriodOverall.Sub(d.LastPeriodOverall).Mul(d.Ratio).Add(d.AdjustOverall).RoundBank(2)) - d.Critical = decimal.NewNullDecimal(d.CurrentPeriodCritical.Sub(d.LastPeriodCritical).Mul(d.Ratio).Add(d.AdjustCritical).RoundBank(2)) - d.Peak = decimal.NewNullDecimal(d.CurrentPeriodPeak.Sub(d.LastPeriodPeak).Mul(d.Ratio).Add(d.AdjustPeak).RoundBank(2)) - d.Flat = decimal.NewNullDecimal(d.CurrentPeriodFlat.Sub(d.LastPeriodFlat).Mul(d.Ratio).Add(d.AdjustFlat).RoundBank(2)) - d.Valley = decimal.NewNullDecimal(d.CurrentPeriodValley.Sub(d.LastPeriodValley).Mul(d.Ratio).Add(d.AdjustValley).RoundBank(2)) -} - -type EndUserImport struct { - MeterId string `excel:"meterId"` - LastPeriodOverall decimal.Decimal `excel:"lastPeriodOverall"` - CurrentPeriodOverall decimal.Decimal `excel:"currentPeriodOverall"` - LastPeriodCritical decimal.NullDecimal `excel:"lastPeriodCritical"` - LastPeriodPeak decimal.NullDecimal `excel:"lastPeriodPeak"` - LastPeriodValley decimal.NullDecimal `excel:"lastPeriodValley"` - CurrentPeriodCritical decimal.NullDecimal `excel:"currentPeriodCritical"` - CurrentPeriodPeak decimal.NullDecimal `excel:"currentPeriodPeak"` - CurrentPeriodValley decimal.NullDecimal `excel:"currentPeriodValley"` - AdjustOverall decimal.Decimal `excel:"adjustOverall"` - AdjustCritical decimal.NullDecimal `excel:"adjustCritical"` - AdjustPeak decimal.NullDecimal `excel:"adjustPeak"` - AdjustFlat decimal.NullDecimal `excel:"adjustFlat"` - AdjustValley decimal.NullDecimal `excel:"adjustValley"` -} - -type EndUserPeriodStat struct { - CustomerName string `json:"customerName"` - Address string `json:"address"` - ParkId string `json:"parkId"` - MeterId string `bun:"meter_04kv_id" json:"meterId"` - IsPublicMeter bool `bun:"public_meter" json:"isPublicMeter"` - Kind int8 `bun:"-" json:"pvKind"` - Overall decimal.NullDecimal `json:"overall"` - Critical decimal.NullDecimal `json:"critical"` - Peak decimal.NullDecimal `json:"peak"` - Valley decimal.NullDecimal `json:"valley"` - OverallFee decimal.NullDecimal `json:"overallFee"` - CriticalFee decimal.NullDecimal `json:"criticalFee"` - PeakFee decimal.NullDecimal `json:"peakFee"` - ValleyFee decimal.NullDecimal `json:"valleyFee"` - AdjustFee decimal.NullDecimal `bun:"final_diluted" json:"adjustFee"` - AdjustProportion decimal.NullDecimal `bun:"-" json:"adjustProportion"` -} diff --git a/model/maintenance_fee.go b/model/maintenance_fee.go deleted file mode 100644 index 47dcb8a..0000000 --- a/model/maintenance_fee.go +++ /dev/null @@ -1,48 +0,0 @@ -package model - -import ( - "context" - "time" - - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -type MaintenanceFee struct { - bun.BaseModel `bun:"table:maintenance_fee,alias:m"` - CreatedAndModified `bun:"extend"` - Deleted `bun:"extend"` - Id string `bun:",pk,notnull" json:"id"` - ParkId string `bun:",notnull" json:"parkId"` - Name string `bun:",notnull" json:"name"` - Period string `bun:",notnull" json:"period"` - Fee decimal.Decimal `bun:"type:numeric,notnull" json:"fee"` - Memo *string `bun:"type:text" json:"memo"` - Enabled bool `bun:",notnull" json:"enabled"` - Park Park `bun:"rel:belongs-to,join:park_id=id"` -} - -var _ bun.BeforeAppendModelHook = (*MaintenanceFee)(nil) - -func (f *MaintenanceFee) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - f.CreatedAt = oprTime - f.LastModifiedAt = &oprTime - case *bun.UpdateQuery: - f.LastModifiedAt = &oprTime - } - return nil -} - -type AdditionalCharge struct { - ParkId string `json:"parkId"` - Period string `json:"period"` - Fee decimal.Decimal `json:"fee"` - Price decimal.Decimal `json:"price"` - QuarterPrice decimal.Decimal `json:"quarterPrice"` - SemiAnnualPrice decimal.Decimal `json:"semiAnnualPrice"` - Enterprise UserDetailSimplified `json:"user"` - Park Park `json:"park"` -} diff --git a/model/meter_04kv.go b/model/meter_04kv.go deleted file mode 100644 index 407f79b..0000000 --- a/model/meter_04kv.go +++ /dev/null @@ -1,39 +0,0 @@ -package model - -import ( - "context" - "time" - - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -type Meter04KV struct { - bun.BaseModel `bun:"table:meter_04kv,alias:mt"` - CreatedAndModified `bun:"extend"` - Code string `bun:",pk,notnull" json:"code" excel:"code"` - ParkId string `bun:",pk,notnull" json:"parkId"` - Address *string `json:"address" excel:"address"` - CustomerName *string `json:"customerName" excel:"name"` - ContactName *string `json:"contactName" excel:"contact"` - ContactPhone *string `json:"contactPhone" excel:"phone"` - Ratio decimal.Decimal `bun:"type:numeric,notnull" json:"ratio" excel:"ratio"` - Seq int64 `bun:"type:bigint,notnull" json:"seq" excel:"seq"` - IsPublicMeter bool `bun:"public_meter,notnull" json:"isPublicMeter" excel:"public"` - Enabled bool `bun:",notnull" json:"enabled"` - ParkDetail *Park `bun:"rel:belongs-to,join:park_id=id" json:"-"` -} - -var _ bun.BeforeAppendModelHook = (*Meter04KV)(nil) - -func (m *Meter04KV) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - m.CreatedAt = oprTime - m.LastModifiedAt = &oprTime - case *bun.UpdateQuery: - m.LastModifiedAt = &oprTime - } - return nil -} diff --git a/model/park.go b/model/park.go deleted file mode 100644 index 326ee6d..0000000 --- a/model/park.go +++ /dev/null @@ -1,89 +0,0 @@ -package model - -import ( - "context" - "time" - - "github.com/jinzhu/copier" - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -const ( - CATEGORY_TWO_PART int8 = iota - CATEGORY_SINGLE_PV - CATEGORY_SINGLE_NON_PV -) - -const ( - CUSTOMER_METER_NON_PV int8 = iota - CUSTOMER_METER_PV -) - -type Park struct { - bun.BaseModel `bun:"table:park,alias:p"` - CreatedAndModified `bun:"extend"` - Deleted `bun:"extend"` - Id string `bun:",pk,notnull" json:"id"` - UserId string `bun:",notnull" json:"userId"` - Name string `bun:",notnull" json:"name"` - Abbr *string `json:"abbr"` - Area decimal.NullDecimal `bun:"type:numeric" json:"area"` - TenementQuantity decimal.NullDecimal `bun:"type:numeric" json:"tenement"` - Capacity decimal.NullDecimal `bun:"type:numeric" json:"capacity"` - Category int8 `bun:"type:smallint,notnull" json:"category"` - SubmeterType int8 `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"` - Region *string `json:"region"` - Address *string `json:"address"` - Contact *string `json:"contact"` - Phone *string `json:"phone"` - Enabled bool `bun:",notnull" json:"enabled"` - EnterpriseIndex *User `bun:"rel:belongs-to,join:user_id=id" json:"-"` - Enterprise *UserDetail `bun:"rel:belongs-to,join:user_id=id" json:"-"` - MaintenanceFees []*MaintenanceFee `bun:"rel:has-many,join:id=park_id" json:"-"` - Meters []*Meter04KV `bun:"rel:has-many,join:id=park_id" json:"-"` - Reports []*Report `bun:"rel:has-many,join:id=park_id" json:"-"` -} - -type ParkSimplified struct { - bun.BaseModel `bun:"table:park,alias:p"` - Id string `bun:",pk,notnull" json:"id"` - UserId string `bun:",notnull" json:"userId"` - Name string `bun:",notnull" json:"name"` - Abbr *string `json:"abbr"` - Area decimal.NullDecimal `json:"area"` - TenementQuantity decimal.NullDecimal `json:"tenement"` - Capacity decimal.NullDecimal `json:"capacity"` - Category int8 `bun:"type:smallint,notnull" json:"category"` - SubmeterType int8 `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"` - Region *string `json:"region"` - Address *string `json:"address"` - Contact *string `json:"contact"` - Phone *string `json:"phone"` -} - -type ParkPeriodStatistics struct { - Id string `bun:"park__id,notnull" json:"id"` - Name string `bun:"park__name,notnull" json:"name"` - Period *Date `bun:"type:date" json:"period"` -} - -func FromPark(park Park) ParkSimplified { - dest := ParkSimplified{} - copier.Copy(&dest, park) - return dest -} - -var _ bun.BeforeAppendModelHook = (*Park)(nil) - -func (p *Park) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - p.CreatedAt = oprTime - p.LastModifiedAt = &oprTime - case *bun.UpdateQuery: - p.LastModifiedAt = &oprTime - } - return nil -} diff --git a/model/publicity.go b/model/publicity.go deleted file mode 100644 index 088e3cd..0000000 --- a/model/publicity.go +++ /dev/null @@ -1,116 +0,0 @@ -package model - -import "github.com/shopspring/decimal" - -type PaidPart struct { - Overall decimal.Decimal `json:"overall"` - OverallPrice decimal.Decimal `json:"overallPrice"` - ConsumptionFee decimal.Decimal `json:"consumptionFee"` - OverallFee decimal.Decimal `json:"overallFee"` - Critical decimal.NullDecimal `json:"critical"` - CriticalPrice decimal.NullDecimal `json:"criticalPrice"` - CriticalFee decimal.NullDecimal `json:"criticalFee"` - Peak decimal.NullDecimal `json:"peak"` - PeakPrice decimal.NullDecimal `json:"peakPrice"` - PeakFee decimal.NullDecimal `json:"peakFee"` - Flat decimal.NullDecimal `json:"flat"` - FlatPrice decimal.NullDecimal `json:"flatPrice"` - FlatFee decimal.NullDecimal `json:"flatFee"` - Valley decimal.NullDecimal `json:"valley"` - ValleyPrice decimal.NullDecimal `json:"valleyPrice"` - ValleyFee decimal.NullDecimal `json:"valleyFee"` - BasicFee decimal.Decimal `json:"basicFee"` - AdjustFee decimal.Decimal `json:"adjustFee"` -} - -type EndUserOverallPart struct { - Overall decimal.Decimal `json:"overall"` - OverallPrice decimal.Decimal `json:"overallPrice"` - OverallFee decimal.Decimal `json:"consumptionFee"` - Critical decimal.NullDecimal `json:"critical"` - CriticalPrice decimal.NullDecimal `json:"criticalPrice"` - CriticalFee decimal.NullDecimal `json:"criticalFee"` - Peak decimal.NullDecimal `json:"peak"` - PeakPrice decimal.NullDecimal `json:"peakPrice"` - PeakFee decimal.NullDecimal `json:"peakFee"` - Flat decimal.NullDecimal `json:"flat"` - FlatPrice decimal.NullDecimal `json:"flatPrice"` - FlatFee decimal.NullDecimal `json:"flatFee"` - Valley decimal.NullDecimal `json:"valley"` - ValleyPrice decimal.NullDecimal `json:"valleyPrice"` - ValleyFee decimal.NullDecimal `json:"valleyFee"` -} - -type ConsumptionOverallPart struct { - Overall decimal.Decimal `json:"overall"` - OverallPrice decimal.Decimal `json:"overallPrice"` - ConsumptionFee decimal.Decimal `json:"consumptionFee"` - OverallFee decimal.Decimal `json:"overallFee"` - Critical decimal.NullDecimal `json:"critical"` - CriticalPrice decimal.NullDecimal `json:"criticalPrice"` - CriticalFee decimal.NullDecimal `json:"criticalFee"` - Peak decimal.NullDecimal `json:"peak"` - PeakPrice decimal.NullDecimal `json:"peakPrice"` - PeakFee decimal.NullDecimal `json:"peakFee"` - Flat decimal.NullDecimal `json:"flat"` - FlatPrice decimal.NullDecimal `json:"flatPrice"` - FlatFee decimal.NullDecimal `json:"flatFee"` - Valley decimal.NullDecimal `json:"valley"` - ValleyPrice decimal.NullDecimal `json:"valleyPrice"` - ValleyFee decimal.NullDecimal `json:"valleyFee"` - Proportion decimal.Decimal `json:"proportion"` -} - -type LossPart struct { - Quantity decimal.Decimal `json:"quantity"` - Price decimal.Decimal `json:"price"` - ConsumptionFee decimal.Decimal `json:"consumptionFee"` - Proportion decimal.Decimal `json:"proportion"` - AuthorizeQuantity decimal.Decimal `json:"authorizeQuantity"` - AuthorizeConsumptionFee decimal.Decimal `json:"authorizeConsumptionFee"` -} - -type OtherShouldCollectionPart struct { - LossFee decimal.NullDecimal `json:"lossFee"` - BasicFees decimal.Decimal `json:"basicFees"` -} - -type MaintenancePart struct { - BasicFees decimal.Decimal `json:"basicFees"` - LossFee decimal.Decimal `json:"lossFee"` - AdjustFee decimal.Decimal `json:"adjustFee"` - LossProportion decimal.Decimal `json:"lossProportion"` - AdjustProportion decimal.Decimal `json:"adjustProportion"` - AdjustPrice decimal.Decimal `json:"adjustPrice"` -} - -type EndUserSummary struct { - CustomerName *string `json:"customerName"` - Address *string `json:"address"` - MeterId string `json:"meterId"` - IsPublicMeter bool `json:"isPublicMeter"` - Overall decimal.Decimal `json:"overall"` - OverallPrice decimal.Decimal `json:"overallPrice"` - OverallFee decimal.Decimal `json:"overallFee"` - Critical decimal.NullDecimal `json:"critical"` - CriticalFee decimal.NullDecimal `json:"criticalFee"` - Peak decimal.NullDecimal `json:"peak"` - PeakFee decimal.NullDecimal `json:"peakFee"` - Valley decimal.NullDecimal `json:"valley"` - ValleyFee decimal.NullDecimal `json:"valleyFee"` - Loss decimal.Decimal `json:"loss"` - LossFee decimal.Decimal `json:"lossFee"` -} - -type Publicity struct { - Report Report `json:"index"` - User UserDetail `json:"enterprise"` - Park Park `json:"park"` - Paid PaidPart `json:"paid"` - EndUser ConsumptionOverallPart `json:"endUserSum"` - Loss LossPart `json:"loss"` - PublicConsumptionOverall ConsumptionOverallPart `json:"public"` - OtherCollections OtherShouldCollectionPart `json:"others"` - Maintenance MaintenancePart `json:"maintenance"` - EndUserDetails []EndUserSummary `json:"endUser"` -} diff --git a/model/region.go b/model/region.go deleted file mode 100644 index 8fecddf..0000000 --- a/model/region.go +++ /dev/null @@ -1,11 +0,0 @@ -package model - -import "github.com/uptrace/bun" - -type Region struct { - bun.BaseModel `bun:"table:region,alias:r"` - Code string `bun:",pk,notnull" json:"code"` - Name string `bun:",notnull" json:"name"` - Level int `bun:",notnull" json:"level"` - Parent string `bun:",notnull" json:"parent"` -} diff --git a/model/report.go b/model/report.go deleted file mode 100644 index 0192b6d..0000000 --- a/model/report.go +++ /dev/null @@ -1,99 +0,0 @@ -package model - -import ( - "context" - "time" - - "github.com/uptrace/bun" -) - -const ( - REPORT_NOT_WITHDRAW int8 = iota - REPORT_WITHDRAW_APPLIED - REPORT_WITHDRAW_DENIED - REPORT_WITHDRAW_GRANTED -) - -type Report struct { - bun.BaseModel `bun:"table:report,alias:r"` - CreatedAndModified `bun:"extend"` - Id string `bun:",pk,notnull" json:"id"` - ParkId string `bun:",notnull" json:"parkId"` - Period time.Time `bun:"type:date,notnull" json:"period" time_format:"simple_date" time_location:"shanghai"` - Category int8 `bun:"type:smallint,notnull" json:"category"` - SubmeterType int8 `bun:"meter_04kv_type,type:smallint,notnull" json:"meter04kvType"` - StepState Steps `bun:"type:jsonb,notnull" json:"stepState"` - Published bool `bun:",notnull" json:"published"` - PublishedAt *time.Time `bun:"type:timestamptz,nullzero" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"` - Withdraw int8 `bun:"type:smallint,notnull" json:"withdraw"` - LastWithdrawAppliedAt *time.Time `bun:"type:timestamptz,nullzero" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"` - LastWithdrawAuditAt *time.Time `bun:"type:timestamptz,nullzero" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"` - Park *Park `bun:"rel:belongs-to,join:park_id=id" json:"-"` - Summary *ReportSummary `bun:"rel:has-one,join:id=report_id" json:"-"` - WillDilutedFees []*WillDilutedFee `bun:"rel:has-many,join:id=report_id" json:"-"` - EndUsers []*EndUserDetail `bun:"rel:has-many,join:id=report_id,join:park_id=park_id" json:"-"` -} - -type Steps struct { - Summary bool `json:"summary"` - WillDiluted bool `json:"willDiluted"` - Submeter bool `json:"submeter"` - Calculate bool `json:"calculate"` - Preview bool `json:"preview"` - Publish bool `json:"publish"` -} - -func NewSteps() Steps { - return Steps{ - Summary: false, - WillDiluted: false, - Submeter: false, - Calculate: false, - Preview: false, - Publish: false, - } -} - -type ParkNewestReport struct { - Park Park `bun:"extends" json:"park"` - Report *Report `bun:"extends" json:"report"` -} - -func (p *ParkNewestReport) AfterLoad() { - if p.Report != nil && len(p.Report.Id) == 0 { - p.Report = nil - } -} - -type ReportIndexSimplified struct { - bun.BaseModel `bun:"table:report,alias:r"` - Id string `bun:",pk,notnull" json:"id"` - ParkId string `bun:",notnull" json:"parkId"` - Period Date `bun:"type:date,notnull" json:"period"` - StepState Steps `bun:"type:jsonb,notnull" json:"stepState"` - Published bool `bun:",notnull" json:"published"` - PublishedAt *time.Time `bun:"type:timestampz" json:"publishedAt" time_format:"simple_datetime" time_location:"shanghai"` - Withdraw int8 `bun:"type:smallint,notnull" json:"withdraw"` - LastWithdrawAppliedAt *time.Time `bun:"type:timestamptz" json:"lastWithdrawAppliedAt" time_format:"simple_datetime" time_location:"shanghai"` - LastWithdrawAuditAt *time.Time `bun:"type:timestamptz" json:"lastWithdrawAuditAt" time_format:"simple_datetime" time_location:"shanghai"` -} - -type JoinedReportForWithdraw struct { - Report Report `bun:"extends" json:"report"` - Park ParkSimplified `bun:"extends" json:"park"` - User UserDetailSimplified `bun:"extends" json:"user"` -} - -var _ bun.BeforeAppendModelHook = (*Report)(nil) - -func (p *Report) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - p.CreatedAt = oprTime - p.LastModifiedAt = &oprTime - case *bun.UpdateQuery: - p.LastModifiedAt = &oprTime - } - return nil -} diff --git a/model/report_summary.go b/model/report_summary.go deleted file mode 100644 index 467c955..0000000 --- a/model/report_summary.go +++ /dev/null @@ -1,119 +0,0 @@ -package model - -import ( - "errors" - - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -type ReportSummary struct { - bun.BaseModel `bun:"table:report_summary,alias:rs"` - ReportId string `bun:",pk,notnull" json:"-"` - Overall decimal.Decimal `bun:"type:numeric,notnull" json:"overall"` - OverallFee decimal.Decimal `bun:"type:numeric,notnull" json:"overallFee"` - ConsumptionFee decimal.NullDecimal `bun:"type:numeric" json:"consumptionFee"` - OverallPrice decimal.NullDecimal `bun:"type:numeric" json:"overallPrice"` - Critical decimal.Decimal `bun:"type:numeric,notnull" json:"critical"` - CriticalFee decimal.Decimal `bun:"type:numeric,notnull" json:"criticalFee"` - CriticalPrice decimal.NullDecimal `bun:"type:numeric" json:"criticalPrice"` - Peak decimal.Decimal `bun:"type:numeric,notnull" json:"peak"` - PeakFee decimal.Decimal `bun:"type:numeric,notnull" json:"peakFee"` - PeakPrice decimal.NullDecimal `bun:"type:numeric" json:"peakPrice"` - Flat decimal.Decimal `bun:"type:numeric,notnull" json:"flat"` - FlatFee decimal.Decimal `bun:"type:numeric,notnull" json:"flatFee"` - FlatPrice decimal.NullDecimal `bun:"type:numeric" json:"flatPrice"` - Valley decimal.Decimal `bun:"type:numeric,notnull" json:"valley"` - ValleyFee decimal.Decimal `bun:"type:numeric,notnull" json:"valleyFee"` - ValleyPrice decimal.NullDecimal `bun:"type:numeric" json:"valleyPrice"` - Loss decimal.NullDecimal `bun:"type:numeric" json:"loss"` - LossFee decimal.NullDecimal `bun:"type:numeric" json:"lossFee"` - LossProportion decimal.NullDecimal `bun:"type:numeric" json:"lossProportion"` - AuthorizeLoss decimal.NullDecimal `bun:"type:numeric" json:"authorizeLoss"` - AuthorizeLossFee decimal.NullDecimal `bun:"type:numeric" json:"authorizeLossFee"` - AuthorizeLossProportion decimal.NullDecimal `bun:"type:numeric" json:"authorizeLossProportion"` - BasicFee decimal.Decimal `bun:"type:numeric,notnull" json:"basicFee"` - BasicDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"basicDilutedPrice"` - AdjustFee decimal.Decimal `bun:"type:numeric,notnull" json:"adjustFee"` - AdjustDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"adjustDilutedPrice"` - MaintenanceDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"maintencanceDilutedPrice"` - LossDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"lossDilutedPrice"` - PublicConsumptionDilutedPrice decimal.NullDecimal `bun:"type:numeric" json:"publicConsumptionDilutedPrice"` - MaintenanceOverall decimal.NullDecimal `bun:"type:numeric" json:"maintenanceOverall"` - FinalDilutedOverall decimal.NullDecimal `bun:"type:numeric" json:"finalDilutedOverall"` - Customers Consumptions `bun:"type:jsonb" json:"customers"` - Publics Consumptions `bun:"type:jsonb" json:"publics"` -} - -type Consumptions struct { - Consumption decimal.NullDecimal `json:"consumption"` - ConsumptionFee decimal.NullDecimal `json:"fee"` - Critical decimal.NullDecimal `json:"critical"` - CriticalFee decimal.NullDecimal `json:"criticalFee"` - Peak decimal.NullDecimal `json:"peak"` - PeakFee decimal.NullDecimal `json:"peakFee"` - Flat decimal.NullDecimal `json:"flat"` - FlatFee decimal.NullDecimal `json:"flatFee"` - Valley decimal.NullDecimal `json:"valley"` - ValleyFee decimal.NullDecimal `json:"valleyFee"` - Proportion decimal.NullDecimal `json:"proportion"` -} - -func NewConsumptions() Consumptions { - return Consumptions{ - Consumption: decimal.NewNullDecimal(decimal.Zero), - ConsumptionFee: decimal.NewNullDecimal(decimal.Zero), - Critical: decimal.NewNullDecimal(decimal.Zero), - Peak: decimal.NewNullDecimal(decimal.Zero), - PeakFee: decimal.NewNullDecimal(decimal.Zero), - Flat: decimal.NewNullDecimal(decimal.Zero), - CriticalFee: decimal.NewNullDecimal(decimal.Zero), - FlatFee: decimal.NewNullDecimal(decimal.Zero), - Valley: decimal.NewNullDecimal(decimal.Zero), - ValleyFee: decimal.NewNullDecimal(decimal.Zero), - Proportion: decimal.NewNullDecimal(decimal.Zero), - } -} - -func (s ReportSummary) Validate() (bool, error) { - amountSum := decimal.Sum(s.Critical, s.Peak, s.Valley) - if amountSum.GreaterThan(s.Overall) { - return false, errors.New("峰谷计量总量大于总计电量") - } - feeSum := decimal.Sum(s.CriticalFee, s.PeakFee, s.ValleyFee) - if feeSum.GreaterThan(s.OverallFee) { - return false, errors.New("峰谷计量费用大于总计费用") - } - return true, nil -} - -func (s *ReportSummary) CalculatePrices() { - s.ConsumptionFee = decimal.NewNullDecimal(s.OverallFee.Sub(s.BasicFee).Sub(s.AdjustFee)) - if s.Overall.GreaterThan(decimal.Zero) { - s.OverallPrice = decimal.NewNullDecimal(s.ConsumptionFee.Decimal.Div(s.Overall).RoundBank(8)) - } else { - s.OverallPrice = decimal.NewNullDecimal(decimal.Zero) - } - if s.Critical.GreaterThan(decimal.Zero) { - s.CriticalPrice = decimal.NewNullDecimal(s.CriticalFee.Div(s.Critical).RoundBank(8)) - } else { - s.CriticalPrice = decimal.NewNullDecimal(decimal.Zero) - } - if s.Peak.GreaterThan(decimal.Zero) { - s.PeakPrice = decimal.NewNullDecimal(s.PeakFee.Div(s.Peak).RoundBank(8)) - } else { - s.PeakPrice = decimal.NewNullDecimal(decimal.Zero) - } - if s.Valley.GreaterThan(decimal.Zero) { - s.ValleyPrice = decimal.NewNullDecimal(s.ValleyFee.Div(s.Valley).RoundBank(8)) - } else { - s.ValleyPrice = decimal.NewNullDecimal(decimal.Zero) - } - s.Flat = s.Overall.Sub(s.Critical).Sub(s.Peak).Sub(s.Valley) - s.FlatFee = s.ConsumptionFee.Decimal.Sub(s.CriticalFee).Sub(s.PeakFee).Sub(s.ValleyFee) - if s.Flat.GreaterThan(decimal.Zero) { - s.FlatPrice = decimal.NewNullDecimal(s.FlatFee.Div(s.Flat).RoundBank(8)) - } else { - s.FlatPrice = decimal.NewNullDecimal(decimal.Zero) - } -} diff --git a/model/shared.go b/model/shared.go deleted file mode 100644 index 84d2fe0..0000000 --- a/model/shared.go +++ /dev/null @@ -1,32 +0,0 @@ -package model - -import "time" - -type Created struct { - CreatedAt time.Time `bun:"type:timestamptz,notnull" json:"createdAt" time_format:"simple_datetime" time_location:"shanghai"` -} - -type CreatedWithUser struct { - Created `bun:"extend"` - CreatedBy *string `json:"createdBy"` -} - -type Deleted struct { - DeletedAt *time.Time `bun:"type:timestamptz,soft_delete,nullzero" json:"deletedAt" time_format:"simple_datetime" time_location:"shanghai"` -} - -type DeletedWithUser struct { - Deleted `bun:"extend"` - DeletedBy *string `json:"deletedBy"` -} - -type CreatedAndModified struct { - Created `bun:"extend"` - LastModifiedAt *time.Time `bun:"type:timestamptz,nullzero" json:"lastModifiedAt" time_format:"simple_datetime" time_location:"shanghai"` -} - -type CreatedAndModifiedWithUser struct { - CreatedAndModified `bun:"extend"` - CreatedBy *string `json:"createdBy"` - LastModifiedBy *string `json:"lastModifiedBy"` -} diff --git a/model/user.go b/model/user.go index 3420fb7..391df8a 100644 --- a/model/user.go +++ b/model/user.go @@ -1,48 +1,7 @@ package model -import ( - "context" - "time" - - "github.com/uptrace/bun" -) - const ( USER_TYPE_ENT int8 = iota USER_TYPE_SUP USER_TYPE_OPS ) - -type User struct { - bun.BaseModel `bun:"table:user,alias:u"` - Created `bun:"extend"` - Id string `bun:",pk,notnull" json:"id"` - Username string `bun:",notnull" json:"username"` - Password string `bun:",notnull" json:"-"` - ResetNeeded bool `bun:",notnull" json:"resetNeeded"` - Type int8 `bun:"type:smallint,notnull" json:"type"` - Enabled bool `bun:",notnull" json:"enabled"` - Detail *UserDetail `bun:"rel:has-one,join:id=id" json:"-"` - Charges []*UserCharge `bun:"rel:has-many,join:id=user_id" json:"-"` -} - -type UserWithCredentials struct { - bun.BaseModel `bun:"table:user,alias:u"` - Created `bun:"extend"` - Id string `bun:",pk,notnull" json:"id"` - Username string `bun:",notnull" json:"username"` - Password string `bun:",notnull" json:"credential"` - ResetNeeded bool `bun:",notnull" json:"resetNeeded"` - Type int8 `bun:"type:smallint,notnull" json:"type"` - Enabled bool `bun:",notnull" json:"enabled"` -} - -var _ bun.BeforeAppendModelHook = (*User)(nil) - -func (u *User) BeforeAppendModel(ctx context.Context, query bun.Query) error { - switch query.(type) { - case *bun.InsertQuery: - u.CreatedAt = time.Now() - } - return nil -} diff --git a/model/user_charges.go b/model/user_charges.go deleted file mode 100644 index fc80c60..0000000 --- a/model/user_charges.go +++ /dev/null @@ -1,43 +0,0 @@ -package model - -import ( - "context" - "time" - - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -type UserCharge struct { - bun.BaseModel `bun:"table:user_charge,alias:c"` - Created `bun:"extend"` - Seq int64 `bun:"type:bigint,pk,notnull,autoincrement" json:"seq"` - UserId string `bun:",notnull" json:"userId"` - Fee decimal.NullDecimal `bun:"type:numeric" json:"fee"` - Discount decimal.NullDecimal `bun:"type:numeric" json:"discount"` - Amount decimal.NullDecimal `bun:"type:numeric" json:"amount"` - ChargeTo Date `bun:"type:date,notnull" json:"chargeTo"` - Settled bool `bun:",notnull" json:"settled"` - SettledAt *time.Time `bun:"type:timestamptz" json:"settledAt" time_format:"simple_datetime" time_location:"shanghai"` - Cancelled bool `bun:",notnull" json:"cancelled"` - CancelledAt *time.Time `bun:"type:timestamptz" json:"cancelledAt" time_format:"simple_datetime" time_location:"shanghai"` - Refunded bool `bun:",notnull" json:"refunded"` - RefundedAt *time.Time `bun:"type:timestamptz" json:"refundedAt" time_format:"simple_datetime" time_location:"shanghai"` - Detail *UserDetail `bun:"rel:belongs-to,join:user_id=id" json:"-"` -} - -type ChargeWithName struct { - UserDetail `bun:"extend"` - UserCharge `bun:"extend"` -} - -var _ bun.BeforeAppendModelHook = (*UserCharge)(nil) - -func (uc *UserCharge) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - uc.CreatedAt = oprTime - } - return nil -} diff --git a/model/user_detail.go b/model/user_detail.go deleted file mode 100644 index 3cd0190..0000000 --- a/model/user_detail.go +++ /dev/null @@ -1,69 +0,0 @@ -package model - -import ( - "context" - "time" - - "github.com/jinzhu/copier" - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -type UserDetail struct { - bun.BaseModel `bun:"table:user_detail,alias:d"` - CreatedAndModifiedWithUser `bun:"extend"` - DeletedWithUser `bun:"extend"` - Id string `bun:",pk,notnull" json:"-"` - Name *string `json:"name"` - Abbr *string `json:"abbr"` - Region *string `json:"region"` - Address *string `json:"address"` - Contact *string `json:"contact"` - Phone *string `json:"phone"` - UnitServiceFee decimal.Decimal `bun:"type:numeric,notnull" json:"unitServiceFee"` - ServiceExpiration Date `bun:"type:date,notnull" json:"serviceExpiration"` -} - -type JoinedUserDetail struct { - UserDetail `bun:"extend"` - Id string `json:"id"` - Username string `json:"username"` - Type int8 `json:"type"` - Enabled bool `json:"enabled"` -} - -type FullJoinedUserDetail struct { - UserDetail `bun:"extend"` - User `bun:"extend"` -} - -type UserDetailSimplified struct { - bun.BaseModel `bun:"table:user_detail,alias:d"` - Id string `bun:",pk,notnull" json:"id"` - Name *string `json:"name"` - Abbr *string `json:"abbr"` - Region *string `json:"region"` - Address *string `json:"address"` - Contact *string `json:"contact"` - Phone *string `json:"phone"` -} - -func FromUserDetail(user UserDetail) UserDetailSimplified { - dest := UserDetailSimplified{} - copier.Copy(&dest, user) - return dest -} - -var _ bun.BeforeAppendModelHook = (*UserDetail)(nil) - -func (d *UserDetail) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - d.CreatedAt = oprTime - d.LastModifiedAt = &oprTime - case *bun.UpdateQuery: - d.LastModifiedAt = &oprTime - } - return nil -} diff --git a/model/will_diluted_fee.go b/model/will_diluted_fee.go deleted file mode 100644 index fe8d641..0000000 --- a/model/will_diluted_fee.go +++ /dev/null @@ -1,35 +0,0 @@ -package model - -import ( - "context" - "time" - - "github.com/shopspring/decimal" - "github.com/uptrace/bun" -) - -type WillDilutedFee struct { - bun.BaseModel `bun:"table:will_diluted_fee,alias:w"` - CreatedAndModified `bun:"extend"` - Id string `bun:",pk,notnull" json:"diluteId"` - ReportId string `bun:",notnull" json:"reportId"` - SourceId *string `json:"sourceId"` - Name string `bun:",notnull" json:"name"` - Fee decimal.Decimal `bun:"type:numeric,notnull" json:"fee"` - Memo *string `bun:"type:text" json:"memo"` - Origin *MaintenanceFee `bun:"rel:belongs-to,join:source_id=id"` -} - -var _ bun.BeforeAppendModelHook = (*WillDilutedFee)(nil) - -func (d *WillDilutedFee) BeforeAppendModel(ctx context.Context, query bun.Query) error { - oprTime := time.Now() - switch query.(type) { - case *bun.InsertQuery: - d.CreatedAt = oprTime - d.LastModifiedAt = &oprTime - case *bun.UpdateQuery: - d.LastModifiedAt = &oprTime - } - return nil -} diff --git a/router/router.go b/router/router.go index 070b4de..db53a00 100644 --- a/router/router.go +++ b/router/router.go @@ -1,7 +1,6 @@ package router import ( - "electricity_bill_calc/controller" "electricity_bill_calc/logger" "electricity_bill_calc/security" "fmt" @@ -44,18 +43,6 @@ func App() *fiber.App { })) app.Use(security.SessionRecovery) - controller.InitializeUserController(app) - controller.InitializeRegionController(app) - controller.InitializeChargesController(app) - controller.InitializeParkController(app) - controller.InitializeMaintenanceFeeController(app) - controller.InitializeMeter04kVController(app) - controller.InitializeReportController(app) - controller.InitializeEndUserController(app) - controller.InitializeWithdrawController(app) - controller.InitializeStatisticsController(app) - controller.InitializeGodModeController(app) - return app } diff --git a/service/calculate.go b/service/calculate.go deleted file mode 100644 index 6680aa4..0000000 --- a/service/calculate.go +++ /dev/null @@ -1,262 +0,0 @@ -package service - -import ( - "database/sql" - "electricity_bill_calc/cache" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/model" - "fmt" - - "github.com/shopspring/decimal" -) - -type _CalculateService struct{} - -var CalculateService _CalculateService - -func (_CalculateService) ComprehensivelyCalculateReport(reportId string) (err error) { - ctx, cancel := global.TimeoutContext(12) - defer cancel() - - // 资料准备 - var report = new(model.Report) - err = global.DB.NewSelect().Model(report). - Relation("Summary"). - Relation("WillDilutedFees"). - Relation("EndUsers"). - Where("r.id = ?", reportId). - Scan(ctx) - if err != nil || report == nil { - return exceptions.NewNotFoundErrorFromError("未找到指定的公示报表", err) - } - - // 综合计算 - report.Summary.CalculatePrices() - - // 计算维护费总计 - maintenanceFeeTotal := decimal.NewFromInt(0) - for _, m := range report.WillDilutedFees { - maintenanceFeeTotal = maintenanceFeeTotal.Add(m.Fee) - } - - // 计算终端用户信息与概览中的合计 - report.Summary.Customers = model.NewConsumptions() - report.Summary.Publics = model.NewConsumptions() - for _, eu := range report.EndUsers { - eu.OverallFee = decimal.NewNullDecimal( - eu.Overall.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - eu.CriticalFee = decimal.NewNullDecimal( - eu.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2), - ) - eu.PeakFee = decimal.NewNullDecimal( - eu.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2), - ) - eu.FlatFee = decimal.NewNullDecimal( - eu.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2), - ) - eu.ValleyFee = decimal.NewNullDecimal( - eu.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2), - ) - if eu.IsPublicMeter { - report.Summary.Publics.Consumption.Decimal = report.Summary.Publics.Consumption.Decimal.Add(eu.Overall.Decimal) - report.Summary.Publics.Critical.Decimal = report.Summary.Publics.Critical.Decimal.Add(eu.Critical.Decimal) - report.Summary.Publics.Peak.Decimal = report.Summary.Publics.Peak.Decimal.Add(eu.Peak.Decimal) - report.Summary.Publics.Flat.Decimal = report.Summary.Publics.Flat.Decimal.Add(eu.Flat.Decimal) - report.Summary.Publics.Valley.Decimal = report.Summary.Publics.Valley.Decimal.Add(eu.Valley.Decimal) - } else { - report.Summary.Customers.Consumption.Decimal = report.Summary.Customers.Consumption.Decimal.Add(eu.Overall.Decimal) - report.Summary.Customers.Critical.Decimal = report.Summary.Customers.Critical.Decimal.Add(eu.Critical.Decimal) - report.Summary.Customers.Peak.Decimal = report.Summary.Customers.Peak.Decimal.Add(eu.Peak.Decimal) - report.Summary.Customers.Flat.Decimal = report.Summary.Customers.Flat.Decimal.Add(eu.Flat.Decimal) - report.Summary.Customers.Valley.Decimal = report.Summary.Customers.Valley.Decimal.Add(eu.Valley.Decimal) - } - } - - // 计算户表总电费和公共总电费以及相应的摊薄 - if report.SubmeterType == model.CUSTOMER_METER_NON_PV { - // 计算终端用户部分 - report.Summary.Customers.ConsumptionFee = decimal.NewNullDecimal( - report.Summary.Customers.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.CriticalFee = decimal.NewNullDecimal( - report.Summary.Customers.Critical.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.PeakFee = decimal.NewNullDecimal( - report.Summary.Customers.Peak.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.FlatFee = decimal.NewNullDecimal( - report.Summary.Customers.Flat.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.ValleyFee = decimal.NewNullDecimal( - report.Summary.Customers.Valley.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - // 计算公共区域部分 - report.Summary.Publics.ConsumptionFee = decimal.NewNullDecimal( - report.Summary.Publics.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.CriticalFee = decimal.NewNullDecimal( - report.Summary.Publics.Critical.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.PeakFee = decimal.NewNullDecimal( - report.Summary.Publics.Peak.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.FlatFee = decimal.NewNullDecimal( - report.Summary.Publics.Flat.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.ValleyFee = decimal.NewNullDecimal( - report.Summary.Publics.Valley.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - } - if report.SubmeterType == model.CUSTOMER_METER_PV { - // 计算终端用户部分 - report.Summary.Customers.ConsumptionFee = decimal.NewNullDecimal( - report.Summary.Customers.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.CriticalFee = decimal.NewNullDecimal( - report.Summary.Customers.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.PeakFee = decimal.NewNullDecimal( - report.Summary.Customers.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.FlatFee = decimal.NewNullDecimal( - report.Summary.Customers.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2), - ) - report.Summary.Customers.ValleyFee = decimal.NewNullDecimal( - report.Summary.Customers.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2), - ) - // 计算公共区域部分 - report.Summary.Publics.ConsumptionFee = decimal.NewNullDecimal( - report.Summary.Publics.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.CriticalFee = decimal.NewNullDecimal( - report.Summary.Publics.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.PeakFee = decimal.NewNullDecimal( - report.Summary.Publics.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.FlatFee = decimal.NewNullDecimal( - report.Summary.Publics.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2), - ) - report.Summary.Publics.ValleyFee = decimal.NewNullDecimal( - report.Summary.Publics.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2), - ) - } - if report.Summary.Overall.Abs().GreaterThan(decimal.Zero) { - report.Summary.Customers.Proportion = decimal.NewNullDecimal( - report.Summary.Customers.Consumption.Decimal.Div(report.Summary.Overall).RoundBank(15), - ) - report.Summary.Publics.Proportion = decimal.NewNullDecimal( - report.Summary.Publics.Consumption.Decimal.Div(report.Summary.Overall).RoundBank(15), - ) - } - - // 计算线损 - report.Summary.Loss = decimal.NewNullDecimal( - report.Summary.Overall.Sub(report.Summary.Publics.Consumption.Decimal).Sub(report.Summary.Customers.Consumption.Decimal), - ) - report.Summary.LossFee = decimal.NewNullDecimal( - report.Summary.Loss.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(8), - ) - if report.Summary.Overall.Abs().GreaterThan(decimal.Zero) { - report.Summary.LossProportion = decimal.NewNullDecimal( - report.Summary.Loss.Decimal.Div(report.Summary.Overall).RoundBank(15), - ) - if report.Summary.LossProportion.Decimal.GreaterThan(decimal.NewFromFloat(0.1)) { - report.Summary.AuthorizeLoss = decimal.NewNullDecimal( - report.Summary.Overall.Mul(decimal.NewFromFloat(0.1)).RoundBank(8), - ) - report.Summary.AuthorizeLossFee = decimal.NewNullDecimal( - report.Summary.AuthorizeLoss.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(8), - ) - } else { - report.Summary.AuthorizeLoss = report.Summary.Loss - report.Summary.AuthorizeLossFee = report.Summary.LossFee - } - } - if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) { - report.Summary.LossDilutedPrice = decimal.NewNullDecimal( - report.Summary.AuthorizeLossFee.Decimal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8), - ) - } - - // 计算基本电费和调整电费等的摊薄 - if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) { - report.Summary.BasicDilutedPrice = decimal.NewNullDecimal( - report.Summary.BasicFee.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8), - ) - report.Summary.AdjustDilutedPrice = decimal.NewNullDecimal( - report.Summary.AdjustFee.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8), - ) - report.Summary.MaintenanceDilutedPrice = decimal.NewNullDecimal( - maintenanceFeeTotal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8), - ) - } - - // 计算摊薄总计 - report.Summary.MaintenanceOverall = decimal.NewNullDecimal(maintenanceFeeTotal) - report.Summary.FinalDilutedOverall = decimal.NewNullDecimal( - report.Summary.BasicFee. - Add(report.Summary.AdjustFee). - Add(report.Summary.AuthorizeLossFee.Decimal), - ) - - // 计算终端用户的全部摊薄内容 - for _, eu := range report.EndUsers { - // 计算户表表计的摊薄内容 - if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) { - eu.OverallProportion = eu.Overall.Decimal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(15) - } else { - eu.OverallProportion = decimal.Zero - } - eu.BasicFeeDiluted = decimal.NewNullDecimal( - eu.Overall.Decimal.Mul(report.Summary.BasicDilutedPrice.Decimal).RoundBank(2), - ) - eu.AdjustFeeDiluted = decimal.NewNullDecimal( - eu.Overall.Decimal.Mul(report.Summary.AdjustDilutedPrice.Decimal).RoundBank(2), - ) - eu.LossDiluted = decimal.NewNullDecimal( - report.Summary.AuthorizeLoss.Decimal.Mul(eu.OverallProportion).RoundBank(8), - ) - eu.LossFeeDiluted = decimal.NewNullDecimal( - eu.Overall.Decimal.Mul(report.Summary.LossDilutedPrice.Decimal).RoundBank(8), - ) - eu.FinalDiluted = decimal.NewNullDecimal( - eu.BasicFeeDiluted.Decimal. - Add(eu.AdjustFeeDiluted.Decimal). - Add(eu.LossFeeDiluted.Decimal). - RoundBank(2), - ) - eu.FinalCharge = decimal.NewNullDecimal( - eu.OverallFee.Decimal.Add(eu.FinalDiluted.Decimal).RoundBank(2), - ) - } - - // 向数据库保存报表概况以及终端用户摊薄结果 - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return - } - - _, err = tx.NewUpdate().Model(report.Summary).WherePK().Exec(ctx) - if err != nil { - tx.Rollback() - return - } - for _, eu := range report.EndUsers { - _, err = tx.NewUpdate().Model(eu).WherePK().Exec(ctx) - if err != nil { - tx.Rollback() - return - } - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return - } - cache.AbolishRelation(fmt.Sprintf("publicity:%s", reportId)) - return -} diff --git a/service/charge.go b/service/charge.go deleted file mode 100644 index d3fcead..0000000 --- a/service/charge.go +++ /dev/null @@ -1,284 +0,0 @@ -package service - -import ( - "context" - "database/sql" - "electricity_bill_calc/cache" - "electricity_bill_calc/config" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - "strconv" - "time" - - "github.com/fufuok/utils" - "github.com/samber/lo" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _ChargeService struct { - l *zap.Logger -} - -var ChargeService = _ChargeService{ - l: logger.Named("Service", "Charge"), -} - -func (c _ChargeService) CreateChargeRecord(charge *model.UserCharge, extendWithIgnoreSettle bool) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - _, err = tx.NewInsert().Model(charge).Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - if extendWithIgnoreSettle { - err := c.updateUserExpiration(&tx, ctx, charge.UserId, charge.ChargeTo) - if err != nil { - return err - } - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation("charge") - return nil -} - -func (c _ChargeService) SettleCharge(seq int64, uid string) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - var record = new(model.UserCharge) - err = tx.NewSelect().Model(&record). - Where("seq = ?", seq). - Where("user_id = ?", uid). - Scan(ctx) - if err != nil { - return nil - } - if record == nil { - return exceptions.NewNotFoundError("未找到匹配指定条件的计费记录。") - } - currentTime := time.Now() - _, err = tx.NewUpdate().Model((*model.UserCharge)(nil)). - Where("seq = ?", seq). - Where("user_id = ?", uid). - Set("settled = ?", true). - Set("settled_at = ?", currentTime). - Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - err = c.updateUserExpiration(&tx, ctx, uid, record.ChargeTo) - if err != nil { - return err - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation(fmt.Sprintf("charge:%s:%d", uid, seq)) - return nil -} - -func (c _ChargeService) RefundCharge(seq int64, uid string) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - currentTime := time.Now() - res, err := tx.NewUpdate().Model((*model.UserCharge)(nil)). - Where("seq = ?", seq). - Where("user_id = ?", uid). - Set("refunded = ?", true). - Set("refunded_at = ?", currentTime). - Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - if rows, _ := res.RowsAffected(); rows == 0 { - tx.Rollback() - return exceptions.NewNotFoundError("未找到匹配指定条件的计费记录。") - } - lastValidExpriation, err := c.lastValidChargeTo(&tx, &ctx, uid) - if err != nil { - tx.Rollback() - return exceptions.NewNotFoundError("未找到最后合法的计费时间。") - } - err = c.updateUserExpiration(&tx, ctx, uid, lastValidExpriation) - if err != nil { - return err - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation(fmt.Sprintf("charge:%s:%d", uid, seq)) - return nil -} - -func (c _ChargeService) CancelCharge(seq int64, uid string) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - currentTime := time.Now() - res, err := tx.NewUpdate().Model((*model.UserCharge)(nil)). - Where("seq = ?", seq). - Where("user_id = ?", uid). - Set("cancelled = ?", true). - Set("cancelled_at = ?", currentTime). - Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - if rows, _ := res.RowsAffected(); rows == 0 { - tx.Rollback() - return exceptions.NewNotFoundError("未找到匹配指定条件的计费记录。") - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - tx, err = global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - lastValidExpriation, err := c.lastValidChargeTo(&tx, &ctx, uid) - if err != nil { - return exceptions.NewNotFoundError("未找到最后合法的计费时间。") - } - err = c.updateUserExpiration(&tx, ctx, uid, lastValidExpriation) - if err != nil { - return err - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation("user") - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) - cache.AbolishRelation("charge") - cache.AbolishRelation(fmt.Sprintf("charge:%s:%d", uid, seq)) - return nil -} - -func (ch _ChargeService) updateUserExpiration(tx *bun.Tx, ctx context.Context, uid string, expiration model.Date) error { - _, err := tx.NewUpdate().Model((*model.UserDetail)(nil)). - Set("service_expiration = ?", expiration). - Where("id = ?", uid). - Exec(ctx) - if err != nil { - tx.Rollback() - } - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) - return err -} - -func (_ChargeService) ListPagedChargeRecord(keyword, beginDate, endDate string, page int) ([]model.ChargeWithName, int64, error) { - var ( - cond = global.DB.NewSelect() - condition = make([]string, 0) - charges = make([]model.UserCharge, 0) - ) - cond = cond.Model(&charges).Relation("Detail") - condition = append(condition, strconv.Itoa(page)) - if len(keyword) != 0 { - keywordCond := "%" + keyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("detail.name like ?", keywordCond). - WhereOr("detail.abbr like ?", keywordCond) - }) - condition = append(condition, keyword) - } - if len(beginDate) != 0 { - beginTime, err := time.ParseInLocation("2006-01-02", beginDate, time.Local) - beginTime = utils.BeginOfDay(beginTime) - if err != nil { - return make([]model.ChargeWithName, 0), 0, err - } - cond = cond.Where("c.created_at >= ?", beginTime) - condition = append(condition, strconv.FormatInt(beginTime.Unix(), 10)) - } - if len(endDate) != 0 { - endTime, err := time.ParseInLocation("2006-01-02", endDate, time.Local) - endTime = utils.EndOfDay(endTime) - if err != nil { - return make([]model.ChargeWithName, 0), 0, err - } - cond = cond.Where("c.created_at <= ?", endTime) - condition = append(condition, strconv.FormatInt(endTime.Unix(), 10)) - } - - if cachedTotal, err := cache.RetreiveCount("charge_with_name", condition...); cachedTotal != -1 && err == nil { - if cachedCharges, _ := cache.RetreiveSearch[[]model.ChargeWithName]("charge_with_name", condition...); cachedCharges != nil { - return *cachedCharges, cachedTotal, nil - } - } - - startItem := (page - 1) * config.ServiceSettings.ItemsPageSize - var ( - total int - err error - ) - ctx, cancel := global.TimeoutContext() - defer cancel() - total, err = cond.Limit(config.ServiceSettings.ItemsPageSize).Offset(startItem).ScanAndCount(ctx) - - relations := []string{"charge"} - chargesWithName := make([]model.ChargeWithName, 0) - for _, c := range charges { - chargesWithName = append(chargesWithName, model.ChargeWithName{ - UserCharge: c, - UserDetail: *c.Detail, - }) - relations = append(relations, fmt.Sprintf("charge:%s:%d", c.UserId, c.Seq)) - } - - cache.CacheCount(relations, "charge_with_name", int64(total), condition...) - cache.CacheSearch(chargesWithName, relations, "charge_with_name", condition...) - return chargesWithName, int64(total), err -} - -func (_ChargeService) lastValidChargeTo(tx *bun.Tx, ctx *context.Context, uid string) (model.Date, error) { - var records []model.Date - err := tx.NewSelect().Table("user_charge"). - Where("settled = ? and cancelled = ? and refunded = ? and user_id = ?", true, false, false, uid). - Column("charge_to"). - Scan(*ctx, &records) - if err != nil { - return model.NewEmptyDate(), nil - } - lastValid := lo.Reduce(records, func(acc, elem model.Date, index int) model.Date { - if elem.Time.After(acc.Time) { - return elem - } else { - return acc - } - }, model.NewEmptyDate()) - return lastValid, nil -} diff --git a/service/end_user.go b/service/end_user.go deleted file mode 100644 index 72fa950..0000000 --- a/service/end_user.go +++ /dev/null @@ -1,479 +0,0 @@ -package service - -import ( - "context" - "database/sql" - "electricity_bill_calc/cache" - "electricity_bill_calc/config" - "electricity_bill_calc/excel" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - "io" - "strconv" - "time" - - mapset "github.com/deckarep/golang-set/v2" - "github.com/samber/lo" - "github.com/shopspring/decimal" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _EndUserService struct { - l *zap.Logger -} - -type MeterAppears struct { - Meter string - Appears int64 -} - -var EndUserService = _EndUserService{ - l: logger.Named("Service", "EndUser"), -} - -func (_EndUserService) SearchEndUserRecord(reportId, keyword string, page int) ([]model.EndUserDetail, int64, error) { - var ( - conditions = make([]string, 0) - endUsers = make([]model.EndUserDetail, 0) - cond = global.DB.NewSelect().Model(&endUsers) - ) - conditions = append(conditions, reportId, strconv.Itoa(page)) - cond = cond.Where("report_id = ?", reportId) - if len(keyword) > 0 { - keywordCond := "%" + keyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("customer_name like ?", keywordCond). - WhereOr("contact_name like ?", keywordCond). - WhereOr("contact_phone like ?", keywordCond). - WhereOr("meter_04kv_id like ?", keywordCond) - }) - conditions = append(conditions, keyword) - } - if cachedTotal, err := cache.RetreiveCount("end_user_detail", conditions...); cachedTotal != -1 && err == nil { - if cachedEndUsers, _ := cache.RetreiveSearch[[]model.EndUserDetail]("end_user_detail", conditions...); cachedEndUsers != nil { - return *cachedEndUsers, cachedTotal, nil - } - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - - startItem := (page - 1) * config.ServiceSettings.ItemsPageSize - total, err := cond.Limit(config.ServiceSettings.ItemsPageSize). - Offset(startItem). - Order("seq asc", "meter_04kv_id asc"). - ScanAndCount(ctx) - - relations := []string{"end_user", "report", "park"} - for _, eu := range endUsers { - relations = append(relations, fmt.Sprintf("end_user:%s:%s", eu.ReportId, eu.MeterId)) - } - cache.CacheCount(relations, "end_user_detail", int64(total), conditions...) - cache.CacheSearch(endUsers, relations, "end_user_detail", conditions...) - return endUsers, int64(total), err -} - -func (_EndUserService) AllEndUserRecord(reportId string) ([]model.EndUserDetail, error) { - if cachedEndUsers, _ := cache.RetreiveSearch[[]model.EndUserDetail]("end_user_detail", "report", reportId); cachedEndUsers != nil { - return *cachedEndUsers, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - users := make([]model.EndUserDetail, 0) - err := global.DB.NewSelect().Model(&users). - Where("report_id = ?", reportId). - Order("seq asc", "meter_04kv_id asc"). - Scan(ctx) - relations := lo.Map(users, func(eu model.EndUserDetail, _ int) string { - return fmt.Sprintf("end_user:%s:%s", eu.ReportId, eu.MeterId) - }) - relations = append(relations, "report", "park") - cache.CacheSearch(users, relations, "end_user_detail", "report", reportId) - return users, err -} - -func (_EndUserService) FetchSpecificEndUserRecord(reportId, parkId, meterId string) (*model.EndUserDetail, error) { - if cachedEndUser, _ := cache.RetreiveEntity[model.EndUserDetail]("end_user_detail", fmt.Sprintf("%s_%s_%s", reportId, parkId, meterId)); cachedEndUser != nil { - return cachedEndUser, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - record := new(model.EndUserDetail) - err := global.DB.NewSelect().Model(record). - Where("report_id = ?", reportId). - Where("park_id = ?", parkId). - Where("meter_04kv_id = ?", meterId). - Scan(ctx) - cache.CacheEntity(record, []string{fmt.Sprintf("end_user:%s:%s", reportId, meterId), "report", "park"}, "end_user_detail", fmt.Sprintf("%s_%s_%s", reportId, parkId, meterId)) - return record, err -} - -func (_EndUserService) UpdateEndUserRegisterRecord(tx *bun.Tx, ctx *context.Context, record model.EndUserDetail) (err error) { - record.CalculatePeriod() - updateColumns := []string{ - "current_period_overall", - "adjust_overall", - "current_period_critical", - "current_period_peak", - "current_period_flat", - "current_period_valley", - "adjust_critical", - "adjust_peak", - "adjust_flat", - "adjust_valley", - "overall", - "critical", - "peak", - "flat", - "valley", - } - if record.Initialize { - updateColumns = append(updateColumns, - "last_period_overall", - "last_period_critical", - "last_period_peak", - "last_period_flat", - "last_period_valley", - ) - } - - _, err = tx.NewUpdate().Model(&record). - WherePK(). - Column(updateColumns...). - Exec(*ctx) - cache.AbolishRelation(fmt.Sprintf("end_user:%s:%s", record.ReportId, record.MeterId)) - return -} - -func (_EndUserService) newVirtualExcelAnalysisError(err error) *excel.ExcelAnalysisError { - return &excel.ExcelAnalysisError{Col: -1, Row: -1, Err: excel.AnalysisError{Err: err}} -} - -func (es _EndUserService) BatchImportNonPVRegister(reportId string, file io.Reader) *exceptions.BatchError { - ctx, cancel := global.TimeoutContext(120) - defer cancel() - - errs := exceptions.NewBatchError() - users, err := es.AllEndUserRecord(reportId) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - var reportDetail = new(model.Report) - err = global.DB.NewSelect().Model(reportDetail). - Where("id = ?", reportId). - Scan(ctx) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(fmt.Errorf("未能找到相应的报表。%w", err))) - return errs - } - meterAppers := make([]MeterAppears, 0) - err = global.DB.NewSelect().Model((*model.EndUserDetail)(nil)). - ColumnExpr("meter_04kv_id as meter"). - ColumnExpr("count(*) as appears"). - Where("park_id = ?", reportDetail.ParkId). - Group("meter_04kv_id"). - Scan(ctx, &meterAppers) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - indexedUsers := lo.Reduce( - users, - func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail { - acc[elem.MeterId] = elem - return acc - }, - make(map[string]model.EndUserDetail, 0), - ) - analyzer, err := excel.NewEndUserNonPVExcelAnalyzer(file) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - imports, excelErrs := analyzer.Analysis(*new(model.EndUserImport)) - if len(excelErrs) > 0 { - for _, e := range excelErrs { - errs.AddError(e) - } - return errs - } - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - - for _, im := range imports { - if elem, ok := indexedUsers[im.MeterId]; ok { - if appears, has := lo.Find(meterAppers, func(m MeterAppears) bool { - return m.Meter == elem.MeterId - }); has { - if appears.Appears <= 1 { - elem.LastPeriodOverall = im.LastPeriodOverall - elem.LastPeriodCritical = decimal.Zero - elem.LastPeriodPeak = decimal.Zero - elem.LastPeriodValley = decimal.Zero - elem.LastPeriodFlat = elem.LastPeriodOverall.Sub(elem.LastPeriodCritical).Sub(elem.LastPeriodPeak).Sub(elem.LastPeriodValley) - elem.Initialize = true - } - } - elem.CurrentPeriodOverall = im.CurrentPeriodOverall - elem.AdjustOverall = im.AdjustOverall - elem.CurrentPeriodCritical = decimal.Zero - elem.CurrentPeriodPeak = decimal.Zero - elem.CurrentPeriodValley = decimal.Zero - elem.CurrentPeriodFlat = elem.CurrentPeriodOverall.Sub(elem.CurrentPeriodCritical).Sub(elem.CurrentPeriodPeak).Sub(elem.CurrentPeriodValley) - elem.AdjustCritical = decimal.Zero - elem.AdjustPeak = decimal.Zero - elem.AdjustValley = decimal.Zero - elem.AdjustFlat = elem.AdjustOverall.Sub(elem.AdjustCritical).Sub(elem.AdjustPeak).Sub(elem.AdjustValley) - err := es.UpdateEndUserRegisterRecord(&tx, &ctx, elem) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - } - } else { - errs.AddError(exceptions.NewNotFoundError(fmt.Sprintf("表计 %s 未找到", im.MeterId))) - } - } - if errs.Len() > 0 { - tx.Rollback() - return errs - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - errs.AddError(es.newVirtualExcelAnalysisError(err)) - } - cache.AbolishRelation("end_user_detail") - return errs -} - -func (es _EndUserService) BatchImportPVRegister(reportId string, file io.Reader) *exceptions.BatchError { - ctx, cancel := global.TimeoutContext(120) - defer cancel() - - errs := exceptions.NewBatchError() - users, err := es.AllEndUserRecord(reportId) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - var reportDetail = new(model.Report) - err = global.DB.NewSelect().Model(reportDetail).Where("id = ?", reportId).Scan(ctx) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(fmt.Errorf("未能找到相应的报表。%w", err))) - return errs - } - meterAppers := make([]MeterAppears, 0) - err = global.DB.NewSelect().Model((*model.EndUserDetail)(nil)). - ColumnExpr("meter_04kv_id as meter"). - ColumnExpr("count(*) as appears"). - Where("park_id = ?", reportDetail.Id). - Group("meter_04kv_id"). - Scan(ctx, &meterAppers) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - indexedUsers := lo.Reduce( - users, - func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail { - acc[elem.MeterId] = elem - return acc - }, - make(map[string]model.EndUserDetail, 0), - ) - analyzer, err := excel.NewEndUserPVExcelAnalyzer(file) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - imports, excelErrs := analyzer.Analysis(*new(model.EndUserImport)) - if len(excelErrs) > 0 { - for _, e := range excelErrs { - errs.AddError(e) - } - return errs - } - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - return errs - } - - for _, im := range imports { - if elem, ok := indexedUsers[im.MeterId]; ok { - if appears, has := lo.Find(meterAppers, func(m MeterAppears) bool { - return m.Meter == elem.MeterId - }); has { - if appears.Appears <= 1 { - elem.LastPeriodOverall = im.LastPeriodOverall - elem.LastPeriodCritical = im.LastPeriodCritical.Decimal - elem.LastPeriodPeak = im.LastPeriodPeak.Decimal - elem.LastPeriodValley = im.LastPeriodValley.Decimal - elem.LastPeriodFlat = elem.LastPeriodOverall.Sub(elem.LastPeriodCritical).Sub(elem.LastPeriodPeak).Sub(elem.LastPeriodValley) - elem.Initialize = true - } - } - elem.CurrentPeriodOverall = im.CurrentPeriodOverall - elem.AdjustOverall = im.AdjustOverall - elem.CurrentPeriodCritical = im.CurrentPeriodCritical.Decimal - elem.CurrentPeriodPeak = im.CurrentPeriodPeak.Decimal - elem.CurrentPeriodValley = im.CurrentPeriodValley.Decimal - elem.CurrentPeriodFlat = elem.CurrentPeriodOverall.Sub(elem.CurrentPeriodCritical).Sub(elem.CurrentPeriodPeak).Sub(elem.CurrentPeriodValley) - elem.AdjustCritical = im.AdjustCritical.Decimal - elem.AdjustPeak = im.AdjustPeak.Decimal - elem.AdjustValley = im.AdjustValley.Decimal - elem.AdjustFlat = elem.AdjustOverall.Sub(elem.AdjustCritical).Sub(elem.AdjustPeak).Sub(elem.AdjustValley) - err := es.UpdateEndUserRegisterRecord(&tx, &ctx, elem) - if err != nil { - errs.AddError(es.newVirtualExcelAnalysisError(err)) - } - } else { - errs.AddError(es.newVirtualExcelAnalysisError(exceptions.NewNotFoundError(fmt.Sprintf("表计 %s 未找到", im.MeterId)))) - } - } - if errs.Len() > 0 { - tx.Rollback() - return errs - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - errs.AddError(es.newVirtualExcelAnalysisError(err)) - } - cache.AbolishRelation("end_user_detail") - return errs -} - -func (es _EndUserService) StatEndUserRecordInPeriod(requestUser, requestPark, startDate, endDate string) ([]model.EndUserPeriodStat, error) { - var ( - conditions = make([]string, 0) - relations = []string{ - fmt.Sprintf("park:%s", requestPark), - "end_user_detail", - } - cond = global.DB.NewSelect(). - Model((*model.EndUserDetail)(nil)). - Relation("Report", func(sq *bun.SelectQuery) *bun.SelectQuery { - return sq.ExcludeColumn("*") - }). - Relation("Park", func(sq *bun.SelectQuery) *bun.SelectQuery { - return sq.ExcludeColumn("*") - }) - ) - if len(requestUser) > 0 { - cond = cond.Where("park.user_id = ?", requestUser) - conditions = append(conditions, requestUser) - } else { - conditions = append(conditions, "_") - } - if len(requestPark) > 0 { - cond = cond.Where("eud.park_id = ?", requestPark) - conditions = append(conditions, requestPark) - } else { - conditions = append(conditions, "_") - } - if len(startDate) > 0 { - parseTime, err := time.Parse("2006-01", startDate) - if err != nil { - return make([]model.EndUserPeriodStat, 0), fmt.Errorf("不能解析给定的参数[startDate],%w", err) - } - start := model.NewDate(parseTime) - cond = cond.Where("report.period >= ?::date", start.ToString()) - conditions = append(conditions, startDate) - } else { - conditions = append(conditions, "_") - } - if len(endDate) > 0 { - parseTime, err := time.Parse("2006-01", endDate) - if err != nil { - return make([]model.EndUserPeriodStat, 0), fmt.Errorf("不能解析给定的参数[endDate],%w", err) - } - end := model.NewDate(parseTime) - cond = cond.Where("report.period <= ?::date", end.ToString()) - conditions = append(conditions, endDate) - } - if cached, err := cache.RetreiveSearch[[]model.EndUserPeriodStat]("end_user_stat", conditions...); cached != nil && err == nil { - return *cached, nil - } - ctx, cancel := global.TimeoutContext(120) - defer cancel() - var endUserSums []model.EndUserPeriodStat - err := cond.Column("eud.meter_04kv_id", "eud.park_id"). - ColumnExpr("sum(?) as overall", bun.Ident("eud.overall")). - ColumnExpr("sum(?) as overall_fee", bun.Ident("eud.overall_fee")). - ColumnExpr("sum(?) as critical", bun.Ident("eud.critical")). - ColumnExpr("sum(?) as critical_fee", bun.Ident("eud.critical_fee")). - ColumnExpr("sum(?) as peak", bun.Ident("eud.peak")). - ColumnExpr("sum(?) as peak_fee", bun.Ident("eud.peak_fee")). - ColumnExpr("sum(?) as valley", bun.Ident("eud.valley")). - ColumnExpr("sum(?) as valley_fee", bun.Ident("eud.valley_fee")). - ColumnExpr("sum(?) as final_diluted", bun.Ident("eud.final_diluted")). - Where("report.published = ?", true). - Group("eud.meter_04kv_id", "eud.park_id"). - Scan(ctx, &endUserSums) - if err != nil { - return make([]model.EndUserPeriodStat, 0), fmt.Errorf("未能完成终端用户在指定期限内的统计,%w", err) - } - parkIds := lo.Reduce( - endUserSums, - func(acc mapset.Set[string], elem model.EndUserPeriodStat, _ int) mapset.Set[string] { - acc.Add(elem.ParkId) - return acc - }, - mapset.NewSet[string](), - ) - meterArchives := make([]model.Meter04KV, 0) - if len(parkIds.ToSlice()) > 0 { - err = global.DB.NewSelect(). - Model(&meterArchives).Relation("ParkDetail"). - Where("park_id in (?)", bun.In(parkIds.ToSlice())). - Scan(ctx) - if err != nil { - return make([]model.EndUserPeriodStat, 0), fmt.Errorf("未能获取到终端表计的最新基础档案,%w", err) - } - } - filledStats := lo.Map( - endUserSums, - func(elem model.EndUserPeriodStat, _ int) model.EndUserPeriodStat { - archive, has := lo.Find(meterArchives, func(meter model.Meter04KV) bool { - return meter.Code == elem.MeterId - }) - if has { - if archive.Address != nil { - elem.Address = *archive.Address - } else { - elem.Address = "" - } - if archive.CustomerName != nil { - elem.CustomerName = *archive.CustomerName - } else { - elem.CustomerName = "" - } - elem.IsPublicMeter = archive.IsPublicMeter - elem.Kind = archive.ParkDetail.SubmeterType - } - if elem.OverallFee.Valid && elem.AdjustFee.Valid && !elem.OverallFee.Decimal.IsZero() { - elem.AdjustProportion = decimal.NewNullDecimal( - elem.AdjustFee.Decimal.Div(elem.OverallFee.Decimal).RoundBank(8), - ) - } else { - elem.AdjustProportion = decimal.NullDecimal{} - } - return elem - }, - ) - cache.CacheSearch(filledStats, relations, "end_user_stat", conditions...) - return filledStats, nil -} diff --git a/service/god_mode.go b/service/god_mode.go deleted file mode 100644 index 044b423..0000000 --- a/service/god_mode.go +++ /dev/null @@ -1,875 +0,0 @@ -package service - -import ( - "context" - "database/sql" - "electricity_bill_calc/cache" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - "time" - - "github.com/samber/lo" - "github.com/shopspring/decimal" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _GodModeService struct { - l *zap.Logger -} - -var GodModeService = _GodModeService{ - l: logger.Named("Service", "GodMode"), -} - -// 从此处开始为删除报表相关的部分 - -func (_GodModeService) resetReportIndex(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) { - var report = new(model.Report) - err := tx.NewSelect().Model(report).Where("id = ?", reportId).Scan(*ctx) - if err != nil { - tx.Rollback() - return false, exceptions.NewNotFoundError("指定报表索引未找到。") - } - report.StepState.Summary = false - report.StepState.WillDiluted = false - report.StepState.Submeter = false - report.StepState.Calculate = false - report.StepState.Preview = false - report.StepState.Publish = false - report.Published = false - report.PublishedAt = nil - report.Withdraw = model.REPORT_NOT_WITHDRAW - report.LastWithdrawAppliedAt = nil - report.LastWithdrawAuditAt = nil - - res, err := tx.NewUpdate().Model(report). - WherePK(). - Column( - "step_state", - "published", - "published_at", - "withdraw", - "last_withdraw_applied_at", - "last_withdraw_audit_at", - ). - Exec(*ctx) - - if affected, _ := res.RowsAffected(); err != nil || affected == 0 { - tx.Rollback() - return false, err - } - return true, err -} - -func (_GodModeService) resetReportSummary(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) { - var summary = &model.ReportSummary{ - ReportId: reportId, - } - _, err := tx.NewUpdate().Model(summary).WherePK().Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - var report = new(model.Report) - err = tx.NewSelect().Model(report).Where("id = ?", reportId).Scan(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - report.StepState.Summary = false - res, err := tx.NewUpdate().Model(report). - Column("step_state"). - WherePK(). - Exec(*ctx) - rows, _ := res.RowsAffected() - if err != nil { - tx.Rollback() - } - return rows >= 0, err -} - -func (_GodModeService) flushReportMaintenances(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) { - _, err := tx.NewDelete().Model((*model.WillDilutedFee)(nil)). - Where("report_id = ?", reportId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - var report = new(model.Report) - err = tx.NewSelect().Model(report).Where("id = ?", reportId).Scan(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - report.StepState.WillDiluted = false - res, err := tx.NewUpdate().Model(report). - WherePK(). - Column("step_state"). - Exec(*ctx) - rows, _ := res.RowsAffected() - if err != nil { - tx.Rollback() - } - return rows >= 0, err -} - -func (g _GodModeService) resetSingleEndUserRecord(tx *bun.Tx, ctx *context.Context, record model.EndUserDetail, additionalColumns ...string) (bool, error) { - record.CurrentPeriodOverall = decimal.Zero - record.CurrentPeriodCritical = decimal.Zero - record.CurrentPeriodPeak = decimal.Zero - record.CurrentPeriodFlat = decimal.Zero - record.CurrentPeriodValley = decimal.Zero - record.AdjustOverall = decimal.Zero - record.AdjustCritical = decimal.Zero - record.AdjustPeak = decimal.Zero - record.AdjustFlat = decimal.Zero - record.AdjustValley = decimal.Zero - record.Overall = decimal.NewNullDecimal(decimal.Zero) - record.Overall.Valid = false - record.OverallFee = decimal.NewNullDecimal(decimal.Zero) - record.OverallFee.Valid = false - record.OverallProportion = decimal.Zero - record.Critical = decimal.NewNullDecimal(decimal.Zero) - record.Critical.Valid = false - record.CriticalFee = decimal.NewNullDecimal(decimal.Zero) - record.CriticalFee.Valid = false - record.Peak = decimal.NewNullDecimal(decimal.Zero) - record.Peak.Valid = false - record.PeakFee = decimal.NewNullDecimal(decimal.Zero) - record.PeakFee.Valid = false - record.Flat = decimal.NewNullDecimal(decimal.Zero) - record.Flat.Valid = false - record.FlatFee = decimal.NewNullDecimal(decimal.Zero) - record.FlatFee.Valid = false - record.Valley = decimal.NewNullDecimal(decimal.Zero) - record.Valley.Valid = false - record.ValleyFee = decimal.NewNullDecimal(decimal.Zero) - record.ValleyFee.Valid = false - record.BasicFeeDiluted = decimal.NewNullDecimal(decimal.Zero) - record.BasicFeeDiluted.Valid = false - record.AdjustFeeDiluted = decimal.NewNullDecimal(decimal.Zero) - record.AdjustFeeDiluted.Valid = false - record.LossDiluted = decimal.NewNullDecimal(decimal.Zero) - record.LossDiluted.Valid = false - record.LossFeeDiluted = decimal.NewNullDecimal(decimal.Zero) - record.LossFeeDiluted.Valid = false - record.FinalDiluted = decimal.NewNullDecimal(decimal.Zero) - record.FinalDiluted.Valid = false - record.FinalCharge = decimal.NewNullDecimal(decimal.Zero) - record.FinalCharge.Valid = false - - columns := []string{ - "current_period_overall", - "current_period_critical", - "current_period_peak", - "current_period_flat", - "current_period_valley", - "adjust_overall", - "adjust_critical", - "adjust_peak", - "adjust_flat", - "adjust_valley", - "overall", - "overall_fee", - "overall_proportion", - "critical", - "critical_fee", - "peak", - "peak_fee", - "flat", - "flat_fee", - "valley", - "valley_fee", - "basic_fee_diluted", - "adjust_fee_diluted", - "loss_diluted", - "loss_fee_diluted", - "maintenance_fee_diluted", - "public_consumption_diluted", - "final_diluted", - "final_charge", - } - columns = append(columns, additionalColumns...) - - _, err := tx.NewUpdate().Model(&record). - WherePK(). - Column(columns...). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - return true, nil -} - -func (g _GodModeService) resynchronizeEndUserArchives(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) { - var currentRecords = make([]*model.EndUserDetail, 0) - err := tx.NewSelect().Model(¤tRecords). - Where("report_id = ?", reportId). - Scan(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - var report = new(model.Report) - err = tx.NewSelect().Model(report). - Where("id = ?", reportId). - Scan(*ctx) - if err != nil || report == nil { - tx.Rollback() - return false, err - } - var latestArchives = make([]model.Meter04KV, 0) - err = tx.NewSelect().Model(&latestArchives). - Where("park_id = ?", report.ParkId). - Where("enabled = ?", true). - Scan(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - - for _, meter := range latestArchives { - record, has := lo.Find(currentRecords, func(rec *model.EndUserDetail) bool { - return rec.ParkId == meter.ParkId && rec.MeterId == meter.Code - }) - if has { - record.CustomerName = meter.CustomerName - record.Address = meter.Address - record.Ratio = meter.Ratio - record.ContactName = meter.ContactName - record.ContactPhone = meter.ContactPhone - record.Seq = meter.Seq - record.IsPublicMeter = meter.IsPublicMeter - success, err := g.resetSingleEndUserRecord( - tx, ctx, *record, - "customer_name", - "address", - "ratio", - "contact_name", - "contact_phone", - "seq", - "public_meter", - ) - if err != nil { - return success, err - } - } else { - newEndUser := model.EndUserDetail{ - ReportId: report.Id, - ParkId: report.ParkId, - MeterId: meter.Code, - Seq: meter.Seq, - Ratio: meter.Ratio, - Address: meter.Address, - CustomerName: meter.CustomerName, - ContactName: meter.ContactName, - ContactPhone: meter.ContactPhone, - IsPublicMeter: meter.IsPublicMeter, - LastPeriodOverall: decimal.Zero, - LastPeriodCritical: decimal.Zero, - LastPeriodPeak: decimal.Zero, - LastPeriodFlat: decimal.Zero, - LastPeriodValley: decimal.Zero, - } - _, err = tx.NewInsert().Model(&newEndUser).Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - } - } - - report.StepState.Submeter = false - res, err := tx.NewUpdate().Model(report). - WherePK(). - Column("step_state"). - Exec(*ctx) - rows, _ := res.RowsAffected() - if err != nil { - tx.Rollback() - } - return rows >= 0, nil -} - -func (g _GodModeService) resetEndUserRecords(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) { - var records = make([]model.EndUserDetail, 0) - err := tx.NewSelect().Model(&records). - Where("report_id = ?", reportId). - Scan(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - for _, u := range records { - success, err := g.resetSingleEndUserRecord(tx, ctx, u) - if err != nil { - return success, err - } - } - var report = new(model.Report) - err = tx.NewSelect().Model(report). - Where("id = ?", reportId). - Scan(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - report.StepState.Submeter = false - res, err := tx.NewUpdate().Model(report). - WherePK(). - Column("step_state"). - Exec(*ctx) - rows, _ := res.RowsAffected() - if err != nil { - tx.Rollback() - } - return rows >= 0, nil -} - -type ReportPeriod struct { - Id string - Period time.Time -} - -func (_GodModeService) isTheLatestReport(ctx *context.Context, reportId string) (bool, error) { - var report = new(model.Report) - err := global.DB.NewSelect().Model(report). - Where("id = ?", reportId). - Scan(*ctx) - if err != nil || report == nil { - return false, exceptions.NewNotFoundErrorFromError("指定报表索引未找到,", err) - } - var maxPeriod time.Time - err = global.DB.NewSelect().Model((*model.Report)(nil)). - ColumnExpr("max(?)", bun.Ident("period")). - Where("park_id = ?", report.ParkId). - Scan(*ctx, &maxPeriod) - if err != nil { - return false, err - } - return maxPeriod.Equal(report.Period), nil -} - -func (_GodModeService) forceDeleteReport(tx *bun.Tx, ctx *context.Context, reportId string) (bool, error) { - _, err := tx.NewDelete().Model((*model.EndUserDetail)(nil)). - Where("report_id = ?", reportId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - _, err = tx.NewDelete().Model((*model.WillDilutedFee)(nil)). - Where("report_id = ?", reportId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - _, err = tx.NewDelete().Model((*model.ReportSummary)(nil)). - Where("report_id = ?", reportId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - _, err = tx.NewDelete().Model((*model.Report)(nil)). - Where("id = ?", reportId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, err - } - return true, nil -} - -func (g _GodModeService) ClearReportSummary(reportId string) (bool, error) { - ctx, cancel := global.TimeoutContext(12) - defer cancel() - - isLatest, err := g.isTheLatestReport(&ctx, reportId) - if err != nil { - return false, err - } - if !isLatest { - return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。") - } - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.resetReportSummary(&tx, &ctx, reportId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - return result, nil -} - -func (g _GodModeService) ClearReportMaintenances(reportId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - isLatest, err := g.isTheLatestReport(&ctx, reportId) - if err != nil { - return false, err - } - if !isLatest { - return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。") - } - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.flushReportMaintenances(&tx, &ctx, reportId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - return result, nil -} - -func (g _GodModeService) ResynchronizeEndUser(reportId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - isLatest, err := g.isTheLatestReport(&ctx, reportId) - if err != nil { - return false, err - } - if !isLatest { - return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。") - } - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.resynchronizeEndUserArchives(&tx, &ctx, reportId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation("end_user_detail") - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - return result, nil -} - -func (g _GodModeService) ResetEndUserRegisterRecords(reportId string) (bool, error) { - ctx, cancel := global.TimeoutContext(48) - defer cancel() - - isLatest, err := g.isTheLatestReport(&ctx, reportId) - if err != nil { - return false, err - } - if !isLatest { - return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。") - } - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.resetEndUserRecords(&tx, &ctx, reportId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation("end_user_detail") - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - return result, nil -} - -func (g _GodModeService) ResetReport(reportId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - isLatest, err := g.isTheLatestReport(&ctx, reportId) - if err != nil { - return false, err - } - if !isLatest { - return false, exceptions.NewImproperOperateError("不能操作非最新期数的报表。") - } - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - var result = true - r, err := g.resetEndUserRecords(&tx, &ctx, reportId) - if err != nil { - return false, err - } - result = result && r - r, err = g.flushReportMaintenances(&tx, &ctx, reportId) - if err != nil { - return false, err - } - result = result && r - r, err = g.resetReportSummary(&tx, &ctx, reportId) - if err != nil { - return false, err - } - result = result && r - r, err = g.resetReportIndex(&tx, &ctx, reportId) - if err != nil { - return false, err - } - result = result && r - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation("end_user_detail") - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - return result, nil -} - -func (g _GodModeService) DeleteReport(reportId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - isLatest, err := g.isTheLatestReport(&ctx, reportId) - if err != nil { - return false, err - } - if !isLatest { - return false, exceptions.NewImproperOperateError("不能删除非最新期数的报表。") - } - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.forceDeleteReport(&tx, &ctx, reportId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation("end_user_detail") - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - - return result, nil -} - -// 从此处开始为删除园区相关的内容部分 - -func (_GodModeService) deleteSpecificMaintenance(tx *bun.Tx, ctx *context.Context, parkId, maintenanceId string) (bool, error) { - res, err := tx.NewDelete().Model((*model.MaintenanceFee)(nil)). - Where("park_id = ?", parkId). - Where("id = ?", maintenanceId). - Exec(*ctx) - - if err != nil { - tx.Rollback() - return false, nil - } - if rows, err := res.RowsAffected(); err != nil { - tx.Rollback() - return false, err - } else { - return rows >= 0, err - } -} - -func (_GodModeService) deleteAllMaintenance(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) { - res, err := tx.NewDelete().Model((*model.MaintenanceFee)(nil)). - Where("park_id = ?", parkId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, nil - } - if rows, err := res.RowsAffected(); err != nil { - tx.Rollback() - return false, err - } else { - return rows >= 0, err - } -} - -func (_GodModeService) deleteAllMeters(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) { - res, err := tx.NewDelete().Model((*model.Meter04KV)(nil)). - Where("park_id = ?", parkId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, nil - } - if rows, err := res.RowsAffected(); err != nil { - tx.Rollback() - return false, err - } else { - return rows >= 0, err - } -} - -func (_GodModeService) deletePark(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) { - res, err := tx.NewDelete().Model((*model.Park)(nil)). - Where("id = ?", parkId). - Exec(*ctx) - if err != nil { - tx.Rollback() - return false, nil - } - if rows, err := res.RowsAffected(); err != nil { - tx.Rollback() - return false, err - } else { - return rows >= 0, err - } -} - -func (g _GodModeService) RemoveSpecificMaintenance(parkId, maintenanceId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.deleteSpecificMaintenance(&tx, &ctx, parkId, maintenanceId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", maintenanceId)) - return result, nil -} - -func (g _GodModeService) RemoveAllMaintenance(parkId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.deleteAllMaintenance(&tx, &ctx, parkId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation("maintenance_fee") - return result, nil -} - -func (g _GodModeService) RemoveAllMeters(parkId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.deleteAllMeters(&tx, &ctx, parkId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation("meter_04kv") - return result, nil -} - -func (g _GodModeService) erasePark(tx *bun.Tx, ctx *context.Context, parkId string) (bool, error) { - var reportIds = make([]string, 0) - err := tx.NewSelect().Model((*model.Report)(nil)). - Where("park_id = ?", parkId). - Column("id"). - Scan(*ctx, &reportIds) - if err != nil { - tx.Rollback() - return false, err - } - var result = true - for _, id := range reportIds { - r, err := g.forceDeleteReport(tx, ctx, id) - if err != nil { - return false, err - } - result = result && r - } - r, err := g.deleteAllMaintenance(tx, ctx, parkId) - if err != nil { - return false, err - } - result = result && r - r, err = g.deleteAllMeters(tx, ctx, parkId) - if err != nil { - return false, err - } - result = result && r - r, err = g.deletePark(tx, ctx, parkId) - if err != nil { - return false, err - } - result = result && r - - return result, err -} - -func (g _GodModeService) RemovePark(parkId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - result, err := g.erasePark(&tx, &ctx, parkId) - if err != nil { - return false, err - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation(fmt.Sprintf("park:%s", parkId)) - return result, nil -} - -// 从此处开始为删除用户相关的部分 - -func (g _GodModeService) DeleteUser(userId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return false, err - } - - var parkIds = make([]string, 0) - err = tx.NewSelect().Model((*model.Park)(nil)). - Where("user_id = ?", userId). - WhereAllWithDeleted(). - Column("id"). - Scan(ctx, &parkIds) - if err != nil { - tx.Rollback() - return false, err - } - - var result = true - for _, p := range parkIds { - r, err := g.erasePark(&tx, &ctx, p) - if err != nil { - return false, err - } - result = result && r - } - - // 删除用户服务计费数据。 - res, err := tx.NewDelete().Model((*model.UserCharge)(nil)). - Where("user_id = ?", userId). - Exec(ctx) - if err != nil { - tx.Rollback() - return false, err - } - if rows, err := res.RowsAffected(); err != nil { - tx.Rollback() - return false, err - } else { - result = result && (rows >= 0) - } - // 删除用户详细信息数据 - res, err = tx.NewDelete().Model((*model.UserDetail)(nil)). - Where("id = ?", userId). - ForceDelete(). - Exec(ctx) - if err != nil { - tx.Rollback() - return false, err - } - if rows, err := res.RowsAffected(); err != nil { - tx.Rollback() - return false, err - } else { - result = result && (rows >= 0) - } - // 删除用户基本索引数据 - res, err = tx.NewDelete().Model((*model.User)(nil)). - Where("id = ?", userId). - Exec(ctx) - if err != nil { - tx.Rollback() - return false, err - } - if rows, err := res.RowsAffected(); err != nil { - tx.Rollback() - return false, err - } else { - result = result && (rows >= 0) - } - - err = tx.Commit() - if err != nil { - tx.Rollback() - return false, err - } - cache.AbolishRelation(fmt.Sprintf("user:%s", userId)) - cache.AbolishRelation("user") - cache.AbolishRelation("park") - cache.AbolishRelation("report") - cache.AbolishRelation("charge") - return result, nil -} diff --git a/service/maintenance_fee.go b/service/maintenance_fee.go deleted file mode 100644 index e4453d5..0000000 --- a/service/maintenance_fee.go +++ /dev/null @@ -1,295 +0,0 @@ -package service - -import ( - "electricity_bill_calc/cache" - "electricity_bill_calc/config" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - - mapset "github.com/deckarep/golang-set/v2" - "github.com/google/uuid" - "github.com/samber/lo" - "github.com/shopspring/decimal" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _MaintenanceFeeService struct { - l *zap.Logger -} - -var MaintenanceFeeService = _MaintenanceFeeService{ - l: logger.Named("Service", "maintenance"), -} - -func (_MaintenanceFeeService) ListMaintenanceFees(pid []string, period string, requestPage int) ([]model.MaintenanceFee, int64, error) { - conditions := []string{fmt.Sprintf("%d", requestPage)} - conditions = append(conditions, pid...) - conditions = append(conditions, period) - if cachedTotal, err := cache.RetreiveCount("maintenance_fee", conditions...); cachedTotal != -1 && err == nil { - if fees, _ := cache.RetreiveSearch[[]model.MaintenanceFee]("maintenance_fee", conditions...); fees != nil { - return *fees, cachedTotal, nil - } - } - - var ( - fees = make([]model.MaintenanceFee, 0) - cond = global.DB.NewSelect().Model(&fees) - ) - if len(pid) > 0 { - cond = cond.Where("park_id in (?)", bun.In(pid)) - } else { - return make([]model.MaintenanceFee, 0), 0, exceptions.NewIllegalArgumentsError("必须给定所要请求的至少一个园区", "park_id") - } - if len(period) > 0 { - cond = cond.Where("period = ?", period) - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize - total, err := cond.Order("period desc", "created_at desc"). - Limit(config.ServiceSettings.ItemsPageSize). - Offset(startItem). - ScanAndCount(ctx) - if err != nil { - return make([]model.MaintenanceFee, 0), 0, fmt.Errorf("附加费查询出现错误,%w", err) - } - relations := lo.Map(fees, func(f model.MaintenanceFee, _ int) string { - return fmt.Sprintf("maintenance_fee:%s", f.Id) - }) - relations = append(relations, "maintenance_fee", "park") - - cache.CacheCount(relations, "maintenance_fee", int64(total), conditions...) - cache.CacheSearch(fees, relations, "maintenance_fee", conditions...) - return fees, int64(total), nil -} - -func (_MaintenanceFeeService) CreateMaintenanceFeeRecord(fee model.MaintenanceFee) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - - fee.Id = uuid.New().String() - fee.Enabled = true - _, err := global.DB.NewInsert().Model(&fee).Exec(ctx) - if err != nil { - return err - } - cache.AbolishRelation("maintenance_fee") - return nil -} - -func (_MaintenanceFeeService) ModifyMaintenanceFee(fee model.MaintenanceFee) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - - res, err := global.DB.NewUpdate().Model(&fee). - WherePK(). - Column("fee", "memo"). - Exec(ctx) - if err != nil { - if rows, _ := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到匹配的附加费记录。") - } else { - return err - } - } - cache.AbolishRelation("maintenance_fee") - cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fee.Id)) - return nil -} - -func (_MaintenanceFeeService) ChangeMaintenanceFeeState(fid string, state bool) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - - res, err := global.DB.NewUpdate().Model((*model.MaintenanceFee)(nil)). - Where("id = ?", fid). - Set("enabled = ?", state). - Exec(ctx) - if err != nil { - if rows, err := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到匹配的附加费记录。") - } else { - return err - } - } - cache.AbolishRelation("maintenance_fee") - cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fid)) - return nil -} - -func (_MaintenanceFeeService) DeleteMaintenanceFee(fid string) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - - res, err := global.DB.NewDelete().Model((*model.MaintenanceFee)(nil)). - Where("id = ?", fid). - Exec(ctx) - if err != nil { - if rows, err := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到匹配的附加费记录。") - } else { - return err - } - } - cache.AbolishRelation("maintenance_fee") - cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fid)) - return nil -} - -func (_MaintenanceFeeService) EnsureFeeBelongs(uid, mid string) (bool, error) { - if has, _ := cache.CheckExists("maintenance_fee", mid, uid); has { - return true, nil - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - - parks := make([]model.Park, 0) - err := global.DB.NewSelect().Model(&parks). - Relation("MaintenanceFees"). - Where("p.user_id = ?", uid). - Scan(ctx) - if err != nil { - return false, err - } - exists := lo.Reduce(parks, func(acc bool, elem model.Park, _ int) bool { - for _, e := range elem.MaintenanceFees { - if e.Id == mid { - return acc || true - } - } - return acc || false - }, false) - if !exists { - return false, exceptions.NewNotFoundError("指定附加费所属园区未找到。") - } - cache.CacheExists([]string{fmt.Sprintf("maintenance_fee:%s", mid), "maintenance_fee", "park"}, "maintenance_fee", mid, uid) - return exists, nil -} - -type _FeeStat struct { - ParkId string - Period string - Total decimal.Decimal -} - -func (f _MaintenanceFeeService) QueryAdditionalCharges(uid, pid, period, keyword string, requestPage int) ([]model.AdditionalCharge, int64, error) { - var ( - conditions = []string{fmt.Sprintf("%d", requestPage)} - statFees = make([]_FeeStat, 0) - cond = global.DB.NewSelect(). - Model((*model.MaintenanceFee)(nil)). - Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.ExcludeColumn("*") - }). - Relation("Park.Enterprise", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.ExcludeColumn("*") - }). - Where("m.enabled = ?", true) - ) - if len(uid) > 0 { - cond = cond.Where("park__enterprise.id = ?", uid) - conditions = append(conditions, uid) - } else { - conditions = append(conditions, "_") - } - if len(pid) > 0 { - cond = cond.Where("park.id = ?", pid) - conditions = append(conditions, pid) - } else { - conditions = append(conditions, "_") - } - if len(period) > 0 { - cond = cond.Where("m.period = ?", period) - conditions = append(conditions, period) - } else { - conditions = append(conditions, "_") - } - if len(keyword) > 0 { - keywordCond := "%" + keyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("park__enterprise.name like ?", keywordCond). - WhereOr("park__enterprise.abbr like ?", keywordCond). - WhereOr("park.name like ?", keywordCond). - WhereOr("park.abbr like ?", keywordCond). - WhereOr("park.address like ?", keywordCond) - }) - conditions = append(conditions, keyword) - } else { - conditions = append(conditions, "_") - } - - if cachedTotal, err := cache.RetreiveCount("additional_charge", conditions...); cachedTotal != -1 && err == nil { - if cachedData, _ := cache.RetreiveSearch[[]model.AdditionalCharge]("additional_charge", conditions...); cachedData != nil { - return *cachedData, cachedTotal, nil - } - } - ctx, cancel := global.TimeoutContext(24) - defer cancel() - - startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize - - total, err := cond.ColumnExpr("sum(?) as total", bun.Ident("fee")). - Column("park_id", "period"). - Group("park_id", "period"). - Order("period desc"). - Limit(config.ServiceSettings.ItemsPageSize). - Offset(startItem). - ScanAndCount(ctx, &statFees) - if err != nil { - return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取附加费统计信息出现错误,%w", err) - } - parkIds := lo.Reduce( - statFees, - func(acc mapset.Set[string], elem _FeeStat, _ int) mapset.Set[string] { - acc.Add(elem.ParkId) - return acc - }, - mapset.NewSet[string](), - ) - parks := make([]model.Park, 0) - if len(parkIds.ToSlice()) > 0 { - err = global.DB.NewSelect().Model(&parks).Relation("Enterprise"). - Where("p.id in (?)", bun.In(parkIds.ToSlice())). - Scan(ctx) - if err != nil { - return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取园区信息出现错误,%w", err) - } - } - assembledStat := lo.Reduce( - statFees, - func(acc []model.AdditionalCharge, elem _FeeStat, _ int) []model.AdditionalCharge { - park, has := lo.Find(parks, func(p model.Park) bool { - return p.Id == elem.ParkId - }) - f.l.Debug("Park detection.", zap.Bool("has", has), zap.Any("park", park)) - if has { - if !park.Area.Valid || park.Area.Decimal.Equal(decimal.Zero) { - return acc - } - price := elem.Total.Div(park.Area.Decimal).RoundBank(8) - return append(acc, model.AdditionalCharge{ - ParkId: elem.ParkId, - Period: elem.Period, - Fee: elem.Total, - Price: price, - QuarterPrice: price.Div(decimal.NewFromInt(4)), - SemiAnnualPrice: price.Div(decimal.NewFromInt(2)), - Enterprise: model.FromUserDetail(*park.Enterprise), - Park: park, - }) - } else { - return acc - } - }, - make([]model.AdditionalCharge, 0), - ) - cache.CacheCount([]string{"maintenance_fee"}, "additional_charge", int64(total), conditions...) - cache.CacheSearch(assembledStat, []string{"maintenance_fee"}, "additional_charge", conditions...) - return assembledStat, int64(total), nil -} diff --git a/service/meter04kv.go b/service/meter04kv.go deleted file mode 100644 index 04c901a..0000000 --- a/service/meter04kv.go +++ /dev/null @@ -1,231 +0,0 @@ -package service - -import ( - "context" - "database/sql" - "electricity_bill_calc/cache" - "electricity_bill_calc/config" - "electricity_bill_calc/excel" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - "strconv" - - mapset "github.com/deckarep/golang-set/v2" - "github.com/samber/lo" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _Meter04kVService struct { - l *zap.Logger -} - -var Meter04kVService = _Meter04kVService{ - l: logger.Named("Service", "Meter04KV"), -} - -func (_Meter04kVService) ListMeterDetail(park, keyword string, page int) ([]model.Meter04KV, int64, error) { - var ( - condition = make([]string, 0) - meters = make([]model.Meter04KV, 0) - ) - cond := global.DB.NewSelect().Model(&meters). - Where("park_id = ?", park) - condition = append(condition, park, strconv.Itoa(page)) - if len(keyword) > 0 { - keywordCond := "%" + keyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("address like ?", keywordCond). - WhereOr("code like ?", keywordCond). - WhereOr("customer_name like ?", keywordCond). - WhereOr("contact_name like ?", keywordCond). - WhereOr("contact_phone like ?", keywordCond) - }) - condition = append(condition, keyword) - } - if cachedTotal, err := cache.RetreiveCount("meter_04kv", condition...); cachedTotal != -1 && err == nil { - if cachedMeters, _ := cache.RetreiveSearch[[]model.Meter04KV]("meter_04kv", condition...); cachedMeters != nil { - return *cachedMeters, cachedTotal, nil - } - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - startItem := (page - 1) * config.ServiceSettings.ItemsPageSize - total, err := cond. - Order("seq asc", "code asc"). - Limit(config.ServiceSettings.ItemsPageSize). - Offset(startItem). - ScanAndCount(ctx) - - relations := lo.Map(meters, func(m model.Meter04KV, _ int) string { - return fmt.Sprintf("meter_04kv:%s:%s", m.ParkId, m.Code) - }) - relations = append(relations, "meter_04kv", "park") - - cache.CacheCount(relations, "meter_04kv", int64(total), condition...) - cache.CacheSearch(meters, relations, "meter_04kv", condition...) - return meters, int64(total), err -} - -func (_Meter04kVService) Get04kVMeterDetail(park, code string) (*model.Meter04KV, error) { - if cachedMeter, _ := cache.RetreiveEntity[model.Meter04KV]("meter_04kv", fmt.Sprintf("%s_%s", park, code)); cachedMeter != nil { - return cachedMeter, nil - } - var meter = new(model.Meter04KV) - ctx, cancel := global.TimeoutContext() - defer cancel() - err := global.DB.NewSelect().Model(meter). - Where("code = ?", code). - Where("park_id = ?", park). - Scan(ctx) - if err != nil { - return nil, err - } - cache.CacheEntity(meter, []string{fmt.Sprintf("meter_04kv:%s:%s", park, code), "park"}, "meter_04kv", fmt.Sprintf("%s_%s", park, code)) - return meter, nil -} - -func (_Meter04kVService) insertNewMeter(tx *bun.Tx, ctx *context.Context, meter model.Meter04KV) error { - _, err := tx.NewInsert().Model(&meter).Exec(*ctx) - if err != nil { - tx.Rollback() - } - cache.AbolishRelation("meter_04kv") - return err -} - -func (_Meter04kVService) updateMeter(tx *bun.Tx, ctx *context.Context, meter model.Meter04KV) error { - _, err := tx.NewUpdate().Model(&meter). - Where("code = ?", meter.Code). - Where("park_id = ?", meter.ParkId). - Column("address", "customer_name", "contact_name", "contact_phone", "ratio", "seq", "public_meter", "enabled"). - Exec(*ctx) - if err != nil { - tx.Rollback() - } - cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code)) - return err -} - -func (m _Meter04kVService) CreateSingleMeter(meter model.Meter04KV) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - - err = m.insertNewMeter(&tx, &ctx, meter) - if err != nil { - return err - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation("meter_04kv") - cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code)) - return nil -} - -func (m _Meter04kVService) UpdateSingleMeter(meter *model.Meter04KV) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - - err = m.updateMeter(&tx, &ctx, *meter) - if err != nil { - return err - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code)) - return nil -} - -func (_Meter04kVService) DuplicateMeterCodeValidate(meters []model.Meter04KV) []excel.ExcelAnalysisError { - errs := make([]excel.ExcelAnalysisError, 0) - for i := 0; i < len(meters); i++ { - for j := i + 1; j < len(meters); j++ { - if meters[j].Code == meters[i].Code { - errs = append(errs, excel.ExcelAnalysisError{Row: j + 1, Col: 0, Err: excel.AnalysisError{Err: fmt.Errorf("第 %d 行表计表号与第 %d 行表计表号重复!", j+1, i+1)}}) - } - } - } - return errs -} - -func (m _Meter04kVService) BatchCreateMeter(meters []model.Meter04KV) error { - parkIds := lo.Reduce(meters, func(acc mapset.Set[string], elem model.Meter04KV, index int) mapset.Set[string] { - acc.Add(elem.ParkId) - return acc - }, mapset.NewSet[string]()) - if parkIds.Cardinality() > 1 { - return fmt.Errorf("一次只能向同一个园区中添加0.4kV表计。") - } - parkId, _ := parkIds.Pop() - - ctx, cancel := global.TimeoutContext(120) - defer cancel() - - allMeterCodes := make([]string, 0) - err := global.DB.NewSelect().Model((*model.Meter04KV)(nil)). - Where("park_id = ?", parkId). - Column("code"). - Scan(ctx, &allMeterCodes) - if err != nil { - return err - } - meterCodes := mapset.NewSet(allMeterCodes...) - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - - var ( - updates = make([]model.Meter04KV, 0) - inserts = make([]model.Meter04KV, 0) - ) - for _, meter := range meters { - if meterCodes.Contains(meter.Code) { - updates = append(updates, meter) - } else { - inserts = append(inserts, meter) - } - } - if len(updates) > 0 { - _, err = tx.NewUpdate().Model(&updates). - Column("address", "customer_name", "contact_name", "contact_phone", "ratio", "seq", "public_meter", "enabled"). - Bulk(). - Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - } - if len(inserts) > 0 { - _, err = tx.NewInsert().Model(&inserts).Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation("meter_04kv") - return nil -} diff --git a/service/park.go b/service/park.go deleted file mode 100644 index ab58527..0000000 --- a/service/park.go +++ /dev/null @@ -1,171 +0,0 @@ -package service - -import ( - "electricity_bill_calc/cache" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _ParkService struct { - l *zap.Logger -} - -var ParkService = _ParkService{ - l: logger.Named("Service", "Park"), -} - -func (_ParkService) SaveNewPark(park model.Park) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - _, err := global.DB.NewInsert().Model(&park).Exec(ctx) - if err != nil { - return err - } - cache.AbolishRelation("park") - return nil -} - -func (_ParkService) UpdateParkInfo(park *model.Park) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - res, err := global.DB.NewUpdate().Model(park). - Where("id = ?", park.Id). - Where("user_id = ?", park.UserId). - Column("name", "abbr", "region", "area", "address", "contact", "phone", "capacity", "tenement_quantity", "category", "meter_04kv_type"). - Exec(ctx) - if err != nil { - if rows, _ := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到符合条件的园区。") - } else { - return err - } - } - cache.AbolishRelation(fmt.Sprintf("park:%s", park.Id)) - return nil -} - -func (_ParkService) ChangeParkState(uid, pid string, state bool) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - res, err := global.DB.NewUpdate().Model((*model.Park)(nil)). - Where("id = ?", pid). - Where("user_id = ?", uid). - Set("enabled = ?", state). - Exec(ctx) - if err != nil { - if rows, _ := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到符合条件的园区。") - } else { - return err - } - } - cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) - return nil -} - -func (_ParkService) DeletePark(uid, pid string) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - res, err := global.DB.NewDelete().Model((*model.Park)(nil)). - Where("id = ?", pid). - Where("user_id = ?", uid). - Exec(ctx) - if err != nil { - if rows, _ := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到符合条件的园区。") - } else { - return err - } - } - cache.AbolishRelation("park") - cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) - return nil -} - -func (_ParkService) ListAllParkBelongsTo(uid, keyword string) ([]model.Park, error) { - if parks, _ := cache.RetreiveSearch[[]model.Park]("park", "belong", uid, keyword); parks != nil { - return *parks, nil - } - parks := make([]model.Park, 0) - cond := global.DB.NewSelect().Model(&parks). - Where("user_id = ?", uid) - if len(keyword) > 0 { - keywordCond := "%" + keyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("name like ?", keywordCond). - WhereOr("abbr like ?", keywordCond). - WhereOr("address like ?", keywordCond). - WhereOr("contact like ?", keywordCond). - WhereOr("phone like ?", keywordCond) - }) - } - ctx, cancel := global.TimeoutContext() - defer cancel() - err := cond.Scan(ctx) - if err != nil { - return make([]model.Park, 0), err - } - relations := []string{"park"} - for _, p := range parks { - relations = append(relations, fmt.Sprintf("park:%s", p.Id)) - } - cache.CacheSearch(parks, relations, "park", "belong", uid, keyword) - return parks, nil -} - -func (_ParkService) FetchParkDetail(pid string) (*model.Park, error) { - if park, _ := cache.RetreiveEntity[model.Park]("park", pid); park != nil { - return park, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - var park = new(model.Park) - err := global.DB.NewSelect().Model(park). - Where("id = ?", pid). - Scan(ctx) - if err != nil { - return nil, exceptions.NewNotFoundErrorFromError("未找到符合条件的园区记录。", err) - } - cache.CacheEntity(*park, []string{fmt.Sprintf("park:%s", pid)}, "park", pid) - return park, nil -} - -func (_ParkService) EnsurePark(uid, pid string) (bool, error) { - if has, _ := cache.CheckExists("park", pid, uid); has { - return has, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - has, err := global.DB.NewSelect().Model((*model.Park)(nil)). - Where("id = ?", pid). - Where("user_id = ?", uid). - Exists(ctx) - if has { - cache.CacheExists([]string{fmt.Sprintf("park:%s", pid)}, "park", pid, uid) - } - return has, err -} - -func (_ParkService) AllParkIds(uid string) ([]string, error) { - if ids, _ := cache.RetreiveSearch[[]string]("park", "belong", uid); ids != nil { - return *ids, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - var ids = make([]string, 0) - err := global.DB.NewSelect().Model((*model.Park)(nil)). - Where("user_id = ?", uid). - Column("id"). - Scan(ctx, &ids) - if err != nil { - return make([]string, 0), err - } - cache.CacheSearch(ids, []string{"park"}, "park", "belong", uid) - return ids, nil -} diff --git a/service/region.go b/service/region.go deleted file mode 100644 index 4e6f0ff..0000000 --- a/service/region.go +++ /dev/null @@ -1,70 +0,0 @@ -package service - -import ( - "electricity_bill_calc/cache" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - - "github.com/samber/lo" - "go.uber.org/zap" -) - -type _RegionService struct { - l *zap.Logger -} - -var RegionService = _RegionService{ - l: logger.Named("Service", "Region"), -} - -func (_RegionService) FetchSubRegions(parent string) ([]model.Region, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - if regions, _ := cache.RetreiveSearch[[]model.Region]("region", "parent", parent); regions != nil { - return *regions, nil - } - regions := make([]model.Region, 0) - err := global.DB.NewSelect().Model(®ions).Where("parent = ?", parent).Order("code asc").Scan(ctx) - if err != nil { - return make([]model.Region, 0), err - } - relationNames := lo.Map(regions, func(r model.Region, index int) string { - return fmt.Sprintf("region:%s", r.Code) - }) - cache.CacheSearch(regions, relationNames, "region", "parent", parent) - return regions, err -} - -func (r _RegionService) FetchAllParentRegions(code string) ([]model.Region, error) { - regions := make([]model.Region, 0) - region, err := r.fetchRegion(code) - if err != nil { - return regions, err - } - regions = append(regions, *region) - for region.Level > 1 { - region, err = r.fetchRegion(region.Parent) - if err != nil { - return make([]model.Region, 0), nil - } - regions = append(regions, *region) - } - return regions, nil -} - -func (_RegionService) fetchRegion(code string) (*model.Region, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - if cachedRegion, _ := cache.RetreiveSearch[model.Region]("region", code); cachedRegion != nil { - return cachedRegion, nil - } - region := new(model.Region) - err := global.DB.NewSelect().Model(region).Where("code = ?", code).Scan(ctx) - if err != nil { - relationName := fmt.Sprintf("region:%s", code) - cache.CacheSearch(region, []string{relationName}, "region", code) - } - return region, err -} diff --git a/service/report.go b/service/report.go deleted file mode 100644 index da5043d..0000000 --- a/service/report.go +++ /dev/null @@ -1,742 +0,0 @@ -package service - -import ( - "database/sql" - "electricity_bill_calc/cache" - "electricity_bill_calc/config" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "electricity_bill_calc/tools" - "fmt" - "strconv" - "time" - - "github.com/fufuok/utils" - "github.com/google/uuid" - "github.com/samber/lo" - "github.com/shopspring/decimal" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _ReportService struct { - l *zap.Logger -} - -var ReportService = _ReportService{ - l: logger.Named("Service", "Report"), -} - -func (_ReportService) FetchParksWithNewestReport(uid string) ([]model.ParkNewestReport, error) { - if cachedParks, _ := cache.RetreiveSearch[[]model.ParkNewestReport]("park_newest_report", uid); cachedParks != nil { - return *cachedParks, nil - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - parks := make([]model.Park, 0) - err := global.DB.NewSelect().Model(&parks).Relation("Reports"). - Where("user_id = ?", uid). - Where("enabled = ?", true). - Order("created_at asc"). - Scan(ctx) - if err != nil { - return make([]model.ParkNewestReport, 0), err - } - - reducedParks := lo.Reduce( - parks, - func(acc map[string]model.ParkNewestReport, elem model.Park, index int) map[string]model.ParkNewestReport { - if _, ok := acc[elem.Id]; !ok { - newestReport := lo.MaxBy(elem.Reports, func(a, b *model.Report) bool { - return a.Period.After(b.Period) - }) - acc[elem.Id] = model.ParkNewestReport{ - Report: newestReport, - Park: elem, - } - } - return acc - }, - make(map[string]model.ParkNewestReport, 0), - ) - relations := lo.Map(parks, func(r model.Park, _ int) string { - return fmt.Sprintf("park:%s", r.Id) - }) - relations = append(relations, "park", "report") - cache.CacheSearch(reducedParks, relations, "park_newest_report", uid) - return lo.Values(reducedParks), nil -} - -func (_ReportService) IsNewPeriodValid(uid, pid string, period time.Time) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - reports := make([]model.Report, 0) - if cachedReport, _ := cache.RetreiveSearch[[]model.Report]("report", "user", uid, "park", pid); cachedReport != nil { - reports = *cachedReport - } else { - err := global.DB.NewSelect().Model(&reports).Relation("Park"). - Where("park.user_id = ?", uid). - Where("r.park_id = ?", pid). - Scan(ctx) - if err != nil { - return false, err - } - cache.CacheSearch(reports, []string{"report", "park"}, "park", "user", uid, "park", pid) - } - // 检查给定的期数在目前的记录中是否已经存在 - exists := lo.Reduce( - reports, - func(acc bool, elem model.Report, index int) bool { - if elem.Period.Equal(period) { - return acc || true - } else { - return acc || false - } - }, - false, - ) - if exists { - return false, nil - } - // 检查给定的期数与目前已发布的最大期数的关系 - maxPublished := lo.Reduce( - reports, - func(acc *time.Time, elem model.Report, index int) *time.Time { - if elem.Published { - if acc == nil || (acc != nil && elem.Period.After(*acc)) { - return &elem.Period - } - } - return acc - }, - nil, - ) - // 检查给定的期数与目前未发布的最大期数的关系 - maxUnpublished := lo.Reduce( - reports, - func(acc *time.Time, elem model.Report, index int) *time.Time { - if acc == nil || (acc != nil && elem.Period.After(*acc)) { - return &elem.Period - } - return acc - }, - nil, - ) - if maxUnpublished == nil { - return true, nil - } - if maxPublished != nil && maxUnpublished.Equal(*maxPublished) { - // 此时不存在未发布的报表 - return tools.IsNextMonth(*maxPublished, period), nil - } else { - // 存在未发布的报表 - return false, nil - } -} - -func (_ReportService) InitializeNewReport(parkId string, period time.Time) (string, error) { - ctx, cancel := global.TimeoutContext(120) - defer cancel() - - periods := make([]model.Report, 0) - err := global.DB.NewSelect().Model(&periods). - Where("park_id = ?", parkId). - Where("published = ?", true). - Order("period asc"). - Scan(ctx) - if err != nil { - return "", err - } - // 获取上一期的报表索引信息 - maxPublishedReport := lo.Reduce( - periods, - func(acc *model.Report, elem model.Report, index int) *model.Report { - if acc == nil || (acc != nil && elem.Period.After(acc.Period)) { - return &elem - } - return acc - }, - nil, - ) - var indexedLastPeriodCustomers map[string]model.EndUserDetail - if maxPublishedReport != nil { - // 获取上一期的所有户表信息,并获取当前已启用的所有用户 - lastPeriodCustomers := make([]model.EndUserDetail, 0) - err = global.DB.NewSelect().Model(&lastPeriodCustomers). - Where("report_id = ?", maxPublishedReport.Id). - Scan(ctx) - if err != nil { - return "", err - } - indexedLastPeriodCustomers = lo.Reduce( - lastPeriodCustomers, - func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail { - acc[elem.MeterId] = elem - return acc - }, - make(map[string]model.EndUserDetail, 0), - ) - } else { - indexedLastPeriodCustomers = make(map[string]model.EndUserDetail, 0) - } - currentActivatedCustomers := make([]model.Meter04KV, 0) - err = global.DB.NewSelect().Model(¤tActivatedCustomers). - Where("park_id = ?", parkId). - Where("enabled = ?", true). - Scan(ctx) - if err != nil { - return "", err - } - var parkInfo = new(model.Park) - err = global.DB.NewSelect().Model(parkInfo). - Where("id = ?", parkId). - Scan(ctx) - if err != nil || parkInfo == nil { - return "", exceptions.NewNotFoundError(fmt.Sprintf("指定园区未找到, %v", err)) - } - // 生成新一期的报表 - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return "", err - } - // 插入已经生成的报表索引信息和园区概况信息 - newReport := model.Report{ - Id: uuid.New().String(), - ParkId: parkId, - Period: period, - Category: parkInfo.Category, - SubmeterType: parkInfo.SubmeterType, - StepState: model.NewSteps(), - Published: false, - Withdraw: model.REPORT_NOT_WITHDRAW, - } - newReportSummary := model.ReportSummary{ - ReportId: newReport.Id, - } - _, err = tx.NewInsert().Model(&newReport).Exec(ctx) - if err != nil { - tx.Rollback() - return "", err - } - _, err = tx.NewInsert().Model(&newReportSummary).Exec(ctx) - if err != nil { - tx.Rollback() - return "", err - } - // 生成并插入户表信息 - var inserts = make([]model.EndUserDetail, 0) - for _, customer := range currentActivatedCustomers { - newEndUser := model.EndUserDetail{ - ReportId: newReport.Id, - ParkId: parkId, - MeterId: customer.Code, - Seq: customer.Seq, - Ratio: customer.Ratio, - Address: customer.Address, - CustomerName: customer.CustomerName, - ContactName: customer.ContactName, - ContactPhone: customer.ContactPhone, - IsPublicMeter: customer.IsPublicMeter, - LastPeriodOverall: decimal.Zero, - LastPeriodCritical: decimal.Zero, - LastPeriodPeak: decimal.Zero, - LastPeriodFlat: decimal.Zero, - LastPeriodValley: decimal.Zero, - } - if lastPeriod, ok := indexedLastPeriodCustomers[customer.Code]; ok { - newEndUser.LastPeriodOverall = lastPeriod.CurrentPeriodOverall - newEndUser.LastPeriodCritical = lastPeriod.CurrentPeriodCritical - newEndUser.LastPeriodPeak = lastPeriod.CurrentPeriodPeak - newEndUser.LastPeriodFlat = lastPeriod.CurrentPeriodFlat - newEndUser.LastPeriodValley = lastPeriod.CurrentPeriodValley - } - inserts = append(inserts, newEndUser) - } - if len(inserts) > 0 { - _, err = tx.NewInsert().Model(&inserts).Exec(ctx) - if err != nil { - tx.Rollback() - return "", err - } - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return "", err - } - cache.AbolishRelation("report") - return newReport.Id, nil -} - -func (_ReportService) RetreiveReportIndex(rid string) (*model.Report, error) { - if cachedReport, _ := cache.RetreiveEntity[model.Report]("report", rid); cachedReport != nil { - return cachedReport, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - var report = new(model.Report) - err := global.DB.NewSelect().Model(report). - Where("id = ?", rid). - Scan(ctx) - if err != nil { - return nil, err - } - cache.CacheEntity(report, []string{fmt.Sprintf("report:%s", rid), "park"}, "report", rid) - return report, nil -} - -func (_ReportService) RetreiveReportSummary(rid string) (*model.ReportSummary, error) { - if cachedSummary, _ := cache.RetreiveEntity[model.ReportSummary]("report_summary", rid); cachedSummary != nil { - return cachedSummary, nil - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - var summary = new(model.ReportSummary) - err := global.DB.NewSelect().Model(summary). - Where("report_id = ?", rid). - Scan(ctx) - if err != nil { - return nil, err - } - cache.CacheEntity(summary, []string{fmt.Sprintf("report:%s", rid), "park"}, "report_summary", rid) - return summary, nil -} - -func (_ReportService) UpdateReportSummary(summary *model.ReportSummary) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - _, err := global.DB.NewUpdate().Model(summary). - WherePK(). - Column("overall", "overall_fee", "critical", "critical_fee", "peak", "peak_fee", "valley", "valley_fee", "basic_fee", "adjust_fee"). - Exec(ctx) - if err == nil { - cache.AbolishRelation(fmt.Sprintf("report:%s", summary.ReportId)) - } - return err -} - -func (_ReportService) CalculateSummaryAndFinishStep(reportId string) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - var report = new(model.Report) - err := global.DB.NewSelect().Model(report).Relation("Summary"). - Where("r.id = ?", reportId). - Scan(ctx) - if err != nil || report == nil { - return exceptions.NewNotFoundErrorFromError("未找到指定报表", err) - } - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - - report.Summary.CalculatePrices() - _, err = tx.NewUpdate().Model(report.Summary). - WherePK(). - Column("overall_price", "critical_price", "peak_price", "flat", "flat_fee", "flat_price", "valley_price", "consumption_fee"). - Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - report.StepState.Summary = true - _, err = tx.NewUpdate().Model(report). - WherePK(). - Column("step_state"). - Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - return nil -} - -func (_ReportService) FetchWillDulutedMaintenanceFees(reportId string) ([]model.WillDilutedFee, error) { - if cachedFees, _ := cache.RetreiveSearch[[]model.WillDilutedFee]("will_diluted_fee", "report", reportId); cachedFees != nil { - return *cachedFees, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - fees := make([]model.WillDilutedFee, 0) - err := global.DB.NewSelect().Model(&fees). - Where("report_id = ?", reportId). - Order("created_at asc"). - Scan(ctx) - if err != nil { - return make([]model.WillDilutedFee, 0), nil - } - relations := lo.Map(fees, func(f model.WillDilutedFee, _ int) string { - return fmt.Sprintf("will_diluted_fee:%s", f.Id) - }) - relations = append(relations, fmt.Sprintf("report:will_diluted_fee:%s", reportId), fmt.Sprintf("report:%s", reportId), "park") - cache.CacheSearch(fees, relations, "will_diluted_fee", "report", reportId) - return fees, nil -} - -func (_ReportService) CreateTemporaryWillDilutedMaintenanceFee(fee model.WillDilutedFee) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - - fee.Id = utils.UUIDString() - _, err := global.DB.NewInsert().Model(&fee).Exec(ctx) - cache.AbolishRelation(fmt.Sprintf("report:will_diluted_fee:%s", fee.ReportId)) - return err -} - -func (_ReportService) BatchSaveMaintenanceFee(reportId string, fees []model.WillDilutedFee) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - // 首先删除所有预定义的部分,条件是指定报表ID,SourceID不为空。 - _, err = tx.NewDelete().Model((*model.WillDilutedFee)(nil)). - Where("report_id = ?", reportId). - Where("source_id is not null"). - Exec(ctx) - if err != nil { - tx.Rollback() - return err - } - // 然后插入新的记录 - _, err = tx.NewInsert().Model(&fees).Exec(ctx) - if err != nil { - return err - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return err - } - cache.AbolishRelation(fmt.Sprintf("report:will_diluted_fee:%s", reportId)) - return nil -} - -func (_ReportService) UpdateMaintenanceFee(feeId string, updates map[string]interface{}) (err error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - updates["last_modified_at"] = lo.ToPtr(time.Now()) - _, err = global.DB.NewUpdate().Model(&updates).TableExpr("will_diluted_fee"). - Where("id = ?", feeId). - Exec(ctx) - cache.AbolishRelation(fmt.Sprintf("will_diluted_fee:%s", feeId)) - return -} - -func (_ReportService) DeleteWillDilutedFee(fee string) (err error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - _, err = global.DB.NewDelete().Model((*model.WillDilutedFee)(nil)). - Where("id = ?", fee). - Exec(ctx) - cache.AbolishRelation(fmt.Sprintf("will_diluted_fee:%s", fee)) - return -} - -func (_ReportService) ProgressReportWillDilutedFee(report model.Report) (err error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - report.StepState.WillDiluted = true - _, err = global.DB.NewUpdate().Model(&report). - WherePK(). - Column("step_state"). - Exec(ctx) - cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id)) - return -} - -func (_ReportService) ProgressReportRegisterEndUser(report model.Report) (err error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - report.StepState.Submeter = true - _, err = global.DB.NewUpdate().Model(&report). - WherePK(). - Column("step_state"). - Exec(ctx) - cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id)) - return -} - -func (_ReportService) ProgressReportCalculate(report model.Report) (err error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - report.StepState.Calculate = true - _, err = global.DB.NewUpdate().Model(&report). - WherePK(). - Column("step_state"). - Exec(ctx) - cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id)) - return -} - -func (_ReportService) RetreiveParkEndUserMeterType(reportId string) (int, error) { - if cachedType, _ := cache.RetreiveEntity[int]("park_end_user_meter_type", fmt.Sprintf("report_%s", reportId)); cachedType != nil { - return *cachedType, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - var mType int - err := global.DB.NewSelect().Model((*model.Report)(nil)). - Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Column("meter_04kv_type") - }). - ExcludeColumn("*"). - Where("r.id = ?", reportId). - Scan(ctx, &mType) - if err != nil { - return -1, err - } - cache.CacheEntity(mType, []string{fmt.Sprintf("report:%s", reportId), "park"}, "park_end_user_meter_type", fmt.Sprintf("report_%s", reportId)) - return mType, nil -} - -func (_ReportService) PublishReport(report model.Report) (err error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - report.Published = true - report.PublishedAt = lo.ToPtr(time.Now()) - report.StepState.Publish = true - _, err = global.DB.NewUpdate().Model(&report). - WherePK(). - Column("step_state", "published", "published_at"). - Exec(ctx) - cache.AbolishRelation(fmt.Sprintf("report:%s", report.Id)) - return -} - -func (_ReportService) SearchReport(requestUser, requestPark, requestKeyword string, requestPeriod *time.Time, requestPage int, onlyPublished bool) ([]model.JoinedReportForWithdraw, int64, error) { - var ( - conditions = make([]string, 0) - reports = make([]model.Report, 0) - cond = global.DB.NewSelect(). - Model(&reports). - Relation("Park").Relation("Park.Enterprise") - ) - conditions = append(conditions, strconv.Itoa(requestPage)) - if onlyPublished { - cond = cond.Where("r.published = ?", true) - } - conditions = append(conditions, strconv.FormatBool(onlyPublished)) - if len(requestUser) > 0 { - cond = cond.Where("park.user_id = ?", requestUser) - conditions = append(conditions, requestUser) - } - if len(requestPark) > 0 { - cond = cond.Where("park.id = ?", requestPark) - conditions = append(conditions, requestPark) - } - if requestPeriod != nil { - cond = cond.Where("r.period = ?", *requestPeriod) - conditions = append(conditions, strconv.FormatInt(requestPeriod.Unix(), 10)) - } - if len(requestKeyword) > 0 { - keywordCond := "%" + requestKeyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("park.name like ?", keywordCond). - WhereOr("park__enterprise.name like ?", keywordCond). - WhereOr("park__enterprise.abbr like ?", keywordCond). - WhereOr("park.abbr like ?", keywordCond). - WhereOr("park__enterprise.address like ?", keywordCond). - WhereOr("park.address like ?", keywordCond) - }) - conditions = append(conditions, requestKeyword) - } - if cachedTotal, err := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 && err == nil { - if cachedRecords, _ := cache.RetreiveSearch[[]model.JoinedReportForWithdraw]("join_report_for_withdraw", conditions...); cachedRecords != nil { - return *cachedRecords, cachedTotal, nil - } - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize - - total, err := cond.Limit(config.ServiceSettings.ItemsPageSize). - Offset(startItem). - ScanAndCount(ctx) - - records := make([]model.JoinedReportForWithdraw, 0) - relations := []string{"report", "park"} - for _, r := range reports { - records = append(records, model.JoinedReportForWithdraw{ - Report: r, - Park: model.FromPark(*r.Park), - User: model.FromUserDetail(*r.Park.Enterprise), - }) - relations = append(relations, fmt.Sprintf("report:%s", r.Id)) - } - - cache.CacheCount(relations, "join_report_for_withdraw", int64(total), conditions...) - cache.CacheSearch(records, relations, "join_report_for_withdraw", conditions...) - return records, int64(total), err -} - -func (_ReportService) AssembleReportPublicity(reportId string) (*model.Publicity, error) { - if cachedPublicity, _ := cache.RetreiveEntity[model.Publicity]("publicity", reportId); cachedPublicity != nil { - return cachedPublicity, nil - } - // 资料准备 - ctx, cancel := global.TimeoutContext() - defer cancel() - - var report = new(model.Report) - err := global.DB.NewSelect().Model(report). - Relation("Summary").Relation("WillDilutedFees").Relation("EndUsers"). - Relation("Park").Relation("Park.Enterprise"). - Where("r.id = ?", reportId). - Scan(ctx) - if err != nil { - return nil, exceptions.NewNotFoundErrorFromError("未找到指定的公示报表", err) - } - - // 组合数据 - paidPart := model.PaidPart{ - Overall: report.Summary.Overall, - OverallPrice: report.Summary.OverallPrice.Decimal, - ConsumptionFee: report.Summary.ConsumptionFee.Decimal, - OverallFee: report.Summary.OverallFee, - Critical: decimal.NewNullDecimal(report.Summary.Critical), - CriticalPrice: report.Summary.CriticalPrice, - CriticalFee: decimal.NewNullDecimal(report.Summary.CriticalFee), - Peak: decimal.NewNullDecimal(report.Summary.Peak), - PeakPrice: report.Summary.PeakPrice, - PeakFee: decimal.NewNullDecimal(report.Summary.PeakFee), - Flat: decimal.NewNullDecimal(report.Summary.Flat), - FlatPrice: report.Summary.FlatPrice, - FlatFee: decimal.NewNullDecimal(report.Summary.FlatFee), - Valley: decimal.NewNullDecimal(report.Summary.Valley), - ValleyPrice: report.Summary.ValleyPrice, - ValleyFee: decimal.NewNullDecimal(report.Summary.ValleyFee), - BasicFee: report.Summary.BasicFee, - AdjustFee: report.Summary.AdjustFee, - } - endUserSummary := model.ConsumptionOverallPart{ - Overall: report.Summary.Customers.Consumption.Decimal, - OverallPrice: report.Summary.OverallPrice.Decimal, - ConsumptionFee: report.Summary.Customers.ConsumptionFee.Decimal, - OverallFee: report.Summary.Customers.ConsumptionFee.Decimal, - Critical: report.Summary.Customers.Critical, - CriticalPrice: report.Summary.CriticalPrice, - CriticalFee: report.Summary.Customers.CriticalFee, - Peak: report.Summary.Customers.Peak, - PeakPrice: report.Summary.PeakPrice, - PeakFee: report.Summary.Customers.PeakFee, - Flat: report.Summary.Customers.Flat, - FlatPrice: report.Summary.FlatPrice, - FlatFee: report.Summary.Customers.FlatFee, - Valley: report.Summary.Customers.Valley, - ValleyPrice: report.Summary.ValleyPrice, - ValleyFee: report.Summary.Customers.ValleyFee, - Proportion: report.Summary.Customers.Proportion.Decimal, - } - lossPart := model.LossPart{ - Quantity: report.Summary.Loss.Decimal, - Price: report.Summary.OverallPrice.Decimal, - ConsumptionFee: report.Summary.LossFee.Decimal, - Proportion: report.Summary.LossProportion.Decimal, - AuthorizeQuantity: report.Summary.AuthorizeLoss.Decimal, - AuthorizeConsumptionFee: report.Summary.AuthorizeLossFee.Decimal, - } - publicSummary := model.ConsumptionOverallPart{ - Overall: report.Summary.Publics.Consumption.Decimal, - OverallPrice: report.Summary.OverallPrice.Decimal, - ConsumptionFee: report.Summary.Publics.ConsumptionFee.Decimal, - OverallFee: report.Summary.Publics.ConsumptionFee.Decimal, - Critical: report.Summary.Publics.Critical, - CriticalPrice: report.Summary.CriticalPrice, - CriticalFee: report.Summary.Publics.CriticalFee, - Peak: report.Summary.Publics.Peak, - PeakPrice: report.Summary.PeakPrice, - PeakFee: report.Summary.Publics.PeakFee, - Flat: report.Summary.Publics.Flat, - FlatPrice: report.Summary.FlatPrice, - FlatFee: report.Summary.Publics.FlatFee, - Valley: report.Summary.Publics.Valley, - ValleyPrice: report.Summary.ValleyPrice, - ValleyFee: report.Summary.Publics.ValleyFee, - Proportion: report.Summary.Publics.Proportion.Decimal, - } - otherCollection := model.OtherShouldCollectionPart{ - LossFee: report.Summary.AuthorizeLossFee, - BasicFees: report.Summary.BasicFee.Add(report.Summary.AdjustFee), - } - finalAdjustFee := lossPart.AuthorizeConsumptionFee.Add(otherCollection.BasicFees) - var adjustPrice = decimal.Zero - if !endUserSummary.Overall.Equal(decimal.Zero) { - adjustPrice = finalAdjustFee.Div(endUserSummary.Overall).RoundBank(8) - } - var adjustProportion = decimal.Zero - if !paidPart.OverallFee.Equal(decimal.Zero) { - adjustProportion = finalAdjustFee.Div(paidPart.OverallFee.Add(finalAdjustFee)).RoundBank(8) - } - maintenanceFees := model.MaintenancePart{ - BasicFees: otherCollection.BasicFees, - LossFee: lossPart.AuthorizeConsumptionFee, - AdjustFee: finalAdjustFee, - LossProportion: lossPart.Proportion, - AdjustPrice: adjustPrice, - AdjustProportion: adjustProportion, - } - if maintenanceFees.LossProportion.GreaterThan(decimal.NewFromFloat(0.1)) { - maintenanceFees.LossProportion = decimal.NewFromFloat(0.1) - } - endUsers := lo.Map( - report.EndUsers, - func(elem *model.EndUserDetail, index int) model.EndUserSummary { - return model.EndUserSummary{ - CustomerName: elem.CustomerName, - Address: elem.Address, - MeterId: elem.MeterId, - IsPublicMeter: elem.IsPublicMeter, - Overall: elem.Overall.Decimal, - OverallPrice: report.Summary.OverallPrice.Decimal, - OverallFee: elem.OverallFee.Decimal, - Critical: elem.Critical, - CriticalFee: elem.CriticalFee, - Peak: elem.Peak, - PeakFee: elem.PeakFee, - Valley: elem.Valley, - ValleyFee: elem.ValleyFee, - Loss: elem.LossDiluted.Decimal, - LossFee: elem.LossFeeDiluted.Decimal, - } - }, - ) - - publicity := &model.Publicity{ - Report: *report, - Park: *report.Park, - User: *report.Park.Enterprise, - Paid: paidPart, - EndUser: endUserSummary, - Loss: lossPart, - PublicConsumptionOverall: publicSummary, - OtherCollections: otherCollection, - Maintenance: maintenanceFees, - EndUserDetails: endUsers, - } - cache.CacheEntity(publicity, []string{fmt.Sprintf("publicity:%s", reportId), fmt.Sprintf("report:%s", reportId), "report", "park"}, "publicity", reportId) - - return publicity, nil -} diff --git a/service/statistics.go b/service/statistics.go deleted file mode 100644 index d500881..0000000 --- a/service/statistics.go +++ /dev/null @@ -1,94 +0,0 @@ -package service - -import ( - "electricity_bill_calc/cache" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - - "github.com/samber/lo" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _StatisticsService struct { - l *zap.Logger -} - -var StatisticsService = _StatisticsService{ - l: logger.Named("Service", "Stat"), -} - -func (_StatisticsService) EnabledEnterprises() (int64, error) { - if cachedCount, err := cache.RetreiveCount("enabled_ent"); cachedCount != -1 && err == nil { - return cachedCount, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - c, err := global.DB.NewSelect().Model((*model.User)(nil)). - Where("type = ?", model.USER_TYPE_ENT). - Where("enabled = ?", true). - Count(ctx) - if err == nil { - cache.CacheCount([]string{"user"}, "enabled_ent", int64(c)) - } - return int64(c), err -} - -func (_StatisticsService) EnabledParks(userIds ...string) (int64, error) { - if cachedParks, err := cache.RetreiveCount("enabled_parks", userIds...); cachedParks != -1 && err == nil { - return cachedParks, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - query := global.DB.NewSelect().Model((*model.Park)(nil)). - Where("enabled = ?", true) - if len(userIds) > 0 { - query = query.Where("user_id in (?)", bun.In(userIds)) - } - c, err := query.Count(ctx) - if err == nil { - cache.CacheCount([]string{"user", "park"}, "enabled_parks", int64(c), userIds...) - } - return int64(c), err -} - -func (_StatisticsService) ParksNewestState(userIds ...string) ([]model.ParkPeriodStatistics, error) { - if cachedState, _ := cache.RetreiveSearch[[]model.ParkPeriodStatistics]("park_period_stat", userIds...); cachedState != nil { - return *cachedState, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - query := global.DB.NewSelect().Model((*model.Report)(nil)). - Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Column("id", "name") - }). - Where("park.enabled = ?", true). - Where("r.published = ?", true) - if len(userIds) > 0 { - query = query.Where("park.user_id in (?)", bun.In(userIds)) - } - parks := make([]model.ParkPeriodStatistics, 0) - groupedParks := make(map[string]model.ParkPeriodStatistics, 0) - err := query.Column("period").Scan(ctx, &parks) - if err != nil { - return make([]model.ParkPeriodStatistics, 0), err - } - for _, p := range parks { - if c, ok := groupedParks[p.Id]; ok { - if c.Period != nil && p.Period != nil && p.Period.After(c.Period.Time) { - groupedParks[p.Id] = p - } - if c.Period == nil && p.Period != nil { - groupedParks[p.Id] = p - } - } else { - groupedParks[p.Id] = p - } - } - cache.CacheSearch(lo.Values(groupedParks), []string{"user", "park"}, "park_period_stat", userIds...) - return lo.Values(groupedParks), nil -} diff --git a/service/user.go b/service/user.go deleted file mode 100644 index bc28c04..0000000 --- a/service/user.go +++ /dev/null @@ -1,447 +0,0 @@ -package service - -import ( - "database/sql" - "electricity_bill_calc/cache" - "electricity_bill_calc/config" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "electricity_bill_calc/tools" - "fmt" - "strconv" - "time" - - "github.com/fufuok/utils" - "github.com/google/uuid" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _UserService struct { - l *zap.Logger -} - -var UserService = _UserService{ - l: logger.Named("Service", "User"), -} - -func (u _UserService) ProcessEnterpriseUserLogin(username, password string) (*model.Session, error) { - user, err := u.findUserWithCredentialsByUsername(username) - - if err != nil { - return nil, err - } - if user == nil { - return nil, exceptions.NewAuthenticationError(404, "用户不存在。") - } - if user.Type != 0 { - return nil, exceptions.NewAuthenticationError(400, "用户类型不正确。") - } - if !user.Enabled { - return nil, exceptions.NewAuthenticationError(403, "用户已被禁用。") - } - hashedPassword := utils.Sha512Hex(password) - if hashedPassword != user.Password { - return nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。") - } - if user.ResetNeeded { - authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。") - authErr.NeedReset = true - return nil, authErr - } - userDetial, _ := u.retreiveUserDetail(user.Id) - if userDetial.ServiceExpiration.Time.Before(time.Now()) { - return nil, exceptions.NewAuthenticationError(406, "用户服务期限已过。") - } - session := &model.Session{ - Token: uuid.New().String(), - Uid: user.Id, - Type: user.Type, - Name: user.Username, - ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), - } - if userDetial != nil { - session.Name = *userDetial.Name - } - cache.CacheSession(session) - return session, nil -} - -func (u _UserService) ProcessManagementUserLogin(username, password string) (*model.Session, error) { - user, err := u.findUserWithCredentialsByUsername(username) - - if err != nil { - return nil, err - } - if user == nil { - return nil, exceptions.NewAuthenticationError(404, "用户不存在。") - } - if user.Type != 1 && user.Type != 2 { - return nil, exceptions.NewAuthenticationError(400, "用户类型不正确。") - } - if !user.Enabled { - return nil, exceptions.NewAuthenticationError(403, "用户已被禁用。") - } - hashedPassword := utils.Sha512Hex(password) - if hashedPassword != user.Password { - return nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。") - } - if user.ResetNeeded { - authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。") - authErr.NeedReset = true - return nil, authErr - } - session := &model.Session{ - Token: uuid.New().String(), - Uid: user.Id, - Type: user.Type, - Name: user.Username, - ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), - } - userDetial, _ := u.retreiveUserDetail(user.Id) - if userDetial != nil { - session.Name = *userDetial.Name - } - cache.CacheSession(session) - return session, nil -} - -func (u _UserService) InvalidUserPassword(uid string) (string, error) { - user, err := u.findUserByID(uid) - if user == nil && err != nil { - return "", exceptions.NewNotFoundError("指定的用户不存在。") - } - ctx, cancel := global.TimeoutContext() - defer cancel() - verifyCode := tools.RandStr(10) - user.Password = utils.Sha512Hex(verifyCode) - user.ResetNeeded = true - res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx) - if err != nil { - return "", err - } - if affected, _ := res.RowsAffected(); affected > 0 { - // ! 清除与此用户所有相关的记录。 - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) - return verifyCode, nil - } else { - return "", exceptions.NewUnsuccessfulOperationError() - } -} - -func (u _UserService) VerifyUserPassword(username, verifyCode string) (bool, error) { - user, err := u.findUserByUsername(username) - if user == nil || err != nil { - return false, exceptions.NewNotFoundError("指定的用户不存在。") - } - hashedVerifyCode := utils.Sha512Hex(verifyCode) - if hashedVerifyCode != user.Password { - return false, nil - } else { - return true, nil - } -} - -func (u _UserService) ResetUserPassword(username, password string) (bool, error) { - user, err := u.findUserByUsername(username) - if user == nil || err != nil { - return false, exceptions.NewNotFoundError("指定的用户不存在。") - } - ctx, cancel := global.TimeoutContext() - defer cancel() - user.Password = utils.Sha512Hex(password) - user.ResetNeeded = false - res, err := global.DB.NewUpdate().Model(user).WherePK().Column("password", "reset_needed").Exec(ctx) - if err != nil { - return false, err - } - if affected, _ := res.RowsAffected(); affected > 0 { - cache.AbolishRelation(fmt.Sprintf("user:%s", user.Id)) - return true, nil - } else { - return false, nil - } -} - -func (_UserService) IsUserExists(uid string) (bool, error) { - if has, _ := cache.CheckExists("user", uid); has { - return has, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("id = ?", uid).Exists(ctx) - if has { - cache.CacheExists([]string{"user", fmt.Sprintf("user_%s", uid)}, "user", uid) - } - return has, err -} - -func (_UserService) IsUsernameExists(username string) (bool, error) { - if has, _ := cache.CheckExists("user", username); has { - return has, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - has, err := global.DB.NewSelect().Model((*model.User)(nil)).Where("username = ?", username).Exists(ctx) - if has { - cache.CacheExists([]string{"user"}, "user", username) - } - return has, err -} - -func (u _UserService) CreateUser(user *model.User, detail *model.UserDetail) (string, error) { - if len(user.Id) == 0 { - user.Id = uuid.New().String() - } - exists, err := u.IsUserExists(user.Id) - if exists { - return "", exceptions.NewNotFoundError("user already exists") - } - if err != nil { - return "", nil - } - detail.Id = user.Id - - verifyCode := tools.RandStr(10) - user.Password = utils.Sha512Hex(verifyCode) - user.ResetNeeded = true - - if detail.Name != nil { - finalAbbr := tools.PinyinAbbr(*detail.Name) - detail.Abbr = &finalAbbr - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return "", err - } - _, err = tx.NewInsert().Model(user).Exec(ctx) - if err != nil { - tx.Rollback() - return "", fmt.Errorf("user create failed: %w", err) - } - _, err = tx.NewInsert().Model(detail).Exec(ctx) - if err != nil { - tx.Rollback() - return "", fmt.Errorf("user Detail create failed: %w", err) - } - err = tx.Commit() - if err != nil { - tx.Rollback() - return "", fmt.Errorf("transaction commit unsuccessful: %w", err) - } - // ! 广谱关联关系的废除必须是在有新记录加入或者有记录被删除的情况下。 - cache.AbolishRelation("user") - return verifyCode, nil -} - -func (u _UserService) SwitchUserState(uid string, enabled bool) error { - exists, err := u.IsUserExists(uid) - if !exists { - return exceptions.NewNotFoundError("user not exists") - } - if err != nil { - return err - } - newStateUser := new(model.User) - newStateUser.Id = uid - newStateUser.Enabled = enabled - ctx, cancel := global.TimeoutContext() - defer cancel() - res, err := global.DB.NewUpdate().Model(newStateUser).WherePK().Column("enabled").Exec(ctx) - if affected, _ := res.RowsAffected(); err == nil && affected > 0 { - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) - } - return err -} - -func (us _UserService) SearchLimitUsers(keyword string, limit int) ([]model.JoinedUserDetail, error) { - if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", keyword, strconv.Itoa(limit)); cachedUsers != nil { - return *cachedUsers, nil - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - - var users = make([]model.User, 0) - keywordCond := "%" + keyword + "%" - err := global.DB.NewSelect().Model(&users).Relation("Detail"). - Where("u.type = ?", model.USER_TYPE_ENT). - WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("u.username like ?", keywordCond). - WhereOr("detail.name like ?", keywordCond). - WhereOr("detail.abbr like ?", keywordCond). - WhereOr("detail.contact like ?", keywordCond). - WhereOr("detail.address like ?", keywordCond) - }). - Order("u.created_at asc"). - Limit(limit). - Offset(0). - Scan(ctx) - if err != nil { - return make([]model.JoinedUserDetail, 0), err - } - var detailedUsers = make([]model.JoinedUserDetail, 0) - var relations = make([]string, 0) - // ! 这里的转换是为了兼容之前使用Xorm时构建的关联关系而存在的 - for _, u := range users { - detailedUsers = append(detailedUsers, model.JoinedUserDetail{ - UserDetail: *u.Detail, - Id: u.Id, - Username: u.Username, - Type: u.Type, - Enabled: u.Enabled, - }) - relations = append(relations, fmt.Sprintf("user:%s", u.Id)) - } - relations = append(relations, "user") - cache.CacheSearch(detailedUsers, relations, "join_user_detail", keyword, strconv.Itoa(limit)) - return detailedUsers, nil -} - -func (_UserService) findUserWithCredentialsByUsername(username string) (*model.UserWithCredentials, error) { - if cachedUser, _ := cache.RetreiveSearch[model.UserWithCredentials]("user_with_credentials", username); cachedUser != nil { - return cachedUser, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - user := new(model.UserWithCredentials) - err := global.DB.NewSelect().Model(user).Where("username = ?", username).Scan(ctx) - if err == nil { - cache.CacheSearch(*user, []string{fmt.Sprintf("user:%s", user.Id)}, "user_with_credentials", username) - } - return user, err -} - -func (u _UserService) findUserByUsername(username string) (*model.User, error) { - if cachedUser, _ := cache.RetreiveSearch[model.User]("user", username); cachedUser != nil { - return cachedUser, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - user := new(model.User) - err := global.DB.NewSelect().Model(user).Where("username = ?", username).Scan(ctx) - if err == nil { - cache.CacheSearch(*user, []string{fmt.Sprintf("user:%s", user.Id)}, "user", username) - } - return user, err -} - -func (_UserService) retreiveUserDetail(uid string) (*model.UserDetail, error) { - if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { - return cachedUser, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - user := &model.UserDetail{ - Id: uid, - } - err := global.DB.NewSelect().Model(user).WherePK().Scan(ctx) - if err == nil { - cache.CacheEntity(*user, []string{fmt.Sprintf("user:%s", uid)}, "user_detail", uid) - } - return user, err -} - -func (_UserService) findUserByID(uid string) (*model.User, error) { - cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid) - if cachedUser != nil { - return cachedUser, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - user := &model.User{ - Id: uid, - } - err := global.DB.NewSelect().Model(&user).WherePK().Scan(ctx) - if err == nil { - cache.CacheEntity(*user, []string{fmt.Sprintf("user:%s", uid)}, "user", uid) - } - return user, err -} - -func (_UserService) ListUserDetail(keyword string, userType int, userState *bool, page int) ([]model.JoinedUserDetail, int64, error) { - var ( - cond = global.DB.NewSelect() - cacheConditions = make([]string, 0) - users = make([]model.User, 0) - ) - cond = cond.Model(&users).Relation("Detail") - cacheConditions = append(cacheConditions, strconv.Itoa(page)) - cond = cond.Where("detail.id <> ?", "000") - if len(keyword) != 0 { - keywordCond := "%" + keyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("u.username like ?", keywordCond). - WhereOr("detail.name like ?", keywordCond) - }) - cacheConditions = append(cacheConditions, keyword) - } - if userType != -1 { - cond = cond.Where("u.type = ?", userType) - cacheConditions = append(cacheConditions, strconv.Itoa(userType)) - } - if userState != nil { - cond = cond.Where("u.enabled = ?", *userState) - cacheConditions = append(cacheConditions, strconv.FormatBool(*userState)) - } - startItem := (page - 1) * config.ServiceSettings.ItemsPageSize - - // * 这里利用已经构建完成的条件集合从缓存中获取数据,如果所有数据都可以从缓存中获取,那么就直接返回了。 - if cacheCounts, err := cache.RetreiveCount("join_user_detail", cacheConditions...); cacheCounts != -1 && err == nil { - if cachedUsers, _ := cache.RetreiveSearch[[]model.JoinedUserDetail]("join_user_detail", cacheConditions...); cachedUsers != nil { - return *cachedUsers, cacheCounts, nil - } - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - total, err := cond. - Limit(config.ServiceSettings.ItemsPageSize).Offset(startItem). - ScanAndCount(ctx) - - var ( - joinedUsers = make([]model.JoinedUserDetail, 0) - relations = []string{"user"} - ) - for _, u := range users { - joinedUsers = append(joinedUsers, model.JoinedUserDetail{ - UserDetail: *u.Detail, - Id: u.Id, - Username: u.Username, - Type: u.Type, - Enabled: u.Enabled, - }) - relations = append(relations, fmt.Sprintf("user:%s", u.Id)) - } - - cache.CacheCount(relations, "join_user_detail", int64(total), cacheConditions...) - cache.CacheSearch(joinedUsers, relations, "join_user_detail", cacheConditions...) - return joinedUsers, int64(total), err -} - -func (_UserService) FetchUserDetail(uid string) (*model.FullJoinedUserDetail, error) { - if cachedUser, _ := cache.RetreiveEntity[model.FullJoinedUserDetail]("full_join_user_detail", uid); cachedUser != nil { - return cachedUser, nil - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - user := &model.User{} - err := global.DB.NewSelect().Model(user).Relation("Detail"). - Where("u.id = ?", uid). - Scan(ctx) - if err == nil { - fullJoinedUser := &model.FullJoinedUserDetail{ - User: *user, - UserDetail: *user.Detail, - } - cache.CacheEntity(*fullJoinedUser, []string{fmt.Sprintf("user:%s", uid)}, "full_join_user_detail", uid) - return fullJoinedUser, nil - } - return nil, err -} diff --git a/service/withdraw.go b/service/withdraw.go deleted file mode 100644 index dfb5066..0000000 --- a/service/withdraw.go +++ /dev/null @@ -1,160 +0,0 @@ -package service - -import ( - "electricity_bill_calc/cache" - "electricity_bill_calc/config" - "electricity_bill_calc/exceptions" - "electricity_bill_calc/global" - "electricity_bill_calc/logger" - "electricity_bill_calc/model" - "fmt" - "strconv" - "time" - - "github.com/samber/lo" - "github.com/uptrace/bun" - "go.uber.org/zap" -) - -type _WithdrawService struct { - l *zap.Logger -} - -var WithdrawService = _WithdrawService{ - l: logger.Named("Service", "Withdraw"), -} - -func (_WithdrawService) ApplyWithdraw(reportId string) (bool, error) { - ctx, cancel := global.TimeoutContext() - defer cancel() - - var report = new(model.Report) - err := global.DB.NewSelect().Model(report). - Where("id = ?", reportId). - Scan(ctx) - if err != nil || report == nil { - return false, exceptions.NewNotFoundErrorFromError("指定报表未能找到", err) - } - if !report.Published { - return false, exceptions.NewImproperOperateError("指定报表尚未发布。") - } - var maxPublished time.Time - err = global.DB.NewSelect().Model((*model.Report)(nil)). - ColumnExpr("max(period)"). - Where("park_id = ?", report.ParkId). - Where("published = ?", true). - Scan(ctx, &maxPublished) - if err != nil { - return false, exceptions.NewNotFoundError("未能找到匹配的系列报表。") - } - if !report.Period.Equal(maxPublished) { - return false, exceptions.NewImproperOperateError("申请撤回的报表必须是最新已发布的报表。") - } - - report.Withdraw = model.REPORT_WITHDRAW_APPLIED - report.LastWithdrawAppliedAt = lo.ToPtr(time.Now()) - _, err = global.DB.NewUpdate().Model(report). - WherePK(). - Column("withdraw", "last_withdraw_applied_at"). - Exec(ctx) - if err != nil { - return false, err - } - cache.AbolishRelation("withdraw_stat") - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - cache.AbolishRelation(fmt.Sprintf("publicity:%s", reportId)) - return true, nil -} - -func (_WithdrawService) FetchPagedWithdrawApplies(page int, keyword string) ([]model.JoinedReportForWithdraw, int64, error) { - var ( - conditions = make([]string, 0) - reports = make([]model.Report, 0) - cond = global.DB.NewSelect().Model(&reports). - Relation("Park").Relation("Park.Enterprise") - ) - conditions = append(conditions, strconv.Itoa(int(model.REPORT_WITHDRAW_APPLIED)), strconv.Itoa(page)) - cond = cond.Where("r.withdraw = ?", model.REPORT_WITHDRAW_APPLIED) - if len(keyword) > 0 { - keywordCond := "%" + keyword + "%" - cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { - return q.Where("p.name like ?", keywordCond). - WhereOr("p.abbr like ?", keywordCond). - WhereOr("d.name like ?", keywordCond). - WhereOr("d.abbr like ?", keywordCond) - }) - conditions = append(conditions, keyword) - } - if cachedTotal, err := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 && err == nil { - if cachedReports, _ := cache.RetreiveSearch[[]model.JoinedReportForWithdraw]("join_user_detail", conditions...); cachedReports != nil { - return *cachedReports, cachedTotal, err - } - } - - ctx, cancel := global.TimeoutContext() - defer cancel() - startItem := (page - 1) * config.ServiceSettings.ItemsPageSize - total, err := cond.Limit(config.ServiceSettings.ItemsPageSize). - Offset(startItem). - ScanAndCount(ctx) - - var ( - joinedReports = make([]model.JoinedReportForWithdraw, 0) - relations = []string{"report", "park"} - ) - for _, r := range reports { - joinedReports = append(joinedReports, model.JoinedReportForWithdraw{ - Report: r, - Park: model.FromPark(*r.Park), - User: model.FromUserDetail(*r.Park.Enterprise), - }) - relations = append(relations, fmt.Sprintf("report:%s", r.Id), fmt.Sprintf("publicity:%s", r.Id)) - } - - cache.CacheCount(relations, "join_report_for_withdraw", int64(total), conditions...) - cache.CacheSearch(joinedReports, relations, "join_report_for_withdraw", conditions...) - return joinedReports, int64(total), err -} - -func (_WithdrawService) AuditWithdraw(reportId string, granted bool) error { - ctx, cancel := global.TimeoutContext() - defer cancel() - - var report = new(model.Report) - err := global.DB.NewSelect().Model(report). - Where("id = ?", reportId). - Scan(ctx) - if err != nil { - return exceptions.NewNotFoundErrorFromError("指定公示报表未找到。", err) - } - report.Withdraw = lo.If(granted, model.REPORT_WITHDRAW_GRANTED).Else(model.REPORT_WITHDRAW_DENIED) - report.LastWithdrawAuditAt = lo.ToPtr(time.Now()) - if granted { - report.Published = false - } - _, err = global.DB.NewUpdate().Model(report). - WherePK(). - Column("withdraw", "last_withdraw_audit_at", "published"). - Exec(ctx) - if err == nil { - cache.AbolishRelation("withdraw_stat") - cache.AbolishRelation(fmt.Sprintf("report:%s", reportId)) - } - return err -} - -func (_WithdrawService) AuditWaits() (int64, error) { - if cachedWaits, err := cache.RetreiveCount("withdraw_waits"); cachedWaits != -1 && err == nil { - return cachedWaits, nil - } - ctx, cancel := global.TimeoutContext() - defer cancel() - - total, err := global.DB.NewSelect().Model((*model.Report)(nil)). - Where("withdraw = ?", model.REPORT_WITHDRAW_APPLIED). - Count(ctx) - if err == nil { - cache.CacheCount([]string{"withdraw_stat"}, "withdraw_waits", int64(total)) - } - return int64(total), err -} From 2b3048141994314dea88d168f5f07aa0144f53b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 31 May 2023 09:38:39 +0800 Subject: [PATCH 002/141] =?UTF-8?q?feat(serial):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=9B=AA=E8=8A=B1=E7=AE=97=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/serial/algorithm.go | 76 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tools/serial/algorithm.go diff --git a/tools/serial/algorithm.go b/tools/serial/algorithm.go new file mode 100644 index 0000000..9ce2354 --- /dev/null +++ b/tools/serial/algorithm.go @@ -0,0 +1,76 @@ +package serial + +import ( + "electricity_bill_calc/config" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/tools/time" + "fmt" + stdTime "time" + + "github.com/rueian/rueidis" + "github.com/samber/lo" + "go.uber.org/zap" +) + +var log = logger.Named("Algorithm", "Unique Serial") + +// 在防止服务器时间回拨的情况下,生成一个用于生成唯一编号的精简时间戳,该时间戳的起始时间为`2022-02-22 22:22:22.000000 +0800`,时间戳精确到秒。 +func generateTimestamp() int64 { + for { + timestamp := time.Timestamp() + cmds := make(rueidis.Commands, 0, 2) + cmds = append(cmds, global.Rd.B().Set().Key("LAST_TIMESTAMP").Value(fmt.Sprintf("%d", timestamp)).Nx().ExSeconds(1).Build()) + cmds = append(cmds, global.Rd.B().Get().Key("LAST_TIMESTAMP").Build()) + results := global.Rd.DoMulti(global.Ctx, cmds...) + store_res, err := lo.Last(results) + if err != nil { + log.Error("从Redis缓存中获取上一次的时间戳失败。", zap.Error(err)) + stdTime.Sleep(1 * stdTime.Second) + continue + } + if stored_timestamp, err := store_res.AsInt64(); err == nil && timestamp >= stored_timestamp { + return timestamp + } else { + log.Error("转换从Redis缓存中获取的时间戳失败。", zap.Error(err)) + stdTime.Sleep(1 * stdTime.Second) + continue + } + } +} + +// 生成一个基于精简改进的雪花算法的数字类型唯一编号。 +func GenerateUniqueSerial() int64 { + for { + timestamp := generateTimestamp() + cmds := make(rueidis.Commands, 0, 2) + cmds = append(cmds, global.Rd.B().Set().Key("SERIAL").Value(fmt.Sprintf("%d", 0)).Nx().ExSeconds(1).Build()) + cmds = append(cmds, global.Rd.B().Incr().Key("SERIAL").Build()) + results := global.Rd.DoMulti(global.Ctx, cmds...) + store_res, err := lo.Last(results) + if err != nil { + log.Error("从Redis缓存中获取上一次的序列号失败。", zap.Error(err)) + stdTime.Sleep(1 * stdTime.Second) + continue + } + if stored_serial, err := store_res.AsInt64(); err == nil { + return (timestamp << 20) | ((config.ServiceSettings.HostSerial & 0xffff) << 16) | (stored_serial & 0xffff_ffff) + } else { + log.Error("转换从Redis缓存中获取的序列号失败。", zap.Error(err)) + stdTime.Sleep(1 * stdTime.Second) + continue + } + } +} + +// 生成一个基于精简改进的雪花算法的字符串类型唯一编号。 +func GenerateUniqueSerialString() string { + uniqueId := GenerateUniqueSerial() + return fmt.Sprintf("%d", uniqueId) +} + +// 生成一个带有前缀的基于精简改进的雪花算法的字符串类型唯一编号。 +func GeneratePrefixedUniqueSerialString(prefix string) string { + uniqueId := GenerateUniqueSerial() + return fmt.Sprintf("%s%d", prefix, uniqueId) +} From 1ccd19d78b2695a70ed06241197689288e01d5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 31 May 2023 09:40:28 +0800 Subject: [PATCH 003/141] =?UTF-8?q?enhance(utils):=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B0=E5=8C=85?= =?UTF-8?q?=E7=9A=84=E4=BD=8D=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/{ => time}/time.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) rename tools/{ => time}/time.go (55%) diff --git a/tools/time.go b/tools/time/time.go similarity index 55% rename from tools/time.go rename to tools/time/time.go index c69b0fb..5bbd36b 100644 --- a/tools/time.go +++ b/tools/time/time.go @@ -1,9 +1,20 @@ -package tools +package time import ( "time" ) +var loc *time.Location = time.FixedZone("+0800", 8*60*60) + +func Now() time.Time { + return time.Now().In(loc) +} + +func Timestamp() int64 { + startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() + return Now().Unix() - startline +} + func DifferenceInMonth(t1, t2 time.Time) int { var differYear, differMonth int differYear = t1.Year() - t2.Year() From 83b0cd89f43afea6b07417042ac96d1d31d39627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 31 May 2023 09:40:57 +0800 Subject: [PATCH 004/141] =?UTF-8?q?enhance(config):=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=9B=AA=E8=8A=B1ID=E7=94=9F=E6=88=90=E7=AE=97=E6=B3=95?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=BB=E6=9C=BA=E7=BC=96=E5=8F=B7=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.go | 1 + settings.yaml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/settings.go b/config/settings.go index ccd4124..a46dd93 100644 --- a/config/settings.go +++ b/config/settings.go @@ -33,6 +33,7 @@ type ServiceSetting struct { MaxSessionLife time.Duration ItemsPageSize int CacheLifeTime time.Duration + HostSerial int64 } // 定义全局变量 diff --git a/settings.yaml b/settings.yaml index f926f74..4a0e548 100644 --- a/settings.yaml +++ b/settings.yaml @@ -19,4 +19,5 @@ Redis: Service: MaxSessionLife: 2h ItemsPageSize: 20 - CacheLifeTime: 5m \ No newline at end of file + CacheLifeTime: 5m + HostSerial: 5 From 31ec847ab2407e5798b2870d3ab6b0fecff6f7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 31 May 2023 09:41:52 +0800 Subject: [PATCH 005/141] =?UTF-8?q?enhance(model):=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BC=9A=E8=AF=9D=E4=B8=AD=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E7=B1=BB=E5=9E=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/session.go b/model/session.go index 536d823..eade844 100644 --- a/model/session.go +++ b/model/session.go @@ -5,7 +5,7 @@ import "time" type Session struct { Uid string `json:"uid"` Name string `json:"name"` - Type int8 `json:"type"` + Type int16 `json:"type"` Token string `json:"token"` ExpiresAt time.Time `json:"expiresAt" time_format:"simple_datetime" time_location:"shanghai"` } From 28609df9ec2e711d4ecd7f0c1eb5a53bf70d6809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 31 May 2023 14:47:50 +0800 Subject: [PATCH 006/141] =?UTF-8?q?enhance(user):=E4=BD=BF=E7=94=A8Goqu=20?= =?UTF-8?q?SQL=E6=9E=84=E5=BB=BA=E5=BA=93=E9=87=8D=E5=86=99=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=9E=84=E6=88=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 8 +++ model/user.go | 67 +++++++++++++++++++++- repository/user.go | 138 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 repository/user.go diff --git a/go.mod b/go.mod index 09bfea7..83046ec 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( ) require ( + github.com/doug-martin/goqu/v9 v9.18.0 github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index f0bfce6..f733226 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -55,6 +56,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY= +github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -74,6 +78,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -170,9 +175,11 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/liamylian/jsontime/v2 v2.0.0 h1:3if2kDW/boymUdO+4Qj/m4uaXMBSF6np9KEgg90cwH0= github.com/liamylian/jsontime/v2 v2.0.0/go.mod h1:UHp1oAPqCBfspokvGmaGe0IAl2IgOpgOgDaKPcvcGGY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -280,6 +287,7 @@ go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95a go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/model/user.go b/model/user.go index 391df8a..4895a82 100644 --- a/model/user.go +++ b/model/user.go @@ -1,7 +1,72 @@ package model +import ( + "time" + + "github.com/shopspring/decimal" +) + const ( - USER_TYPE_ENT int8 = iota + USER_TYPE_ENT int16 = iota USER_TYPE_SUP USER_TYPE_OPS ) + +type ManagementAccountCreationForm struct { + Id *string + Username string + Name string + Contact *string + Phone *string + Type int16 `json:"type"` + Enabled bool + Expires Date +} + +type User struct { + Id string + Username string + Password string + ResetNeeded bool + UserType int16 `db:"type"` + Enabled bool + CreatedAt *time.Time +} + +type UserDetail struct { + Id string + Name *string + Abbr *string + Region *string + Address *string + Contact *string + Phone *string + UnitServiceFee decimal.Decimal `db:"unit_service_fee"` + ServiceExpiration Date + CreatedAt time.Time + CreatedBy *string + LastModifiedAt time.Time + LastModifiedBy *string + DeletedAt *time.Time + DeletedBy *string +} + +type UserWithDetail struct { + Id string + Username string + ResetNeeded bool + UserType int16 `db:"type"` + Enabled bool + Name *string + Abbr *string + Region *string + Address *string + Contact *string + Phone *string + UnitServiceFee decimal.Decimal `db:"unit_service_fee"` + ServiceExpiration Date + CreatedAt time.Time + CreatedBy *string + LastModifiedAt time.Time + LastModifiedBy *string +} diff --git a/repository/user.go b/repository/user.go new file mode 100644 index 0000000..c304e93 --- /dev/null +++ b/repository/user.go @@ -0,0 +1,138 @@ +package repository + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "fmt" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _UserRepository struct { + log *zap.Logger +} + +var UserRepository = _UserRepository{ + log: logger.Named("Repository", "User"), +} + +// 使用用户名查询指定用户的基本信息 +func (ur _UserRepository) FindUserByUsername(username string) (*model.User, error) { + ur.log.Info("根据用户名查询指定用户的基本信息。", zap.String("username", username)) + if cachedUser, _ := cache.RetreiveEntity[model.User]("user", username); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户名条件的用户基本信息。", zap.String("username", username)) + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.User) + sql, params, _ := goqu.From("user").Where(goqu.C("username").Eq(username)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", username)}, "user", username) + return user, nil +} + +// 使用用户唯一编号查询指定用户的基本信息 +func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { + ur.log.Info("根据用户唯一编号查询指定用户的基本信息。", zap.String("user id", uid)) + if cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.User) + sql, params, _ := goqu.From("user").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user", uid) + return user, nil +} + +// 使用用户的唯一编号获取用户的详细信息 +func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, error) { + ur.log.Info("根据用户唯一编号查询指定用户的详细信息。", zap.String("user id", uid)) + if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户详细信息。") + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.UserDetail) + sql, params, _ := goqu.From("user_detail").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_detail", uid) + return user, nil +} + +// 使用用户唯一编号获取用户的综合详细信息 +func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail, error) { + ur.log.Info("根据用户唯一编号查询用户的综合详细信息", zap.String("user id", uid)) + if cachedUser, _ := cache.RetreiveEntity[model.UserWithDetail]("user_information", uid); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户综合详细信息。") + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.UserWithDetail) + sql, params, _ := goqu. + From("user").As("u"). + Join( + goqu.T("user_detail").As("ud"), + goqu.On(goqu.Ex{ + "ud.id": goqu.I("u.id"), + })). + Select( + "u.id", "u.username", "u.reset_needed", "u.type", "u.enabled", + "ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone", + "ud.unit_service_fee", "ud.service_expiration", + "ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by"). + Where(goqu.C("u.id").Eq(uid)). + Prepared(true).ToSQL() + if err := pgxscan.Get( + ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_information", uid) + return user, nil +} + +// 检查指定用户唯一编号是否存在对应的用户 +func (ur _UserRepository) IsUserExists(uid string) (bool, error) { + ur.log.Info("检查指定用户唯一编号是否存在对应的用户。", zap.String("user id", uid)) + if exists, _ := cache.CheckExists("user", uid); exists { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") + return exists, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var userCount int + sql, params, _ := goqu.From("user").Select(goqu.COUNT("*")).Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } + if userCount > 0 { + cache.CacheExists([]string{"user", fmt.Sprintf("user:%s", uid)}, "user", uid) + } + return userCount > 0, nil +} From 0d5457fca3758e8224ea12b695a823a587cd31d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 31 May 2023 16:26:16 +0800 Subject: [PATCH 007/141] =?UTF-8?q?enhance(migration):=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- migration/20220915101901_initialize.tx.up.sql | 240 ------------------ .../20220915101901_uninitialize.tx.down.sql | 21 -- .../20220915131901_add_constraints.up.sql | 31 --- ...20220915131901_remove_constraints.down.sql | 31 --- ...0220920131201_jsonb_to_summary.tx.down.sql | 23 -- .../20220920131201_summary_to_jsonb.tx.up.sql | 46 ---- migration/20220921143001_maintenance.down.sql | 15 -- migration/20220921143001_maintenance.up.sql | 37 --- migration/main.go | 21 -- 9 files changed, 465 deletions(-) delete mode 100644 migration/20220915101901_initialize.tx.up.sql delete mode 100644 migration/20220915101901_uninitialize.tx.down.sql delete mode 100644 migration/20220915131901_add_constraints.up.sql delete mode 100644 migration/20220915131901_remove_constraints.down.sql delete mode 100644 migration/20220920131201_jsonb_to_summary.tx.down.sql delete mode 100644 migration/20220920131201_summary_to_jsonb.tx.up.sql delete mode 100644 migration/20220921143001_maintenance.down.sql delete mode 100644 migration/20220921143001_maintenance.up.sql delete mode 100644 migration/main.go diff --git a/migration/20220915101901_initialize.tx.up.sql b/migration/20220915101901_initialize.tx.up.sql deleted file mode 100644 index a8fb2ab..0000000 --- a/migration/20220915101901_initialize.tx.up.sql +++ /dev/null @@ -1,240 +0,0 @@ -create table if not exists region ( - code varchar(15) not null primary key, - name varchar(50) not null, - level smallint not null default 0, - parent varchar(15) not null default '0' -); - -create table if not exists `user` ( - created_at timestamptz not null, - id varchar(120) not null primary key, - username varchar(30) not null, - password varchar(256) not null, - reset_needed boolean not null default false, - type smallint not null, - enabled boolean not null default true -); - -create table if not exists user_detail ( - created_at timestamptz not null, - created_by varchar(100), - last_modified_at timestamptz, - last_modified_by varchar(100), - deleted_at timestamptz, - deleted_by varchar(100), - id varchar(120) not null primary key, - name varchar(100), - abbr varchar(50), - region varchar(10), - address varchar(120), - contact varchar(100), - phone varchar(50), - unit_service_fee numeric(8,2) not null default 0, - service_expiration date not null -); - -create table if not exists user_charge ( - created_at timestamptz not null, - seq bigserial not null primary key, - user_id varchar(120) not null, - fee numeric(12,2), - discount numeric(5,4), - amount numeric(12,2), - charge_to date not null, - settled boolean not null default false, - settled_at timestamptz, - cancelled boolean not null default false, - cancelled_at timestamptz, - refunded boolean not null default false, - refunded_at timestamptz -); - -create table if not exists park ( - created_at timestamptz not null, - created_by varchar(100), - last_modified_at timestamptz, - last_modified_by varchar(100), - deleted_at timestamptz, - deleted_by varchar(100), - id varchar(120) not null primary key, - user_id varchar(120) not null, - name varchar(70) not null, - abbr varchar(50), - area numeric(14,2), - tenement_quantity numeric(8,0), - capacity numeric(16,2), - category smallint not null default 0, - meter_04kv_type smallint not null default 0, - region varchar(10), - address varchar(120), - contact varchar(100), - phone varchar(50), - enabled boolean not null default true -); - -create table if not exists meter_04kv ( - created_at timestamptz not null, - created_by varchar(100), - last_modified_at timestamptz, - last_modified_by varchar(100), - code varchar(120) not null, - park_id varchar(120) not null, - address varchar(100), - customer_name varchar(100), - contact_name varchar(70), - contact_phone varchar(50), - ratio numeric(8,4) not null default 1, - seq bigint not null default 0, - public_meter boolean not null default false, - dilute boolean not null default false, - enabled boolean not null default true, - primary key (code, park_id) -); - -create table if not exists maintenance_fee ( - created_at timestamptz not null, - created_by varchar(100), - last_modified_at timestamptz, - last_modified_by varchar(100), - deleted_at timestamptz, - deleted_by varchar(100), - id varchar(120) not null primary key, - park_id varchar(120) not null, - name varchar(50) not null, - fee numeric(8,2) not null default 0, - memo text, - enabled boolean not null default true -); - -create table if not exists report ( - created_at timestamptz not null, - created_by varchar(100), - last_modified_at timestamptz, - last_modified_by varchar(100), - id varchar(120) not null primary key, - park_id varchar(120) not null, - period date not null, - category smallint not null default 0, - meter_04kv_type smallint not null default 0, - step_state jsonb not null, - published boolean not null default false, - published_at timestamptz, - withdraw smallint not null default 0, - last_withdraw_applied_at timestamptz, - last_withdraw_audit_at timestamptz -); - -create table if not exists report_summary ( - report_id varchar(120) not null primary key, - overall numeric(14,2) not null default 0, - overall_fee numeric(14,2) not null default 0, - consumption_fee numeric(14,2), - overall_price numeric(16,8), - critical numeric(14,2) not null default 0, - critical_fee numeric(14,2) not null default 0, - critical_price numeric(16,8), - peak numeric(14,2) not null default 0, - peak_fee numeric(14,2) not null default 0, - peak_price numeric(16,8), - flat numeric(14,2) not null default 0, - flat_fee numeric(14,2) not null default 0, - flat_price numeric(16,8), - valley numeric(14,2) not null default 0, - valley_fee numeric(14,2) not null default 0, - valley_price numeric(16,8), - loss numeric(14,2), - loss_fee numeric(16,2), - loss_proportion numeric(16,15), - customer_consumption numeric(16,2), - customer_consumption_fee numeric(14,2), - customer_consumption_critical numeric(16,2), - customer_consumption_critical_fee numeric(14,2), - customer_consumption_peak numeric(16,2), - customer_consumption_peak_fee numeric(14,2), - customer_consumption_flat numeric(16,2), - customer_consumption_flat_fee numeric(14,2), - customer_consumption_valley numeric(16,2), - customer_consumption_valley_fee numeric(14,2), - public_consumption numeric(16,2), - public_consumption_fee numeric(14,2), - public_consumption_proportion numeric(16,15), - public_consumption_critical numeric(16,2), - public_consumption_critical_fee numeric(14,2), - public_consumption_peak numeric(16,2), - public_consumption_peak_fee numeric(14,2), - public_consumption_flat numeric(16,2), - public_consumption_flat_fee numeric(14,2), - public_consumption_valley numeric(16,2), - public_consumption_valley_fee numeric(14,2), - basic_fee numeric(14,2) not null default 0, - basic_diluted_price numeric(18,8), - adjust_fee numeric(14,2) not null default 0, - adjust_diluted_price numeric(18,8), - maintenance_diluted_price numeric(16,8), - loss_diluted_price numeric(16,8), - public_consumption_diluted_price numeric(16,8), - maintenance_overall numeric(16,8), - final_diluted_overall numeric(14,2) -); - -create table if not exists will_diluted_fee ( - id varchar(120) not null primary key, - report_id varchar(120) not null, - source_id varchar(120), - name varchar(50) not null, - fee numeric(8,2) not null default 0, - memo text -); - -create table if not exists end_user_detail ( - created_at timestamptz not null, - created_by varchar(100), - last_modified_at timestamptz, - last_modified_by varchar(100), - report_id varchar(120) not null, - park_id varchar(120) not null, - meter_04kv_id varchar(120) not null, - seq bigint not null default 0, - ratio numeric(8,4) not null default 1, - address varchar(100), - customer_name varchar(100), - contact_name varchar(70), - contact_phone varcahar(50), - public_meter boolean not null default false, - dilute boolean not null default false, - last_period_overall numeric(14,2) not null default 0, - last_period_critical numeric(14,2) not null default 0, - last_period_peak numeric(14,2) not null default 0, - last_period_flat numeric(14,2) not null default 0, - last_period_valley numeric(14,2) not null default 0, - current_period_overall numeric(14,2) not null default 0, - current_period_critical numeric(14,2) not null default 0, - current_period_peak numeric(14,2) not null default 0, - current_period_flat numeric(14,2) not null default 0, - current_period_valley numeric(14,2) not null default 0, - adjust_overall numeric(14,2) not null default 0, - adjust_critical numeric(14,2) not null default 0, - adjust_peak numeric(14,2) not null default 0, - adjust_flat numeric(14,2) not null default 0, - adjust_valley numeric(14,2) not null default 0, - overall numeric(14,2), - overall_fee numeric(14,2), - overall_proportion numeric(16,15) not null default 0, - critical numeric(14,2), - critical_fee numeric(18,8), - peak numeric(14,2), - peak_fee numeric(18,8), - flat numeric(14,2), - flat_fee numeric(18,8), - valley numeric(14,2), - valley_fee numeric(18,8), - basic_fee_diluted numeric(18,8), - adjust_fee_diluted numeric(18,8), - loss_diluted numeric(18,8), - loss_fee_diluted numeric(18,8), - maintenance_fee_diluted numeric(18,8), - public_consumption_diluted numeric(18,8), - final_diluted numeric(14,2), - final_charge numeric(14,2), - primary key (report_id, park_id, meter_04kv_id) -); \ No newline at end of file diff --git a/migration/20220915101901_uninitialize.tx.down.sql b/migration/20220915101901_uninitialize.tx.down.sql deleted file mode 100644 index f117cdb..0000000 --- a/migration/20220915101901_uninitialize.tx.down.sql +++ /dev/null @@ -1,21 +0,0 @@ -drop table if exists region; - -drop table if exists user; - -drop table if exists user_detail; - -drop table if exists user_charge; - -drop table if exists park; - -drop table if exists meter_04kv; - -drop table if exists maintenance_fee; - -drop table if exists report; - -drop table if exists report_summary; - -drop table if exists will_diluted_fee; - -drop table if exists end_user_detail; \ No newline at end of file diff --git a/migration/20220915131901_add_constraints.up.sql b/migration/20220915131901_add_constraints.up.sql deleted file mode 100644 index 2289513..0000000 --- a/migration/20220915131901_add_constraints.up.sql +++ /dev/null @@ -1,31 +0,0 @@ -alter table if exists `user` add constraint user_type_check check (type in (0, 1, 2)); - -alter table if exists user_detail add constraint positive_service_fee check (unit_service_fee >= 0); - -alter table if exists user_charge add constraint positive_fee check (fee >= 0); - -alter table if exists user_charge add constraint positive_amount check (amount >= 0); - -alter table if exists park add constraint positive_tenement check (tenement_quantity >= 0); - -alter table if exists park add constraint positive_area check (area >= 0); - -alter table if exists park add constraint positive_capacity check (capacity >= 0); - -alter table if exists park add constraint category_check check (category in (0, 1, 2)); - -alter table if exists park add constraint meter_check check (meter_04kv_type in (0, 1)); - -alter table if exists meter_04kv add constraint positive_ratio check (ratio > 0); - -alter table if exists maintenance_fee add constraint positive_fee check (fee >= 0); - -alter table if exists report add constraint category_check check (category in (0, 1, 2)); - -alter table if exists report add constraint meter_check check (meter_04kv_type in (0, 1)); - -alter table if exists report add constraint withdraw_action_check check (withdraw in (0, 1, 2, 3)); - -alter table if exists will_diluted_fee add constraint positive_fee check (fee >= 0); - -alter table if exists end_user_detail add constraint positive_ratio check (ratio > 0); \ No newline at end of file diff --git a/migration/20220915131901_remove_constraints.down.sql b/migration/20220915131901_remove_constraints.down.sql deleted file mode 100644 index bf653a4..0000000 --- a/migration/20220915131901_remove_constraints.down.sql +++ /dev/null @@ -1,31 +0,0 @@ -alter table if exists user drop constraint user_type_check; - -alter table if exists user_detail drop constraint positive_service_fee; - -alter table if exists user_charge drop constraint positive_fee; - -alter table if exists user_charge drop constraint positive_amount; - -alter table if exists park drop constraint positive_tenement; - -alter table if exists park drop constraint positive_area; - -alter table if exists park drop constraint positive_capacity; - -alter table if exists park drop constraint category_check; - -alter table if exists park drop constraint meter_check; - -alter table if exists meter_04kv drop constraint positive_ratio; - -alter table if exists maintenance_fee drop constraint positive_fee; - -alter table if exists report drop constraint category_check; - -alter table if exists report drop constraint meter_check; - -alter table if exists report drop constraint withdraw_action_check; - -alter table if exists will_diluted_fee drop constraint positive_fee; - -alter table if exists end_user_detail drop constraint positive_ratio; \ No newline at end of file diff --git a/migration/20220920131201_jsonb_to_summary.tx.down.sql b/migration/20220920131201_jsonb_to_summary.tx.down.sql deleted file mode 100644 index ce25791..0000000 --- a/migration/20220920131201_jsonb_to_summary.tx.down.sql +++ /dev/null @@ -1,23 +0,0 @@ -update report_summary -set - customer_consumption = nullif(trim(customers->>'consumption'), '')::numeric(16,2), - customer_consumption_fee = nullif(trim(customers->>'consumptionFee'), '')::numeric(14,2), - customer_consumption_critical = nullif(trim(customers->>'critical'), '')::numeric(16,2), - customer_consumption_critical_fee = nullif(trim(customers->>'criticalFee'), '')::numeric(14,2), - customer_consumption_peak = nullif(trim(customers->>'peak'), '')::numeric(16,2), - customer_consumption_peak_fee = nullif(trim(customers->>'peakFee'), '')::numeric(14,2), - customer_consumption_flat = nullif(trim(customers->>'flat'), '')::numeric(16,2), - customer_consumption_flat_fee = nullif(trim(customers->>'flatFee'), '')::numeric(14,2), - customer_consumption_valley = nullif(trim(customers->>'valley'), '')::numeric(16,2), - customer_consumption_valley_fee = nullif(trim(customers->>'valleyFee'), '')::numeric(14,2), - public_consumption = nullif(trim(publics->>'consumption'), '')::numeric(16,2), - public_consumption_fee = nullif(trim(publics->>'consumptionFee'), '')::numeric(14,2), - public_consumption_critical = nullif(trim(publics->>'critical'), '')::numeric(16,2), - public_consumption_critical_fee = nullif(trim(publics->>'criticalFee'), '')::numeric(14,2), - public_consumption_peak = nullif(trim(publics->>'peak'), '')::numeric(16,2), - public_consumption_peak_fee = nullif(trim(publics->>'peakFee'), '')::numeric(14,2), - public_consumption_flat = nullif(trim(publics->>'flat'), '')::numeric(16,2), - public_consumption_flat_fee = nullif(trim(publics->>'flatFee'), '')::numeric(14,2), - public_consumption_valley = nullif(trim(publics->>'valley'), '')::numeric(16,2), - public_consumption_valley_fee = nullif(trim(publics->>'valleyFee'), '')::numeric(14,2), - public_consumption_proportion = nullif(trim(publics->>'proportion'), '')::numeric(16,15); \ No newline at end of file diff --git a/migration/20220920131201_summary_to_jsonb.tx.up.sql b/migration/20220920131201_summary_to_jsonb.tx.up.sql deleted file mode 100644 index 4497bf0..0000000 --- a/migration/20220920131201_summary_to_jsonb.tx.up.sql +++ /dev/null @@ -1,46 +0,0 @@ -alter table report_summary - add column if not exists customers jsonb, - add column if not exists publics jsonb, - add column if not exists diluteds jsonb; - -update report_summary -set - customers = jsonb_build_object( - 'consumption', customer_consumption, - 'fee', customer_consumption_fee, - 'critical', customer_consumption_critical, - 'criticalFee', customer_consumption_critical_fee, - 'peak', customer_consumption_peak, - 'peakFee', customer_consumption_peak_fee, - 'flat', customer_consumption_flat, - 'flatFee', customer_consumption_flat_fee, - 'valley', customer_consumption_valley, - 'valleyFee', customer_consumption_valley_fee, - 'proportion', null - ), - diluteds = jsonb_build_object( - 'consumption', public_consumption, - 'fee', public_consumption_fee, - 'critical', public_consumption_critical, - 'criticalFee', public_consumption_critical_fee, - 'peak', public_consumption_peak, - 'peakFee', public_consumption_peak_fee, - 'flat', public_consumption_flat, - 'flatFee', public_consumption_flat_fee, - 'valley', public_consumption_valley, - 'valleyFee', public_consumption_valley_fee, - 'proportion', public_consumption_proportion - ), - publics = jsonb_build_object( - 'consumption', null, - 'fee', null, - 'critical', null, - 'criticalFee', null, - 'peak', null, - 'peakFee', null, - 'flat', null, - 'flatFee', null, - 'valley', null, - 'valleyFee', null, - 'proportion', null - ); \ No newline at end of file diff --git a/migration/20220921143001_maintenance.down.sql b/migration/20220921143001_maintenance.down.sql deleted file mode 100644 index 8c30b46..0000000 --- a/migration/20220921143001_maintenance.down.sql +++ /dev/null @@ -1,15 +0,0 @@ -alter table meter_04kv - add column dilute boolean not null default false; - -alter table end_user_detail - add column dilute boolean not null default false, - add column maintenance_fee_diluted numeric(18,8), - add column public_consumption_diluted numeric(18,8); - -alter table maintenance_fee - drop column period; - -alter table report_summary - drop column authorize_loss, - drop column authorize_loss_fee, - drop column authorize_loss_proportion; \ No newline at end of file diff --git a/migration/20220921143001_maintenance.up.sql b/migration/20220921143001_maintenance.up.sql deleted file mode 100644 index 1eff678..0000000 --- a/migration/20220921143001_maintenance.up.sql +++ /dev/null @@ -1,37 +0,0 @@ -alter table meter_04kv - drop column dilute; - -alter table end_user_detail - drop column dilute, - drop column maintenance_fee_diluted, - drop column public_consumption_diluted; - -alter table maintenance_fee - add column period varchar(10); - -alter table report_summary - drop column customer_consumption, - drop column customer_consumption_fee, - drop column customer_consumption_critical, - drop column customer_consumption_critical_fee, - drop column customer_consumption_peak, - drop column customer_consumption_peak_fee, - drop column customer_consumption_flat, - drop column customer_consumption_flat_fee, - drop column customer_consumption_valley, - drop column customer_consumption_valley_fee, - drop column public_consumption, - drop column public_consumption_fee, - drop column public_consumption_critical, - drop column public_consumption_critical_fee, - drop column public_consumption_peak, - drop column public_consumption_peak_fee, - drop column public_consumption_flat, - drop column public_consumption_flat_fee, - drop column public_consumption_valley, - drop column public_consumption_valley_fee, - drop column public_consumption_proportion, - drop column diluteds, - add column authorize_loss numeric(14,2), - add column authorize_loss_fee numeric(16,2), - add column authorize_loss_proportion numeric(16,15); \ No newline at end of file diff --git a/migration/main.go b/migration/main.go deleted file mode 100644 index 09979af..0000000 --- a/migration/main.go +++ /dev/null @@ -1,21 +0,0 @@ -package migration - -import ( - "electricity_bill_calc/logger" - "embed" - - "github.com/uptrace/bun/migrate" - "go.uber.org/zap" -) - -var ( - //go:embed *.sql - sqlMigrations embed.FS - Migrations = migrate.NewMigrations() -) - -func init() { - if err := Migrations.Discover(sqlMigrations); err != nil { - logger.Named("Migrations").Fatal("Unable to load migrations.", zap.Error(err)) - } -} From 47cd27c9689c4af5b1f7c5578b161d465a66bc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 31 May 2023 22:08:08 +0800 Subject: [PATCH 008/141] =?UTF-8?q?fix(db):=E5=AE=8C=E6=88=90=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5=E9=85=8D=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global/db.go | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/global/db.go b/global/db.go index 6ed62cd..1acdc74 100644 --- a/global/db.go +++ b/global/db.go @@ -3,13 +3,11 @@ package global import ( "context" "fmt" - "time" "electricity_bill_calc/config" "electricity_bill_calc/logger" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" "github.com/samber/lo" "go.uber.org/zap" @@ -20,29 +18,27 @@ var ( ) func SetupDatabaseConnection() error { - connConfig := &pgx.ConnConfig{ - Config: pgconn.Config{ - Host: config.DatabaseSettings.Host, - Port: uint16(config.DatabaseSettings.Port), - User: config.DatabaseSettings.User, - Password: config.DatabaseSettings.Pass, - Database: config.DatabaseSettings.DB, - TLSConfig: nil, - ConnectTimeout: 0 * time.Second, - RuntimeParams: map[string]string{"application_name": "elec_service_go"}, - }, - Tracer: QueryLogger{ - logger: logger.Named("PG"), - }, - } - poolConfig := &pgxpool.Config{ - ConnConfig: connConfig, - MaxConnLifetime: 60 * time.Minute, - MaxConnIdleTime: 10 * time.Minute, - HealthCheckPeriod: 10 * time.Second, - MaxConns: int32(config.DatabaseSettings.MaxOpenConns), - MinConns: int32(config.DatabaseSettings.MaxIdleConns), + connString := fmt.Sprintf( + "postgres://%s:%s@%s:%d/%s?sslmode=disable&connect_timeout=%d&application_name=%s&pool_max_conns=%d&pool_min_conns=%d&pool_max_conn_lifetime=%s&pool_max_conn_idle_time=%s&pool_health_check_period=%s", + config.DatabaseSettings.User, + config.DatabaseSettings.Pass, + config.DatabaseSettings.Host, + config.DatabaseSettings.Port, + config.DatabaseSettings.DB, + 0, + "elec_service_go", + config.DatabaseSettings.MaxOpenConns, + config.DatabaseSettings.MaxIdleConns, + "60m", + "10m", + "10s", + ) + poolConfig, err := pgxpool.ParseConfig(connString) + if err != nil { + logger.Named("DB INIT").Error("数据库连接初始化失败。", zap.Error(err)) + return err } + poolConfig.ConnConfig.Tracer = QueryLogger{logger: logger.Named("PG")} DB, _ = pgxpool.NewWithConfig(context.Background(), poolConfig) return nil From 523e6215f44130e9f6a84adcf6c88539511041e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 05:37:41 +0800 Subject: [PATCH 009/141] =?UTF-8?q?enhance(user):=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=8F=AF=E8=BF=90=E8=A1=8C=E7=9A=84=E7=A8=8B=E5=BA=8F=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E7=BB=93=E6=9E=84=E4=BB=A5=E5=8F=8A=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 58 ++++++++---------- model/user.go | 32 ++++++++++ repository/user.go | 73 ++++++++++++++++++++-- service/user.go | 148 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 272 insertions(+), 39 deletions(-) create mode 100644 service/user.go diff --git a/main.go b/main.go index 37628ab..3363cc3 100644 --- a/main.go +++ b/main.go @@ -6,12 +6,12 @@ import ( "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" + "electricity_bill_calc/repository" "electricity_bill_calc/router" "electricity_bill_calc/service" "fmt" "time" - "github.com/shopspring/decimal" "go.uber.org/zap" ) @@ -43,52 +43,46 @@ func init() { } func intializeSingularity() error { - singularityExists, err := service.UserService.IsUserExists("000") + l := logger.Named("Init", "Singularity") + singularityExists, err := repository.UserRepository.IsUserExists("000") if err != nil { - return fmt.Errorf("singularity detect failed: %w", err) + l.Error("检测奇点账号失败。", zap.Error(err)) + return fmt.Errorf("检测奇点账号失败: %w", err) } if singularityExists { + l.Info("奇点账号已经存在,跳过剩余初始化步骤。") return nil } - singularity := &model.User{ - Id: "000", - Username: "singularity", - Type: 2, - Enabled: true, - } - singularityName := "Singularity" + singularityId := "000" singularityExpires, err := model.ParseDate("2099-12-31") if err != nil { - return fmt.Errorf("singularity expires time parse failed: %w", err) + l.Error("奇点用户账号过期时间解析失败。", zap.Error(err)) + return fmt.Errorf("奇点用户账号过期时间解析失败: %w", err) } - singularityDetail := &model.UserDetail{ - Name: &singularityName, - UnitServiceFee: decimal.Zero, - ServiceExpiration: singularityExpires, + singularity := &model.ManagementAccountCreationForm{ + Id: &singularityId, + Username: "singularity", + Name: "Singularity", + Type: 2, + Enabled: true, + Expires: singularityExpires, } - verifyCode, err := service.UserService.CreateUser(singularity, singularityDetail) + verifyCode, err := service.UserService.CreateUserAccount( + singularity.IntoUser(), + singularity.IntoUserDetail()) if err != nil { - return fmt.Errorf("singularity account failed to create: %w", err) + l.Error("创建奇点账号失败。", zap.Error(err)) + return fmt.Errorf("创建奇点账号失败: %w", err) } logger.Info( - fmt.Sprintf("Singularity account created, use %s as verify code to reset password.", verifyCode), - zap.String("account", "singularity"), - zap.String("verifyCode", verifyCode), + fmt.Sprintf("奇点账号已经完成创建, 首次登录需要使用验证码 [%s] 重置密码。", *verifyCode), + zap.String("账号名称", "singularity"), + zap.String("验证码", *verifyCode), ) return nil } -func DBConnectionKeepLive() { - for range time.Tick(30 * time.Second) { - ctx, cancel := global.TimeoutContext() - defer cancel() - err := global.DB.Ping(ctx) - if err != nil { - continue - } - } -} - +// 清理Redis缓存中的孤儿键。 func RedisOrphanCleanup() { cleanLogger := logger.Named("Cache").With(zap.String("function", "Cleanup")) for range time.Tick(2 * time.Minute) { @@ -102,8 +96,6 @@ func RedisOrphanCleanup() { } func main() { - // 本次停用检测的原因是:使用Ping来保持数据库链接看起来没有什么用处。 - // go DBConnectionKeepLive() go RedisOrphanCleanup() app := router.App() app.Listen(fmt.Sprintf(":%d", config.ServerSettings.HttpPort)) diff --git a/model/user.go b/model/user.go index 4895a82..8ba6787 100644 --- a/model/user.go +++ b/model/user.go @@ -23,6 +23,38 @@ type ManagementAccountCreationForm struct { Expires Date } +func (m ManagementAccountCreationForm) IntoUser() *User { + return &User{ + Id: *m.Id, + Username: m.Username, + Password: "", + ResetNeeded: false, + UserType: m.Type, + Enabled: m.Enabled, + CreatedAt: nil, + } +} + +func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail { + return &UserDetail{ + Id: *m.Id, + Name: &m.Name, + Abbr: nil, + Region: nil, + Address: nil, + Contact: m.Contact, + Phone: m.Phone, + UnitServiceFee: decimal.Zero, + ServiceExpiration: m.Expires, + CreatedAt: time.Now(), + CreatedBy: nil, + LastModifiedAt: time.Now(), + LastModifiedBy: nil, + DeletedAt: nil, + DeletedBy: nil, + } +} + type User struct { Id string Username string diff --git a/repository/user.go b/repository/user.go index c304e93..58c1598 100644 --- a/repository/user.go +++ b/repository/user.go @@ -5,6 +5,8 @@ import ( "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" + "electricity_bill_calc/tools" + "electricity_bill_calc/tools/time" "fmt" "github.com/doug-martin/goqu/v9" @@ -15,10 +17,12 @@ import ( type _UserRepository struct { log *zap.Logger + ds goqu.DialectWrapper } var UserRepository = _UserRepository{ log: logger.Named("Repository", "User"), + ds: goqu.Dialect("postgres"), } // 使用用户名查询指定用户的基本信息 @@ -32,7 +36,7 @@ func (ur _UserRepository) FindUserByUsername(username string) (*model.User, erro defer cancel() var user = new(model.User) - sql, params, _ := goqu.From("user").Where(goqu.C("username").Eq(username)).Prepared(true).ToSQL() + sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"username": username}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) return nil, err @@ -52,7 +56,7 @@ func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { defer cancel() var user = new(model.User) - sql, params, _ := goqu.From("user").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) return nil, err @@ -72,7 +76,7 @@ func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, err defer cancel() var user = new(model.UserDetail) - sql, params, _ := goqu.From("user_detail").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + sql, params, _ := ur.ds.From("user_detail").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) return nil, err @@ -92,7 +96,7 @@ func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail defer cancel() var user = new(model.UserWithDetail) - sql, params, _ := goqu. + sql, params, _ := ur.ds. From("user").As("u"). Join( goqu.T("user_detail").As("ud"), @@ -104,7 +108,7 @@ func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail "ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone", "ud.unit_service_fee", "ud.service_expiration", "ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by"). - Where(goqu.C("u.id").Eq(uid)). + Where(goqu.Ex{"u.id": uid}). Prepared(true).ToSQL() if err := pgxscan.Get( ctx, global.DB, &user, sql, params...); err != nil { @@ -126,7 +130,7 @@ func (ur _UserRepository) IsUserExists(uid string) (bool, error) { defer cancel() var userCount int - sql, params, _ := goqu.From("user").Select(goqu.COUNT("*")).Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) return false, err @@ -136,3 +140,60 @@ func (ur _UserRepository) IsUserExists(uid string) (bool, error) { } return userCount > 0, nil } + +// 创建一个新用户 +func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, operator *string) (bool, error) { + ctx, cancel := global.TimeoutContext() + defer cancel() + tx, err := global.DB.Begin(ctx) + if err != nil { + ur.log.Error("启动数据库事务失败。", zap.Error(err)) + return false, err + } + + createdTime := time.Now() + userSql, userParams, _ := ur.ds. + Insert("user"). + Rows( + goqu.Record{ + "id": user.Id, "username": user.Username, "password": user.Password, + "reset_needed": user.ResetNeeded, "type": user.UserType, "enabled": user.Enabled, + "created_at": createdTime, + }, + ). + Prepared(true).ToSQL() + userResult, err := tx.Exec(ctx, userSql, userParams...) + if err != nil { + ur.log.Error("向数据库插入新用户基本信息失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + userDetailSql, userDetailParams, _ := ur.ds. + Insert("user_detail"). + Rows( + goqu.Record{ + "id": user.Id, "name": detail.Name, "abbr": tools.PinyinAbbr(*detail.Name), "region": detail.Region, + "address": detail.Address, "contact": detail.Contact, "phone": detail.Phone, + "unit_service_fee": detail.UnitServiceFee, "service_expiration": detail.ServiceExpiration, + "created_at": createdTime, "created_by": operator, + "last_modified_at": createdTime, "last_modified_by": operator, + }, + ). + Prepared(true).ToSQL() + detailResult, err := tx.Exec(ctx, userDetailSql, userDetailParams...) + if err != nil { + ur.log.Error("向数据库插入新用户详细信息失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + + err = tx.Commit(ctx) + if err != nil { + ur.log.Error("提交数据库事务失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } else { + cache.AbolishRelation("user") + } + return userResult.RowsAffected() > 0 && detailResult.RowsAffected() > 0, nil +} diff --git a/service/user.go b/service/user.go new file mode 100644 index 0000000..d528222 --- /dev/null +++ b/service/user.go @@ -0,0 +1,148 @@ +package service + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/config" + "electricity_bill_calc/exceptions" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "electricity_bill_calc/tools" + "electricity_bill_calc/tools/serial" + "electricity_bill_calc/tools/time" + + "github.com/fufuok/utils" + "github.com/google/uuid" + "github.com/samber/lo" + "go.uber.org/zap" +) + +type _UserService struct { + log *zap.Logger +} + +var UserService = _UserService{ + log: logger.Named("Service", "User"), +} + +func matchUserPassword(controlCode, testCode string) bool { + hashedCode := utils.Sha512Hex(testCode) + return controlCode == hashedCode +} + +// 处理用户登录的通用过程。 +func (us _UserService) processUserLogin(username, password string, userType []int16) (*model.User, *model.UserDetail, error) { + us.log.Info("处理用户登录。", zap.String("username", username)) + user, err := repository.UserRepository.FindUserByUsername(username) + if err != nil { + us.log.Error("处理用户登录失败。", zap.String("username", username), zap.Error(err)) + return nil, nil, err + } + if user == nil { + us.log.Warn("处理用户登录失败,用户不存在。", zap.String("username", username)) + return nil, nil, exceptions.NewAuthenticationError(404, "用户不存在。") + } + if !lo.Contains(userType, user.UserType) { + us.log.Warn("处理用户登录失败,用户类型错误。", zap.String("username", username), zap.Int16s("user type", userType)) + return nil, nil, exceptions.NewAuthenticationError(400, "用户类型不正确。") + } + if !user.Enabled { + us.log.Warn("处理用户登录失败,用户已被禁用。", zap.String("username", username)) + return nil, nil, exceptions.NewAuthenticationError(403, "用户已被禁用。") + } + if user.ResetNeeded { + us.log.Warn("处理用户登录失败,用户需要重置密码。", zap.String("username", username)) + authErr := exceptions.NewAuthenticationError(401, "用户凭据已失效。") + authErr.NeedReset = true + return nil, nil, authErr + } + if !matchUserPassword(user.Password, password) { + us.log.Warn("处理用户登录失败,密码错误。", zap.String("username", username)) + return nil, nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。") + } + userDetail, err := repository.UserRepository.FindUserDetailById(user.Id) + if err != nil { + us.log.Error("处理企业用户登录失败,查询用户详细信息失败。", zap.String("username", username), zap.Error(err)) + return nil, nil, err + } + if userDetail.ServiceExpiration.Before(time.Now()) { + us.log.Warn("处理企业用户登录失败,用户服务已过期。", zap.String("username", username)) + return nil, nil, exceptions.NewAuthenticationError(406, "用户服务期限已过。") + } + return user, userDetail, nil +} + +// 处理企业用户登录 +func (us _UserService) ProcessEnterpriseUserLogin(username, password string) (*model.Session, error) { + user, userDetail, err := us.processUserLogin(username, password, []int16{model.USER_TYPE_ENT}) + if err != nil { + us.log.Error("处理企业用户登录失败。", zap.String("username", username), zap.Error(err)) + return nil, err + } + token, _ := uuid.NewRandom() + userSession := &model.Session{ + Uid: user.Id, + Name: user.Username, + Type: user.UserType, + Token: token.String(), + ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), + } + if userDetail != nil && userDetail.Name != nil { + userSession.Name = *userDetail.Name + } + err = cache.CacheSession(userSession) + if err != nil { + us.log.Error("处理企业用户登录失败,缓存用户会话失败。", zap.String("username", username), zap.Error(err)) + return nil, err + } + return userSession, nil +} + +// 处理运维、监管用户登录 +func (us _UserService) ProcessManagementUserLogin(username, password string) (*model.Session, error) { + user, userDetail, err := us.processUserLogin(username, password, []int16{model.USER_TYPE_OPS, model.USER_TYPE_SUP}) + if err != nil { + us.log.Error("处理运维、监管用户登录失败。", zap.String("username", username), zap.Error(err)) + return nil, err + } + token, _ := uuid.NewRandom() + userSession := &model.Session{ + Uid: user.Id, + Name: user.Username, + Type: user.UserType, + Token: token.String(), + ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), + } + if userDetail != nil { + userSession.Name = *userDetail.Name + } + err = cache.CacheSession(userSession) + if err != nil { + us.log.Error("处理运维、监管用户登录失败,缓存用户会话失败。", zap.String("username", username), zap.Error(err)) + return nil, err + } + return userSession, nil +} + +// 创建用户账号的通用方法。 +func (us _UserService) CreateUserAccount(user *model.User, detail *model.UserDetail) (*string, error) { + if lo.IsEmpty(user.Id) { + var prefix string + if user.UserType == model.USER_TYPE_ENT { + prefix = "E" + } else { + prefix = "S" + } + user.Id = serial.GeneratePrefixedUniqueSerialString(prefix) + detail.Id = user.Id + } + verifyCode := tools.RandStr(10) + user.Password = utils.Sha512Hex(verifyCode) + user.ResetNeeded = true + res, err := repository.UserRepository.CreateUser(*user, *detail, nil) + if err != nil || !res { + us.log.Error("创建用户账号失败。", zap.String("username", user.Username), zap.Error(err)) + return nil, err + } + return &verifyCode, nil +} From 61fef8d0fa17f808da8b11542b9d2139b577aa57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 06:01:29 +0800 Subject: [PATCH 010/141] =?UTF-8?q?enhance(user):=E6=89=93=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=A8=8B=E5=BA=8F=E5=9F=BA=E6=9C=AC=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/user.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ repository/user.go | 16 +++++++-------- router/router.go | 3 +++ 3 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 controller/user.go diff --git a/controller/user.go b/controller/user.go new file mode 100644 index 0000000..c5c2ec3 --- /dev/null +++ b/controller/user.go @@ -0,0 +1,49 @@ +package controller + +import ( + "electricity_bill_calc/exceptions" + "electricity_bill_calc/model" + "electricity_bill_calc/response" + "electricity_bill_calc/service" + "net/http" + + "github.com/gofiber/fiber/v2" +) + +func InitializeUserHandlers(router *fiber.App) { + router.Post("/login", doLogin) +} + +type _LoginForm struct { + Username string `json:"uname"` + Password string `json:"upass"` + Type int16 `json:"type"` +} + +func doLogin(c *fiber.Ctx) error { + result := response.NewResult(c) + loginData := new(_LoginForm) + if err := c.BodyParser(loginData); err != nil { + return result.Error(http.StatusInternalServerError, "表单解析失败。") + } + var ( + session *model.Session + err error + ) + if loginData.Type == model.USER_TYPE_ENT { + session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) + } else { + session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password) + } + if err != nil { + if authError, ok := err.(*exceptions.AuthenticationError); ok { + if authError.NeedReset { + return result.LoginNeedReset() + } + return result.Error(int(authError.Code), authError.Message) + } else { + return result.Error(http.StatusInternalServerError, err.Error()) + } + } + return result.LoginSuccess(session) +} diff --git a/repository/user.go b/repository/user.go index 58c1598..fa42f04 100644 --- a/repository/user.go +++ b/repository/user.go @@ -35,14 +35,14 @@ func (ur _UserRepository) FindUserByUsername(username string) (*model.User, erro ctx, cancel := global.TimeoutContext() defer cancel() - var user = new(model.User) + var user model.User sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"username": username}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) return nil, err } cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", username)}, "user", username) - return user, nil + return &user, nil } // 使用用户唯一编号查询指定用户的基本信息 @@ -55,14 +55,14 @@ func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { ctx, cancel := global.TimeoutContext() defer cancel() - var user = new(model.User) + var user model.User sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) return nil, err } cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user", uid) - return user, nil + return &user, nil } // 使用用户的唯一编号获取用户的详细信息 @@ -75,14 +75,14 @@ func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, err ctx, cancel := global.TimeoutContext() defer cancel() - var user = new(model.UserDetail) + var user model.UserDetail sql, params, _ := ur.ds.From("user_detail").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) return nil, err } cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_detail", uid) - return user, nil + return &user, nil } // 使用用户唯一编号获取用户的综合详细信息 @@ -95,7 +95,7 @@ func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail ctx, cancel := global.TimeoutContext() defer cancel() - var user = new(model.UserWithDetail) + var user model.UserWithDetail sql, params, _ := ur.ds. From("user").As("u"). Join( @@ -116,7 +116,7 @@ func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail return nil, err } cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_information", uid) - return user, nil + return &user, nil } // 检查指定用户唯一编号是否存在对应的用户 diff --git a/router/router.go b/router/router.go index db53a00..965eeb3 100644 --- a/router/router.go +++ b/router/router.go @@ -1,6 +1,7 @@ package router import ( + "electricity_bill_calc/controller" "electricity_bill_calc/logger" "electricity_bill_calc/security" "fmt" @@ -43,6 +44,8 @@ func App() *fiber.App { })) app.Use(security.SessionRecovery) + controller.InitializeUserHandlers(app) + return app } From 73737c37537fd0b791ef9343b44da1c8afcdd67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 06:13:43 +0800 Subject: [PATCH 011/141] =?UTF-8?q?feat(user):=E5=AE=8C=E6=88=90=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=99=BB=E5=87=BA=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/user.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/controller/user.go b/controller/user.go index c5c2ec3..981f6dc 100644 --- a/controller/user.go +++ b/controller/user.go @@ -1,9 +1,11 @@ package controller import ( + "electricity_bill_calc/cache" "electricity_bill_calc/exceptions" "electricity_bill_calc/model" "electricity_bill_calc/response" + "electricity_bill_calc/security" "electricity_bill_calc/service" "net/http" @@ -11,6 +13,7 @@ import ( ) func InitializeUserHandlers(router *fiber.App) { + router.Delete("/login", security.MustAuthenticated, doLogout) router.Post("/login", doLogin) } @@ -47,3 +50,16 @@ func doLogin(c *fiber.Ctx) error { } return result.LoginSuccess(session) } + +func doLogout(c *fiber.Ctx) error { + result := response.NewResult(c) + session := c.Locals("session") + if session == nil { + return result.Success("用户会话已结束。") + } + _, err := cache.ClearSession(session.(*model.Session).Token) + if err != nil { + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Success("用户已成功登出系统。") +} From 71f39c8c2fd767f25725df7861e2489aa6a8f168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 10:05:29 +0800 Subject: [PATCH 012/141] =?UTF-8?q?enhance(cache):=E6=9B=B4=E6=AD=A3?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=87=BD=E6=95=B0=E5=90=8D=E7=A7=B0=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=86=E9=A1=B5=E6=A3=80=E7=B4=A2=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/abstract.go | 2 +- cache/count.go | 4 ++-- cache/entity.go | 4 ++-- cache/search.go | 47 +++++++++++++++++++++++++++++++++++++++++++++-- cache/session.go | 4 ++-- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/cache/abstract.go b/cache/abstract.go index cedab1f..8d24c69 100644 --- a/cache/abstract.go +++ b/cache/abstract.go @@ -40,7 +40,7 @@ func Cache[T interface{}](key string, value *T, expires time.Duration) error { } // 从Redis缓存中获取一个数据 -func Retreive[T interface{}](key string) (*T, error) { +func Retrieve[T interface{}](key string) (*T, error) { getCmd := global.Rd.B().Get().Key(key).Build() result := global.Rd.Do(global.Ctx, getCmd) if result.Error() != nil { diff --git a/cache/count.go b/cache/count.go index b1baa54..5b8b65b 100644 --- a/cache/count.go +++ b/cache/count.go @@ -34,7 +34,7 @@ func CacheCount(relationNames []string, entityName string, count int64, conditio } // 从缓存中获取模型名称明确的,包含指定条件的实体记录数量 -func RetreiveCount(entityName string, condtions ...string) (int64, error) { +func RetrieveCount(entityName string, condtions ...string) (int64, error) { countKey := assembleCountKey(entityName, condtions...) exist, err := Exists(countKey) if err != nil { @@ -43,7 +43,7 @@ func RetreiveCount(entityName string, condtions ...string) (int64, error) { if !exist { return -1, nil } - instance, err := Retreive[_CountRecord](countKey) + instance, err := Retrieve[_CountRecord](countKey) if instance != nil && err == nil { return instance.Count, nil } else { diff --git a/cache/entity.go b/cache/entity.go index c9d4eb8..db1ff52 100644 --- a/cache/entity.go +++ b/cache/entity.go @@ -28,9 +28,9 @@ func CacheEntity[T any](instance T, relationNames []string, entityName, id strin } // 从缓存中取出模型名称明确的,使用ID进行检索的实体内容。 -func RetreiveEntity[T any](entityName, id string) (*T, error) { +func RetrieveEntity[T any](entityName, id string) (*T, error) { entityKey := assembleEntityKey(entityName, id) - instance, err := Retreive[T](entityKey) + instance, err := Retrieve[T](entityKey) return instance, err } diff --git a/cache/search.go b/cache/search.go index 7468bd4..d83bcd6 100644 --- a/cache/search.go +++ b/cache/search.go @@ -29,9 +29,9 @@ func CacheSearch[T any](instance T, relationNames []string, entityName string, c } // 从缓存中取得模型名称明确的,使用或者包含非ID检索条件的实体内容。 -func RetreiveSearch[T any](entityName string, conditions ...string) (*T, error) { +func RetrieveSearch[T any](entityName string, conditions ...string) (*T, error) { searchKey := assembleSearchKey(entityName, conditions...) - instance, err := Retreive[T](searchKey) + instance, err := Retrieve[T](searchKey) return instance, err } @@ -40,3 +40,46 @@ func AbolishSearch(entityName string) error { pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName)) return DeleteAll(pattern) } + +// 向缓存中保存指定模型名称的分页检索结果,会同时采用`CacheCount`中的方法保存检索结果的总数量。 +func CachePagedSearch[T any](instance T, total int64, relationNames []string, entityName string, conditions ...string) error { + searchKey := assembleSearchKey(entityName, conditions...) + countKey := assembleCountKey(entityName, conditions...) + err := Cache(searchKey, &instance, 5*time.Minute) + if err != nil { + return err + } + cacheInstance := &_CountRecord{Count: total} + err = Cache(countKey, cacheInstance, 5*time.Minute) + for _, relationName := range relationNames { + CacheRelation(relationName, STORE_TYPE_KEY, searchKey) + CacheRelation(relationName, STORE_TYPE_KEY, countKey) + } + return err +} + +// 从缓存中获取指定模型名称的分页检索结果,会同时采用`RetrieveCount`中的方法获取检索结果的总数量。 +func RetrievePagedSearch[T any](entityName string, conditions ...string) (*T, int64, error) { + searchKey := assembleSearchKey(entityName, conditions...) + countKey := assembleCountKey(entityName, conditions...) + instance, err := Retrieve[T](searchKey) + if err != nil { + return nil, -1, err + } + count, err := Retrieve[_CountRecord](countKey) + if err != nil { + return nil, -1, err + } + return instance, count.Count, nil +} + +// 从缓存中删除指定模型名称的分页检索结果,会同时采用`AbolishCountEntity`中的方法删除检索结果的总数量。 +func AbolishPagedSearch(entityName string) error { + pattern := fmt.Sprintf("%s:%s:*", TAG_SEARCH, strings.ToUpper(entityName)) + err := DeleteAll(pattern) + if err != nil { + return err + } + pattern = fmt.Sprintf("%s:%s:*", TAG_COUNT, strings.ToUpper(entityName)) + return DeleteAll(pattern) +} diff --git a/cache/session.go b/cache/session.go index 30000d7..2dab0d2 100644 --- a/cache/session.go +++ b/cache/session.go @@ -21,9 +21,9 @@ func CacheSession(session *model.Session) error { return Cache(key, session, config.ServiceSettings.MaxSessionLife) } -func RetreiveSession(token string) (*model.Session, error) { +func RetrieveSession(token string) (*model.Session, error) { key := SessionKey(token) - return Retreive[model.Session](key) + return Retrieve[model.Session](key) } func HasSession(token string) (bool, error) { From 9aa32d99b966b7a6c26bdcef3a065c0276b3829e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 12:04:03 +0800 Subject: [PATCH 013/141] =?UTF-8?q?feat(user):=E9=80=9A=E8=BF=87=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=94=A8=E6=88=B7=E6=A3=80=E7=B4=A2=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E7=BB=A7=E7=BB=AD=E7=A1=AE=E5=AE=9A=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=9A=84=E5=9F=BA=E6=9C=AC=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/search.go | 9 +++ config/settings.go | 2 +- controller/user.go | 44 ++++++++++++- repository/user.go | 135 ++++++++++++++++++++++++++++++++++++-- response/base_response.go | 2 +- security/security.go | 2 +- tools/utils.go | 35 ++++++++++ 7 files changed, 219 insertions(+), 10 deletions(-) diff --git a/cache/search.go b/cache/search.go index d83bcd6..fb4b8df 100644 --- a/cache/search.go +++ b/cache/search.go @@ -1,11 +1,16 @@ package cache import ( + "electricity_bill_calc/logger" "fmt" "strings" "time" + + "go.uber.org/zap" ) +var log = logger.Named("Cache") + func assembleSearchKey(entityName string, additional ...string) string { var keys = make([]string, 0) keys = append(keys, strings.ToUpper(entityName)) @@ -70,6 +75,10 @@ func RetrievePagedSearch[T any](entityName string, conditions ...string) (*T, in if err != nil { return nil, -1, err } + if instance == nil || count == nil { + log.Warn("检索结果或者检索总数为空。", zap.String("searchKey", searchKey), zap.String("countKey", countKey)) + return nil, -1, nil + } return instance, count.Count, nil } diff --git a/config/settings.go b/config/settings.go index a46dd93..b8403ec 100644 --- a/config/settings.go +++ b/config/settings.go @@ -31,7 +31,7 @@ type RedisSetting struct { type ServiceSetting struct { MaxSessionLife time.Duration - ItemsPageSize int + ItemsPageSize uint CacheLifeTime time.Duration HostSerial int64 } diff --git a/controller/user.go b/controller/user.go index 981f6dc..f500569 100644 --- a/controller/user.go +++ b/controller/user.go @@ -4,10 +4,12 @@ import ( "electricity_bill_calc/cache" "electricity_bill_calc/exceptions" "electricity_bill_calc/model" + "electricity_bill_calc/repository" "electricity_bill_calc/response" "electricity_bill_calc/security" "electricity_bill_calc/service" "net/http" + "strconv" "github.com/gofiber/fiber/v2" ) @@ -15,6 +17,7 @@ import ( func InitializeUserHandlers(router *fiber.App) { router.Delete("/login", security.MustAuthenticated, doLogout) router.Post("/login", doLogin) + router.Get("/account", security.ManagementAuthorize, searchUsers) } type _LoginForm struct { @@ -53,13 +56,48 @@ func doLogin(c *fiber.Ctx) error { func doLogout(c *fiber.Ctx) error { result := response.NewResult(c) - session := c.Locals("session") - if session == nil { + session, err := _retreiveSession(c) + if err != nil { return result.Success("用户会话已结束。") } - _, err := cache.ClearSession(session.(*model.Session).Token) + _, err = cache.ClearSession(session.Token) if err != nil { return result.Error(http.StatusInternalServerError, err.Error()) } return result.Success("用户已成功登出系统。") } + +func searchUsers(c *fiber.Ctx) error { + result := response.NewResult(c) + requestPage, err := strconv.Atoi(c.Query("page", "1")) + if err != nil { + return result.NotAccept("查询参数[page]格式不正确。") + } + requestKeyword := c.Query("keyword") + requestUserType, err := strconv.Atoi(c.Query("type", "-1")) + if err != nil { + return result.NotAccept("查询参数[type]格式不正确。") + } + var requestUserStat *bool + state, err := strconv.ParseBool(c.Query("state")) + if err != nil { + requestUserStat = nil + } else { + requestUserStat = &state + } + users, total, err := repository.UserRepository.FindUser( + &requestKeyword, + int16(requestUserType), + requestUserStat, + uint(requestPage), + ) + if err != nil { + return result.NotFound(err.Error()) + } + return result.Json( + http.StatusOK, + "已取得符合条件的用户集合。", + response.NewPagedResponse(requestPage, total).ToMap(), + fiber.Map{"accounts": users}, + ) +} diff --git a/repository/user.go b/repository/user.go index fa42f04..dc164b1 100644 --- a/repository/user.go +++ b/repository/user.go @@ -2,6 +2,7 @@ package repository import ( "electricity_bill_calc/cache" + "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" @@ -28,7 +29,7 @@ var UserRepository = _UserRepository{ // 使用用户名查询指定用户的基本信息 func (ur _UserRepository) FindUserByUsername(username string) (*model.User, error) { ur.log.Info("根据用户名查询指定用户的基本信息。", zap.String("username", username)) - if cachedUser, _ := cache.RetreiveEntity[model.User]("user", username); cachedUser != nil { + if cachedUser, _ := cache.RetrieveEntity[model.User]("user", username); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户名条件的用户基本信息。", zap.String("username", username)) return cachedUser, nil } @@ -48,7 +49,7 @@ func (ur _UserRepository) FindUserByUsername(username string) (*model.User, erro // 使用用户唯一编号查询指定用户的基本信息 func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { ur.log.Info("根据用户唯一编号查询指定用户的基本信息。", zap.String("user id", uid)) - if cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid); cachedUser != nil { + if cachedUser, _ := cache.RetrieveEntity[model.User]("user", uid); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") return cachedUser, nil } @@ -68,7 +69,7 @@ func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { // 使用用户的唯一编号获取用户的详细信息 func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, error) { ur.log.Info("根据用户唯一编号查询指定用户的详细信息。", zap.String("user id", uid)) - if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { + if cachedUser, _ := cache.RetrieveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户详细信息。") return cachedUser, nil } @@ -88,7 +89,7 @@ func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, err // 使用用户唯一编号获取用户的综合详细信息 func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail, error) { ur.log.Info("根据用户唯一编号查询用户的综合详细信息", zap.String("user id", uid)) - if cachedUser, _ := cache.RetreiveEntity[model.UserWithDetail]("user_information", uid); cachedUser != nil { + if cachedUser, _ := cache.RetrieveEntity[model.UserWithDetail]("user_information", uid); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户综合详细信息。") return cachedUser, nil } @@ -141,8 +142,31 @@ func (ur _UserRepository) IsUserExists(uid string) (bool, error) { return userCount > 0, nil } +// 检查指定用户名在数据库中是否已经存在 +func (ur _UserRepository) IsUsernameExists(username string) (bool, error) { + ur.log.Info("检查指定用户名在数据库中是否已经存在。", zap.String("username", username)) + if exists, _ := cache.CheckExists("user", username); exists { + ur.log.Info("已经从缓存获取到了符合指定用户名的用户基本信息。") + return exists, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var userCount int + sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"username": username}).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) + return false, err + } + if userCount > 0 { + cache.CacheExists([]string{"user", fmt.Sprintf("user:%s", username)}, "user", username) + } + return userCount > 0, nil +} + // 创建一个新用户 func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, operator *string) (bool, error) { + ur.log.Info("创建一个新用户。", zap.String("username", user.Username)) ctx, cancel := global.TimeoutContext() defer cancel() tx, err := global.DB.Begin(ctx) @@ -197,3 +221,106 @@ func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, o } return userResult.RowsAffected() > 0 && detailResult.RowsAffected() > 0, nil } + +// 根据给定的条件检索用户 +func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) (*[]model.UserWithDetail, int64, error) { + ur.log.Info("根据给定的条件检索用户。", zap.Uint("page", page), zap.Stringp("keyword", keyword), zap.Int16("user type", userType), zap.Boolp("state", state)) + if users, total, err := cache.RetrievePagedSearch[[]model.UserWithDetail]("user_with_detail", []string{ + fmt.Sprintf("%d", page), + tools.CondFn( + func(v int16) bool { + return v != -1 + }, + userType, + fmt.Sprintf("%d", userType), + "UNDEF", + ), + tools.DefaultStrTo("%s", state, "UNDEF"), + tools.DefaultTo(keyword, ""), + }...); err == nil && users != nil && total != -1 { + return users, total, nil + } + + ctx, cancel := global.TimeoutContext() + defer cancel() + + var ( + userWithDetails []model.UserWithDetail + userCount int64 + ) + userQuery := ur.ds. + From(goqu.T("user").As("u")). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})). + Select( + "u.id", "u.username", "u.reset_needed", "u.type", "u.enabled", + "ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone", + "ud.unit_service_fee", "ud.service_expiration", + "ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by", + ) + countQuery := ur.ds. + From(goqu.T("user").As("u")). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})). + Select(goqu.COUNT("*")) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + userQuery = userQuery.Where( + goqu.Or( + goqu.Ex{"u.username": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.name": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.abbr": goqu.Op{"like": pattern}}, + ), + ) + countQuery = countQuery.Where( + goqu.Or( + goqu.Ex{"u.username": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.name": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.abbr": goqu.Op{"like": pattern}}, + ), + ) + } + + if userType != -1 { + userQuery = userQuery.Where(goqu.Ex{"u.type": userType}) + countQuery = countQuery.Where(goqu.Ex{"u.type": userType}) + } + + if state != nil { + userQuery = userQuery.Where(goqu.Ex{"u.enabled": state}) + countQuery = countQuery.Where(goqu.Ex{"u.enabled": state}) + } + + currentPosition := (page - 1) * config.ServiceSettings.ItemsPageSize + userQuery = userQuery.Offset(currentPosition).Limit(config.ServiceSettings.ItemsPageSize) + + userSql, userParams, _ := userQuery.Prepared(true).ToSQL() + countSql, countParams, _ := countQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &userWithDetails, userSql, userParams...); err != nil { + ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) + return nil, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &userCount, countSql, countParams...); err != nil { + ur.log.Error("从数据库查询用户列表总数失败。", zap.Error(err)) + return nil, 0, err + } + cache.CachePagedSearch( + userWithDetails, + userCount, + []string{"user"}, + "user_with_detail", + []string{ + fmt.Sprintf("%d", page), + tools.CondFn( + func(v int16) bool { + return v != -1 + }, + userType, + fmt.Sprintf("%d", userType), + "UNDEF", + ), + tools.DefaultStrTo("%s", state, "UNDEF"), + tools.DefaultTo(keyword, ""), + }..., + ) + return &userWithDetails, userCount, nil +} diff --git a/response/base_response.go b/response/base_response.go index c156f9f..01ce30b 100644 --- a/response/base_response.go +++ b/response/base_response.go @@ -17,7 +17,7 @@ type BaseResponse struct { type PagedResponse struct { Page int `json:"current"` - Size int `json:"pageSize"` + Size uint `json:"pageSize"` Total int64 `json:"total"` } diff --git a/security/security.go b/security/security.go index 151b097..dda0f61 100644 --- a/security/security.go +++ b/security/security.go @@ -15,7 +15,7 @@ import ( func SessionRecovery(c *fiber.Ctx) error { if auth := c.Get("Authorization", ""); len(auth) > 0 { token := strings.Fields(auth)[1] - session, err := cache.RetreiveSession(token) + session, err := cache.RetrieveSession(token) if err == nil && session != nil { c.Locals("session", session) diff --git a/tools/utils.go b/tools/utils.go index 459f1dd..a0333d9 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -2,6 +2,7 @@ package tools import ( "encoding/json" + "fmt" "strings" "github.com/mozillazg/go-pinyin" @@ -51,3 +52,37 @@ func PartitionSlice[T any](slice []T, chunkSize int) [][]T { } return divided } + +// 判断指定指针是否为空,如果为空,则返回指定默认值(指针形式) +func DefaultTo[T any](originValue *T, defaultValue T) T { + if originValue == nil { + return defaultValue + } + return *originValue +} + +// 判断指定的指针是否为空,如果为空,则返回指定的默认字符串,或者返回指针所指内容的字符串形式。 +func DefaultStrTo[T any](format string, originValue *T, defaultStr string) string { + if originValue == nil { + return defaultStr + } + return fmt.Sprintf(format, originValue) +} + +// 判断指定表达式的值,根据表达式的值返回指定的值。相当于其他语言中的三目运算符。 +func Cond[T any](expr bool, trueValue, falseValue T) T { + if expr { + return trueValue + } + return falseValue +} + +// 使用给定的函数对指定的值进行判断,根据表达式的值返回指定的值。 +func CondFn[T, R any](exprFn func(val T) bool, value T, trueValue, falseValue R) R { + return Cond(exprFn(value), trueValue, falseValue) +} + +// 使用给定的函数对指定的值进行判断,如果表达式为真,则返回指定的值,否则返回另一个值。 +func CondOr[T any](exprFn func(val T) bool, value, elseValue T) T { + return CondFn(exprFn, value, value, elseValue) +} From afc011418124800a137f9220aac30cc812da40a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 12:55:28 +0800 Subject: [PATCH 014/141] =?UTF-8?q?enhance(user):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9F=A5=E8=AF=A2=E7=9A=84=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/user.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/repository/user.go b/repository/user.go index dc164b1..651b61c 100644 --- a/repository/user.go +++ b/repository/user.go @@ -290,6 +290,8 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, countQuery = countQuery.Where(goqu.Ex{"u.enabled": state}) } + userQuery.Order(goqu.I("u.created_at").Desc()) + currentPosition := (page - 1) * config.ServiceSettings.ItemsPageSize userQuery = userQuery.Offset(currentPosition).Limit(config.ServiceSettings.ItemsPageSize) From 6efe16e2fe5488bd818b976ad98f40d9c680c149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 13:56:06 +0800 Subject: [PATCH 015/141] =?UTF-8?q?enhance(user):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=94=A8=E6=88=B7=E7=B3=BB=E5=88=97=E7=9A=84?= =?UTF-8?q?Repository=E5=8A=9F=E8=83=BD=E8=BF=81=E7=A7=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/user.go | 9 ++ repository/user.go | 211 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 200 insertions(+), 20 deletions(-) diff --git a/model/user.go b/model/user.go index 8ba6787..b0d836d 100644 --- a/model/user.go +++ b/model/user.go @@ -55,6 +55,15 @@ func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail { } } +type UserModificationForm struct { + Name string + Region *string + Address *string + Contact *string + Phone *string + UnitServiceFee *decimal.Decimal +} + type User struct { Id string Username string diff --git a/repository/user.go b/repository/user.go index 651b61c..d3588dc 100644 --- a/repository/user.go +++ b/repository/user.go @@ -9,9 +9,12 @@ import ( "electricity_bill_calc/tools" "electricity_bill_calc/tools/time" "fmt" + "strings" + stdTime "time" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/fufuok/utils" "github.com/georgysavva/scany/v2/pgxscan" "go.uber.org/zap" ) @@ -223,9 +226,9 @@ func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, o } // 根据给定的条件检索用户 -func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) (*[]model.UserWithDetail, int64, error) { +func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) ([]model.UserWithDetail, int64, error) { ur.log.Info("根据给定的条件检索用户。", zap.Uint("page", page), zap.Stringp("keyword", keyword), zap.Int16("user type", userType), zap.Boolp("state", state)) - if users, total, err := cache.RetrievePagedSearch[[]model.UserWithDetail]("user_with_detail", []string{ + cacheConditions := []string{ fmt.Sprintf("%d", page), tools.CondFn( func(v int16) bool { @@ -237,8 +240,9 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, ), tools.DefaultStrTo("%s", state, "UNDEF"), tools.DefaultTo(keyword, ""), - }...); err == nil && users != nil && total != -1 { - return users, total, nil + } + if users, total, err := cache.RetrievePagedSearch[[]model.UserWithDetail]("user_with_detail", cacheConditions...); err == nil && users != nil && total != -1 { + return *users, total, nil } ctx, cancel := global.TimeoutContext() @@ -299,30 +303,197 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, countSql, countParams, _ := countQuery.Prepared(true).ToSQL() if err := pgxscan.Select(ctx, global.DB, &userWithDetails, userSql, userParams...); err != nil { ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) - return nil, 0, err + return make([]model.UserWithDetail, 0), 0, err } if err := pgxscan.Get(ctx, global.DB, &userCount, countSql, countParams...); err != nil { ur.log.Error("从数据库查询用户列表总数失败。", zap.Error(err)) - return nil, 0, err + return make([]model.UserWithDetail, 0), 0, err } cache.CachePagedSearch( userWithDetails, userCount, []string{"user"}, "user_with_detail", - []string{ - fmt.Sprintf("%d", page), - tools.CondFn( - func(v int16) bool { - return v != -1 - }, - userType, - fmt.Sprintf("%d", userType), - "UNDEF", - ), - tools.DefaultStrTo("%s", state, "UNDEF"), - tools.DefaultTo(keyword, ""), - }..., + cacheConditions..., ) - return &userWithDetails, userCount, nil + return userWithDetails, userCount, nil +} + +// 更新指定用户的详细信息 +func (ur _UserRepository) UpdateDetail(uid string, userDetail model.UserModificationForm, operator *string) (bool, error) { + ur.log.Info("更新指定用户的详细信息。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userDetailUpdateQuery := ur.ds. + Update("user_detail"). + Set(goqu.Record{ + "name": userDetail.Name, "abbr": tools.PinyinAbbr(userDetail.Name), "region": userDetail.Region, + "address": userDetail.Address, "contact": userDetail.Contact, "phone": userDetail.Phone, + "last_modified_at": time.Now(), "last_modified_by": operator, + }). + Where(goqu.Ex{"id": uid}) + + if userDetail.UnitServiceFee != nil { + userDetailUpdateQuery = userDetailUpdateQuery.Set(goqu.Record{"unit_service_fee": userDetail.UnitServiceFee}) + } + + userDetailSql, userDetailParams, _ := userDetailUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userDetailSql, userDetailParams...); err != nil { + ur.log.Error("向数据库更新指定用户的详细信息失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 更新指定用户的登录凭据 +func (ur _UserRepository) UpdatePassword(uid, newCredential string, needReset bool) (bool, error) { + ur.log.Info("更新指定用户的登录凭据。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userUpdateQuery := ur.ds. + Update("user"). + Set(goqu.Record{"password": utils.Sha512Hex(newCredential), "reset_needed": needReset}). + Where(goqu.Ex{"id": uid}) + + userSql, userParams, _ := userUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil { + ur.log.Error("向数据库更新指定用户的登录凭据失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 更新指定用户的可用性状态 +func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) { + ur.log.Info("更新指定用户的可用性状态。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userUpdateQuery := ur.ds. + Update("user"). + Set(goqu.Record{"enabled": state}). + Where(goqu.Ex{"id": uid}) + + userSql, userParams, _ := userUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil { + ur.log.Error("向数据库更新指定用户的可用性状态失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 检索条目数量有限的用户详细信息 +func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) (*[]model.UserDetail, error) { + ur.log.Info("检索条目数量有限的用户详细信息。", zap.Int16p("user type", userType), zap.Uint("limit", limit), zap.Stringp("keyword", keyword)) + actualUserType := tools.DefaultTo(userType, model.USER_TYPE_ENT) + cacheConditions := []string{ + fmt.Sprintf("%d", actualUserType), + tools.DefaultTo(keyword, ""), + fmt.Sprintf("%d", limit), + } + if users, err := cache.RetrieveSearch[[]model.UserDetail]("user_with_detail_limited", cacheConditions...); err == nil && users != nil { + return users, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var users []model.UserDetail + userQuery := ur.ds. + From(goqu.T("user").As("u")). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})). + Select( + "u.id", "u.username", "u.reset_needed", "u.type", "u.enabled", + "ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone", + "ud.unit_service_fee", "ud.service_expiration", + "ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by", + ) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + userQuery = userQuery.Where( + goqu.Or( + goqu.Ex{"u.username": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.name": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.abbr": goqu.Op{"like": pattern}}, + ), + ) + } + + userQuery = userQuery.Where(goqu.Ex{"u.type": actualUserType}) + + userQuery.Order(goqu.I("u.created_at").Desc()).Limit(limit) + + userSql, userParams, _ := userQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { + ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) + return nil, err + } + cache.CacheSearch(users, []string{"user"}, "user_with_detail_limited", cacheConditions...) + return &users, nil +} + +// 更新指定用户的服务有效期限 +func (ur _UserRepository) UpdateServiceExpiration(uid string, expiration stdTime.Time) (bool, error) { + ur.log.Info("更新指定用户的服务有效期限。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userDetailUpdateQuery := ur.ds. + Update("user_detail"). + Set(goqu.Record{"service_expiration": expiration}). + Where(goqu.Ex{"id": uid}) + + userDetailSql, userDetailParams, _ := userDetailUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userDetailSql, userDetailParams...); err != nil { + ur.log.Error("向数据库更新指定用户的服务有效期限失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 检索指定用户列表的详细信息 +func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]model.UserDetail, error) { + ur.log.Info("检索指定用户列表的详细信息。", zap.Strings("user ids", uids)) + if len(uids) == 0 { + return make([]model.UserDetail, 0), nil + } + cacheConditions := []string{ + strings.Join(uids, ","), + } + if users, err := cache.RetrieveSearch[[]model.UserDetail]("user_detail", cacheConditions...); err == nil && users != nil { + return *users, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var users []model.UserDetail + userQuery := ur.ds. + From("user_detail"). + Where(goqu.Ex{"id": goqu.Any(uids)}) + + userSql, userParams, _ := userQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { + ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) + return make([]model.UserDetail, 0), err + } + cache.CacheSearch(users, []string{"user", "user_detail"}, "user", cacheConditions...) + return users, nil } From bca0fd777d441cd11684e2b58e11e6456f9c33d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 1 Jun 2023 18:53:45 +0800 Subject: [PATCH 016/141] =?UTF-8?q?enhance(log):=E5=B0=86=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=94=B9=E4=B8=BA=E4=B8=AD=E6=96=87=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logger/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger/logger.go b/logger/logger.go index de811e6..53d0493 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -40,7 +40,7 @@ func init() { logger = zap.New(core).Named("App") sugaredLogger = logger.Sugar() - logger.Info("Logger initialized.") + logger.Info("日志系统初始化完成。") } func GetLogger() *zap.Logger { From e5b5322e0d6e6752c304d3635373f72208206c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 2 Jun 2023 06:18:34 +0800 Subject: [PATCH 017/141] =?UTF-8?q?fix(region):=E5=9F=BA=E6=9C=AC=E7=A1=AE?= =?UTF-8?q?=E5=AE=9A=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=88=92=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=98=8E=E7=A1=AE=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E5=8A=9F=E8=83=BD=E7=9A=84=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/region.go | 40 ++++++++++++++++++++++++++++++++++++++++ model/region.go | 8 ++++++++ router/router.go | 1 + 3 files changed, 49 insertions(+) create mode 100644 controller/region.go create mode 100644 model/region.go diff --git a/controller/region.go b/controller/region.go new file mode 100644 index 0000000..9e4af8c --- /dev/null +++ b/controller/region.go @@ -0,0 +1,40 @@ +package controller + +import ( + "electricity_bill_calc/repository" + "electricity_bill_calc/response" + "net/http" + + "github.com/gofiber/fiber/v2" +) + +func InitializeRegionHandlers(router *fiber.App) { + router.Get("/region/:rid", getSubRegions) + router.Get("/regions/:rid", getParentRegions) +} + +func getSubRegions(c *fiber.Ctx) error { + result := response.NewResult(c) + requestParentId := c.Params("rid") + regions, err := repository.RegionRepository.FindSubRegions(requestParentId) + if err != nil { + return result.Error(http.StatusInternalServerError, err.Error()) + } + if len(regions) == 0 { + return result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", fiber.Map{"regions": make([]string, 0)}) + } + return result.Json(http.StatusOK, "已经获取到相关的行政区划。", fiber.Map{"regions": regions}) +} + +func getParentRegions(c *fiber.Ctx) error { + result := response.NewResult(c) + requestRegionCode := c.Params("rid") + regions, err := repository.RegionRepository.FindParentRegions(requestRegionCode) + if err != nil { + return result.Error(http.StatusInternalServerError, err.Error()) + } + if len(regions) == 0 { + return result.Json(http.StatusNotFound, "未能获取到相关的行政区划。", fiber.Map{"regions": make([]string, 0)}) + } + return result.Json(http.StatusOK, "以及获取到相关的行政区划。", fiber.Map{"regions": regions}) +} diff --git a/model/region.go b/model/region.go new file mode 100644 index 0000000..76315bd --- /dev/null +++ b/model/region.go @@ -0,0 +1,8 @@ +package model + +type Region struct { + Code string `json:"code"` + Name string `json:"name"` + Level int32 `json:"level"` + Parent string `json:"parent"` +} diff --git a/router/router.go b/router/router.go index 965eeb3..795d48d 100644 --- a/router/router.go +++ b/router/router.go @@ -45,6 +45,7 @@ func App() *fiber.App { app.Use(security.SessionRecovery) controller.InitializeUserHandlers(app) + controller.InitializeRegionHandlers(app) return app } From ee55507e0575189c857ddaa1cab4c20b6bdb007d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 2 Jun 2023 06:18:57 +0800 Subject: [PATCH 018/141] =?UTF-8?q?enhance(region):=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=88=92=E5=86=85=E5=AE=B9=E7=9A=84?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/region.go | 111 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 repository/region.go diff --git a/repository/region.go b/repository/region.go new file mode 100644 index 0000000..70785db --- /dev/null +++ b/repository/region.go @@ -0,0 +1,111 @@ +package repository + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _RegionRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var RegionRepository = _RegionRepository{ + log: logger.Named("Repository", "Region"), + ds: goqu.Dialect("postgres"), +} + +// 获取指定行政区划下所有直接子级行政区划 +func (r *_RegionRepository) FindSubRegions(parent string) ([]model.Region, error) { + r.log.Info("获取指定行政区划下所有直接子级行政区划", zap.String("parent", parent)) + cacheConditions := []string{ + "parent", parent, + } + if regions, err := cache.RetrieveSearch[[]model.Region]("region", cacheConditions...); err == nil && regions != nil { + r.log.Info("已经从缓存获取到了指定的子级行政区划。") + return *regions, nil + } + + ctx, cancel := global.TimeoutContext() + defer cancel() + + var regions []model.Region + regionQuerySql, regionParams, _ := r.ds. + From("region"). + Where(goqu.Ex{"parent": parent}). + Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, ®ions, regionQuerySql, regionParams...); err != nil { + r.log.Error("获取指定行政区划下所有直接子级行政区划失败!", zap.Error(err)) + return nil, err + } + if len(regions) > 0 { + cache.CacheSearch(regions, []string{"region"}, "region", cacheConditions...) + } + + return regions, nil +} + +// 获取一个指定编号的行政区划详细信息 +func (r *_RegionRepository) FindRegion(code string) (*model.Region, error) { + r.log.Info("获取指定行政区划信息", zap.String("code", code)) + if region, err := cache.RetrieveEntity[model.Region]("region", code); err == nil && region != nil { + r.log.Info("已经从缓存获取到了指定的行政区划详细信息。") + return region, nil + } + + ctx, cancel := global.TimeoutContext() + defer cancel() + + var region model.Region + regionQuerySql, regionParams, _ := r.ds. + From("region"). + Where(goqu.Ex{"code": code}). + Prepared(true).ToSQL() + + if err := pgxscan.Get(ctx, global.DB, ®ion, regionQuerySql, regionParams...); err != nil { + r.log.Error("获取指定行政区划信息失败!", zap.Error(err)) + return nil, err + } + + cache.CacheEntity(region, []string{"region"}, "region", code) + return ®ion, nil +} + +// 获取指定行政区划的所有直接和非直接父级 +func (r *_RegionRepository) FindParentRegions(code string) ([]*model.Region, error) { + r.log.Info("获取指定行政区划的所有直接和非直接父级", zap.String("code", code)) + cacheConditions := []string{ + "parent", code, + } + if regions, err := cache.RetrieveSearch[[]*model.Region]("region", cacheConditions...); err == nil && regions != nil { + r.log.Info("已经从缓存获取到了指定的父级行政区划。") + return *regions, nil + } + + var ( + regionsScanTask = []string{code} + regions = make([]*model.Region, 0) + ) + for len(regionsScanTask) > 0 { + region, err := r.FindRegion(regionsScanTask[0]) + regionsScanTask = append([]string{}, regionsScanTask[1:]...) + if err == nil && region != nil { + regions = append(regions, region) + if region.Parent != "0" { + regionsScanTask = append(regionsScanTask, region.Parent) + } + } + } + + if len(regions) > 0 { + cache.CacheSearch(regions, []string{"region"}, "region", cacheConditions...) + } + return regions, nil +} From 04dc9c51aad776a9f0bffa5e9c1280e9f905e305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 2 Jun 2023 06:26:22 +0800 Subject: [PATCH 019/141] =?UTF-8?q?fix(user):=E6=A0=B9=E6=8D=AE=E8=A1=8C?= =?UTF-8?q?=E6=94=BF=E5=8C=BA=E5=88=92=E9=83=A8=E5=88=86=E7=9A=84=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=BB=93=E8=AE=BA=EF=BC=8C=E4=BF=AE=E6=AD=A3=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=83=A8=E5=88=86=E7=9A=84=E6=A8=A1=E5=9E=8B=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/user.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/repository/user.go b/repository/user.go index d3588dc..8d59a85 100644 --- a/repository/user.go +++ b/repository/user.go @@ -226,7 +226,7 @@ func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, o } // 根据给定的条件检索用户 -func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) ([]model.UserWithDetail, int64, error) { +func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) ([]*model.UserWithDetail, int64, error) { ur.log.Info("根据给定的条件检索用户。", zap.Uint("page", page), zap.Stringp("keyword", keyword), zap.Int16("user type", userType), zap.Boolp("state", state)) cacheConditions := []string{ fmt.Sprintf("%d", page), @@ -241,7 +241,7 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, tools.DefaultStrTo("%s", state, "UNDEF"), tools.DefaultTo(keyword, ""), } - if users, total, err := cache.RetrievePagedSearch[[]model.UserWithDetail]("user_with_detail", cacheConditions...); err == nil && users != nil && total != -1 { + if users, total, err := cache.RetrievePagedSearch[[]*model.UserWithDetail]("user_with_detail", cacheConditions...); err == nil && users != nil && total != -1 { return *users, total, nil } @@ -249,7 +249,7 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, defer cancel() var ( - userWithDetails []model.UserWithDetail + userWithDetails []*model.UserWithDetail userCount int64 ) userQuery := ur.ds. @@ -303,11 +303,11 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, countSql, countParams, _ := countQuery.Prepared(true).ToSQL() if err := pgxscan.Select(ctx, global.DB, &userWithDetails, userSql, userParams...); err != nil { ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) - return make([]model.UserWithDetail, 0), 0, err + return make([]*model.UserWithDetail, 0), 0, err } if err := pgxscan.Get(ctx, global.DB, &userCount, countSql, countParams...); err != nil { ur.log.Error("从数据库查询用户列表总数失败。", zap.Error(err)) - return make([]model.UserWithDetail, 0), 0, err + return make([]*model.UserWithDetail, 0), 0, err } cache.CachePagedSearch( userWithDetails, @@ -397,7 +397,7 @@ func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) { } // 检索条目数量有限的用户详细信息 -func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) (*[]model.UserDetail, error) { +func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) ([]*model.UserDetail, error) { ur.log.Info("检索条目数量有限的用户详细信息。", zap.Int16p("user type", userType), zap.Uint("limit", limit), zap.Stringp("keyword", keyword)) actualUserType := tools.DefaultTo(userType, model.USER_TYPE_ENT) cacheConditions := []string{ @@ -405,13 +405,13 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, tools.DefaultTo(keyword, ""), fmt.Sprintf("%d", limit), } - if users, err := cache.RetrieveSearch[[]model.UserDetail]("user_with_detail_limited", cacheConditions...); err == nil && users != nil { - return users, nil + if users, err := cache.RetrieveSearch[[]*model.UserDetail]("user_with_detail_limited", cacheConditions...); err == nil && users != nil { + return *users, nil } ctx, cancel := global.TimeoutContext() defer cancel() - var users []model.UserDetail + var users []*model.UserDetail userQuery := ur.ds. From(goqu.T("user").As("u")). Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})). @@ -438,12 +438,12 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, userQuery.Order(goqu.I("u.created_at").Desc()).Limit(limit) userSql, userParams, _ := userQuery.Prepared(true).ToSQL() - if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { + if err := pgxscan.Select(ctx, global.DB, users, userSql, userParams...); err != nil { ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) return nil, err } cache.CacheSearch(users, []string{"user"}, "user_with_detail_limited", cacheConditions...) - return &users, nil + return users, nil } // 更新指定用户的服务有效期限 @@ -470,29 +470,29 @@ func (ur _UserRepository) UpdateServiceExpiration(uid string, expiration stdTime } // 检索指定用户列表的详细信息 -func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]model.UserDetail, error) { +func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]*model.UserDetail, error) { ur.log.Info("检索指定用户列表的详细信息。", zap.Strings("user ids", uids)) if len(uids) == 0 { - return make([]model.UserDetail, 0), nil + return make([]*model.UserDetail, 0), nil } cacheConditions := []string{ strings.Join(uids, ","), } - if users, err := cache.RetrieveSearch[[]model.UserDetail]("user_detail", cacheConditions...); err == nil && users != nil { + if users, err := cache.RetrieveSearch[[]*model.UserDetail]("user_detail", cacheConditions...); err == nil && users != nil { return *users, nil } ctx, cancel := global.TimeoutContext() defer cancel() - var users []model.UserDetail + var users []*model.UserDetail userQuery := ur.ds. From("user_detail"). Where(goqu.Ex{"id": goqu.Any(uids)}) userSql, userParams, _ := userQuery.Prepared(true).ToSQL() - if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { + if err := pgxscan.Select(ctx, global.DB, users, userSql, userParams...); err != nil { ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) - return make([]model.UserDetail, 0), err + return make([]*model.UserDetail, 0), err } cache.CacheSearch(users, []string{"user", "user_detail"}, "user", cacheConditions...) return users, nil From 919883f5217bb629c3002c1c2f7841f353feb1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 2 Jun 2023 10:18:16 +0800 Subject: [PATCH 020/141] =?UTF-8?q?enhance(user):=E4=B8=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=A8=A1=E5=9E=8B=E5=A2=9E=E5=8A=A0Json=E9=94=AE?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/user.go | 106 +++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/model/user.go b/model/user.go index b0d836d..d79edd2 100644 --- a/model/user.go +++ b/model/user.go @@ -13,14 +13,14 @@ const ( ) type ManagementAccountCreationForm struct { - Id *string - Username string - Name string - Contact *string - Phone *string - Type int16 `json:"type"` - Enabled bool - Expires Date + Id *string `json:"id"` + Username string `json:"username"` + Name string `json:"name"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + Type int16 `json:"type"` + Enabled bool `json:"enabled"` + Expires Date `json:"expires"` } func (m ManagementAccountCreationForm) IntoUser() *User { @@ -56,58 +56,58 @@ func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail { } type UserModificationForm struct { - Name string - Region *string - Address *string - Contact *string - Phone *string - UnitServiceFee *decimal.Decimal + Name string `json:"name"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + UnitServiceFee *decimal.Decimal `json:"unitServiceFee"` } type User struct { - Id string - Username string - Password string - ResetNeeded bool - UserType int16 `db:"type"` - Enabled bool - CreatedAt *time.Time + Id string `json:"id"` + Username string `json:"username"` + Password string `json:"password"` + ResetNeeded bool `json:"resetNeeded"` + UserType int16 `db:"type"` + Enabled bool `json:"enabled"` + CreatedAt *time.Time `json:"createdAt"` } type UserDetail struct { - Id string - Name *string - Abbr *string - Region *string - Address *string - Contact *string - Phone *string - UnitServiceFee decimal.Decimal `db:"unit_service_fee"` - ServiceExpiration Date - CreatedAt time.Time - CreatedBy *string - LastModifiedAt time.Time - LastModifiedBy *string - DeletedAt *time.Time - DeletedBy *string + Id string `json:"id"` + Name *string `json:"name"` + Abbr *string `json:"abbr"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"` + ServiceExpiration Date `json:"serviceExpiration"` + CreatedAt time.Time `json:"createdAt"` + CreatedBy *string `json:"createdBy"` + LastModifiedAt time.Time `json:"lastModifiedAt"` + LastModifiedBy *string `json:"lastModifiedBy"` + DeletedAt *time.Time `json:"deletedAt"` + DeletedBy *string `json:"deletedBy"` } type UserWithDetail struct { - Id string - Username string - ResetNeeded bool - UserType int16 `db:"type"` - Enabled bool - Name *string - Abbr *string - Region *string - Address *string - Contact *string - Phone *string - UnitServiceFee decimal.Decimal `db:"unit_service_fee"` - ServiceExpiration Date - CreatedAt time.Time - CreatedBy *string - LastModifiedAt time.Time - LastModifiedBy *string + Id string `json:"id"` + Username string `json:"username"` + ResetNeeded bool `json:"resetNeeded"` + UserType int16 `db:"type" json:"type"` + Enabled bool `json:"enabled"` + Name *string `json:"name"` + Abbr *string `json:"abbr"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"` + ServiceExpiration Date `json:"serviceExpiration"` + CreatedAt time.Time `json:"createdAt"` + CreatedBy *string `json:"createdBy"` + LastModifiedAt time.Time `json:"lastModifiedAt"` + LastModifiedBy *string `json:"lastModifiedBy"` } From cd723e98e3687f7541a4cb17ddf13da3d68e8410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 2 Jun 2023 10:18:45 +0800 Subject: [PATCH 021/141] =?UTF-8?q?feat(user):=E5=AE=8C=E6=88=90=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=94=A8=E6=88=B7=E6=9C=8D=E5=8A=A1=E6=9C=89=E6=95=88?= =?UTF-8?q?=E6=9C=9F=E7=9A=84=E5=AE=9A=E4=B9=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/user.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/controller/user.go b/controller/user.go index f500569..22c958c 100644 --- a/controller/user.go +++ b/controller/user.go @@ -3,6 +3,7 @@ package controller import ( "electricity_bill_calc/cache" "electricity_bill_calc/exceptions" + "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/repository" "electricity_bill_calc/response" @@ -12,12 +13,16 @@ import ( "strconv" "github.com/gofiber/fiber/v2" + "go.uber.org/zap" ) +var userLog = logger.Named("Handler", "User") + func InitializeUserHandlers(router *fiber.App) { router.Delete("/login", security.MustAuthenticated, doLogout) router.Post("/login", doLogin) - router.Get("/account", security.ManagementAuthorize, searchUsers) + router.Get("/account", security.OPSAuthorize, searchUsers) + router.Get("/expiration", security.EnterpriseAuthorize, getAccountExpiration) } type _LoginForm struct { @@ -30,12 +35,14 @@ func doLogin(c *fiber.Ctx) error { result := response.NewResult(c) loginData := new(_LoginForm) if err := c.BodyParser(loginData); err != nil { + userLog.Error("表单解析失败!", zap.Error(err)) return result.Error(http.StatusInternalServerError, "表单解析失败。") } var ( session *model.Session err error ) + userLog.Info("有用户请求登录。", zap.String("username", loginData.Username), zap.Int16("type", loginData.Type)) if loginData.Type == model.USER_TYPE_ENT { session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) } else { @@ -48,6 +55,7 @@ func doLogin(c *fiber.Ctx) error { } return result.Error(int(authError.Code), authError.Message) } else { + userLog.Error("用户登录请求处理失败!", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } } @@ -62,6 +70,7 @@ func doLogout(c *fiber.Ctx) error { } _, err = cache.ClearSession(session.Token) if err != nil { + userLog.Error("用户登出处理失败!", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } return result.Success("用户已成功登出系统。") @@ -101,3 +110,21 @@ func searchUsers(c *fiber.Ctx) error { fiber.Map{"accounts": users}, ) } + +func getAccountExpiration(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + userLog.Error("未找到有效的用户会话。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + userDetail, err := repository.UserRepository.FindUserDetailById(session.Uid) + if err != nil { + return result.NotFound("未找到指定的用户档案") + } + return result.Json( + http.StatusOK, + "已经取得用户的服务期限信息", + fiber.Map{"expiration": userDetail.ServiceExpiration.Format("2006-01-02")}, + ) +} From 097e25f0701b864ac5b312f83082dc410f5b41b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 2 Jun 2023 15:51:08 +0800 Subject: [PATCH 022/141] =?UTF-8?q?feat(user):=E5=AE=8C=E6=88=90=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=83=A8=E5=88=86=E6=89=80=E6=9C=89=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E7=9A=84=E8=BF=81=E7=A7=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/user.go | 211 +++++++++++++++++++++++++++++++++++++- go.mod | 24 +++-- go.sum | 54 ++++++++++ model/types.go | 48 +++------ repository/user.go | 20 ++-- response/base_response.go | 18 ++-- service/user.go | 4 +- tools/time/time.go | 10 +- vo/user.go | 126 +++++++++++++++++++++++ 9 files changed, 447 insertions(+), 68 deletions(-) create mode 100644 vo/user.go diff --git a/controller/user.go b/controller/user.go index 22c958c..c79385e 100644 --- a/controller/user.go +++ b/controller/user.go @@ -9,6 +9,8 @@ import ( "electricity_bill_calc/response" "electricity_bill_calc/security" "electricity_bill_calc/service" + "electricity_bill_calc/tools" + "electricity_bill_calc/vo" "net/http" "strconv" @@ -22,7 +24,15 @@ func InitializeUserHandlers(router *fiber.App) { router.Delete("/login", security.MustAuthenticated, doLogout) router.Post("/login", doLogin) router.Get("/account", security.OPSAuthorize, searchUsers) + router.Post("/account", security.OPSAuthorize, createOPSAccount) + router.Get("/account/:uid", security.MustAuthenticated, fetchUserInformation) + router.Put("/account/:uid", security.OPSAuthorize, modifyUserInformation) + router.Put("/account/enabled/state", security.OPSAuthorize, changeUserState) router.Get("/expiration", security.EnterpriseAuthorize, getAccountExpiration) + router.Post("/enterprise", security.OPSAuthorize, createEnterpriseAccount) + router.Get("/enterprise/quick/search", security.OPSAuthorize, quickSearchEnterprise) + router.Put("/password", resetUserPassword) + router.Delete("/password/:uid", security.OPSAuthorize, invalidUserPassword) } type _LoginForm struct { @@ -103,8 +113,7 @@ func searchUsers(c *fiber.Ctx) error { if err != nil { return result.NotFound(err.Error()) } - return result.Json( - http.StatusOK, + return result.Success( "已取得符合条件的用户集合。", response.NewPagedResponse(requestPage, total).ToMap(), fiber.Map{"accounts": users}, @@ -122,9 +131,203 @@ func getAccountExpiration(c *fiber.Ctx) error { if err != nil { return result.NotFound("未找到指定的用户档案") } - return result.Json( - http.StatusOK, + return result.Success( "已经取得用户的服务期限信息", fiber.Map{"expiration": userDetail.ServiceExpiration.Format("2006-01-02")}, ) } + +func createOPSAccount(c *fiber.Ctx) error { + userLog.Info("请求创建运维或监管账户。") + result := response.NewResult(c) + creationForm := new(vo.MGTAndOPSAccountCreationForm) + if err := c.BodyParser(creationForm); err != nil { + userLog.Error("表单解析失败!", zap.Error(err)) + return result.UnableToParse("无法解析提交的数据。") + } + exists, err := repository.UserRepository.IsUsernameExists(creationForm.Username) + if err != nil { + userLog.Error("检查用户名是否已经被使用时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if exists { + return result.Conflict("指定的用户名已经被使用了。") + } + verifyCode, err := service.UserService.CreateUserAccount(creationForm.IntoUser(), creationForm.IntoUserDetail()) + if err != nil { + userLog.Error("创建用户账户时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Created("用户已经成功创建。", fiber.Map{"verify": verifyCode}) +} + +func fetchUserInformation(c *fiber.Ctx) error { + userLog.Info("请求获取用户详细信息。") + result := response.NewResult(c) + targetUserId := c.Params("uid") + exists, err := repository.UserRepository.IsUserExists(targetUserId) + if err != nil { + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !exists { + return result.NotFound("指定的用户不存在。") + } + userDetail, err := repository.UserRepository.FindUserDetailById(targetUserId) + if err != nil { + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Success("用户详细信息已获取到。", fiber.Map{"user": userDetail}) +} + +func modifyUserInformation(c *fiber.Ctx) error { + userLog.Info("请求修改用户详细信息。") + session, _ := _retreiveSession(c) + result := response.NewResult(c) + targetUserId := c.Params("uid") + exists, err := repository.UserRepository.IsUserExists(targetUserId) + if err != nil { + userLog.Error("检查用户是否存在时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !exists { + return result.NotFound("指定的用户不存在。") + } + modificationForm := new(vo.UserDetailModificationForm) + if err := c.BodyParser(modificationForm); err != nil { + userLog.Error("表单解析失败!", zap.Error(err)) + return result.UnableToParse("无法解析提交的数据。") + } + userLog.Debug("用户服务费修改表单:", zap.Any("form", modificationForm)) + detailFormForUpdate, err := modificationForm.IntoModificationModel() + if err != nil { + userLog.Error("用户服务费解析转换失败!", zap.Error(err)) + return result.UnableToParse("无法解析提交的数据,服务费格式不正确。") + } + if ok, err := repository.UserRepository.UpdateDetail(targetUserId, *detailFormForUpdate, &session.Uid); err != nil || !ok { + userLog.Error("更新用户详细信息失败!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Updated("指定用户的信息已经更新。") +} + +func changeUserState(c *fiber.Ctx) error { + userLog.Info("请求修改用户状态。") + result := response.NewResult(c) + modificationForm := new(vo.UserStateChangeForm) + if err := c.BodyParser(modificationForm); err != nil { + userLog.Error("表单解析失败!", zap.Error(err)) + return result.UnableToParse("无法解析提交的数据。") + } + exists, err := repository.UserRepository.IsUserExists(modificationForm.Uid) + if err != nil { + userLog.Error("检查用户是否存在时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !exists { + return result.NotFound("指定的用户不存在。") + } + if ok, err := repository.UserRepository.ChangeState(modificationForm.Uid, modificationForm.Enabled); err != nil || !ok { + userLog.Error("更新用户状态失败!", zap.Error(err)) + return result.NotAccept("无法更新用户状态。") + } + return result.Updated("用户的状态已经更新。") +} + +func createEnterpriseAccount(c *fiber.Ctx) error { + userLog.Info("请求创建企业账户。") + result := response.NewResult(c) + creationForm := new(vo.EnterpriseAccountCreationForm) + if err := c.BodyParser(creationForm); err != nil { + userLog.Error("表单解析失败!", zap.Error(err)) + return result.UnableToParse("无法解析提交的数据。") + } + exists, err := repository.UserRepository.IsUsernameExists(creationForm.Username) + if err != nil { + userLog.Error("检查用户名是否已经被使用时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if exists { + return result.Conflict("指定的用户名已经被使用了。") + } + userDetail, err := creationForm.IntoUserDetail() + if err != nil { + userLog.Error("转换用户详细档案时发生错误!", zap.Error(err)) + return result.UnableToParse("无法解析提交的数据,服务费格式不正确。") + } + verifyCode, err := service.UserService.CreateUserAccount(creationForm.IntoUser(), userDetail) + if err != nil { + userLog.Error("创建用户账户时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Created("用户已经成功创建。", fiber.Map{"verify": verifyCode}) +} + +func quickSearchEnterprise(c *fiber.Ctx) error { + userLog.Info("请求快速查询企业账户。") + result := response.NewResult(c) + keyword := c.Query("keyword") + limit := c.QueryInt("limit", 6) + if limit < 1 { + limit = 6 + } + users, err := repository.UserRepository.SearchUsersWithLimit(nil, &keyword, uint(limit)) + if err != nil { + userLog.Error("查询用户时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Success("已查询到存在符合条件的企业", fiber.Map{"users": users}) +} + +func resetUserPassword(c *fiber.Ctx) error { + userLog.Info("请求重置用户密码。") + result := response.NewResult(c) + repasswordForm := new(vo.RepasswordForm) + if err := c.BodyParser(repasswordForm); err != nil { + userLog.Error("表单解析失败!", zap.Error(err)) + return result.UnableToParse("无法解析提交的数据。") + } + user, err := repository.UserRepository.FindUserByUsername(repasswordForm.Username) + if err != nil { + userLog.Error("检查用户是否存在时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if user == nil { + return result.NotFound("指定的用户不存在。") + } + if !service.UserService.MatchUserPassword(user.Password, repasswordForm.VerifyCode) { + return result.Unauthorized("验证码不正确。") + } + ok, err := repository.UserRepository.UpdatePassword(user.Id, repasswordForm.NewPassword, false) + if err != nil { + userLog.Error("更新用户凭据时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !ok { + return result.NotAccept("无法更新用户凭据。") + } + return result.Updated("用户凭据已经更新。") +} + +func invalidUserPassword(c *fiber.Ctx) error { + userLog.Info("请求使用户凭据失效。") + result := response.NewResult(c) + uid := c.Params("uid") + exists, err := repository.UserRepository.IsUserExists(uid) + if err != nil { + userLog.Error("检查用户是否存在时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !exists { + return result.NotFound("指定的用户不存在。") + } + verifyCode := tools.RandStr(10) + ok, err := repository.UserRepository.UpdatePassword(uid, verifyCode, true) + if err != nil { + userLog.Error("更新用户凭据时发生错误!", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !ok { + return result.NotAccept("未能更新用户凭据。") + } + return result.Updated("用户凭据已经更新。", fiber.Map{"verify": verifyCode}) +} diff --git a/go.mod b/go.mod index 83046ec..3b0e108 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/deckarep/golang-set/v2 v2.1.0 github.com/fufuok/utils v0.7.13 - github.com/gofiber/fiber/v2 v2.38.1 + github.com/gofiber/fiber/v2 v2.46.0 github.com/google/uuid v1.3.0 github.com/jinzhu/copier v0.3.5 github.com/liamylian/jsontime/v2 v2.0.0 @@ -14,14 +14,14 @@ require ( github.com/samber/lo v1.27.0 github.com/shopspring/decimal v1.3.1 github.com/spf13/viper v1.12.0 - github.com/valyala/fasthttp v1.40.0 + github.com/valyala/fasthttp v1.47.0 github.com/xuri/excelize/v2 v2.6.1 go.uber.org/zap v1.23.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) require ( - github.com/andybalholm/brotli v1.0.4 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/georgysavva/scany/v2 v2.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect @@ -29,8 +29,16 @@ require ( github.com/jackc/puddle/v2 v2.2.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect - github.com/klauspost/compress v1.15.0 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/tinylib/msgp v1.1.8 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect @@ -67,11 +75,11 @@ require ( github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect - golang.org/x/crypto v0.6.0 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.6.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.8.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f733226..08a121d 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -78,6 +80,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= +github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns= +github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -165,6 +169,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -178,6 +184,13 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -197,6 +210,9 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -209,6 +225,9 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7 github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= @@ -218,6 +237,11 @@ github.com/rueian/rueidis v0.0.73 h1:+r0Z6C6HMnkquPgY3zaHVpTqmCyJL56Z36GSlyBrufk github.com/rueian/rueidis v0.0.73/go.mod h1:FwnfDILF2GETrvXcYFlhIiru/7NmSIm1f+7C5kutO0I= github.com/samber/lo v1.27.0 h1:GOyDWxsblvqYobqsmUuMddPa2/mMzkKyojlXol4+LaQ= github.com/samber/lo v1.27.0/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= @@ -244,6 +268,9 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/uptrace/bun v1.1.8 h1:slxuaP4LYWFbPRUmTtQhfJN+6eX/6ar2HDKYTcI50SA= @@ -256,6 +283,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc= github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= +github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= @@ -272,6 +301,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -293,6 +323,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -302,6 +333,8 @@ golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -339,6 +372,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -372,10 +407,14 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -395,6 +434,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -435,13 +475,21 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U= golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -451,8 +499,11 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -497,12 +548,15 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/model/types.go b/model/types.go index 90f5713..7a74be8 100644 --- a/model/types.go +++ b/model/types.go @@ -6,6 +6,8 @@ import ( "encoding/json" "fmt" "time" + + et "electricity_bill_calc/tools/time" ) type Date struct { @@ -13,32 +15,24 @@ type Date struct { } func NewDate(t time.Time) Date { - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - panic(err) - } - t = t.In(loc) + t = t.In(et.Loc) return Date{ - Time: time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc), + Time: time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, et.Loc), } } +func SpecificDate(year int, month time.Month, date int) Date { + return NewDate(et.Time(year, month, date, 0, 0, 0, 0)) +} + func NewEmptyDate() Date { - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - panic(err) - } return Date{ - Time: time.Time{}.In(loc), + Time: time.Time{}.In(et.Loc), } } func ParseDate(t string) (Date, error) { - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - return NewEmptyDate(), fmt.Errorf("unable to load time zone, %w", err) - } - d, err := time.ParseInLocation("2006-01-02", t, loc) + d, err := time.ParseInLocation("2006-01-02", t, et.Loc) if err != nil { return NewEmptyDate(), fmt.Errorf("unable to parse given time, %w", err) } @@ -62,30 +56,22 @@ func (d Date) ToString() string { var _ driver.Valuer = (*Date)(nil) func (d Date) Value() (driver.Value, error) { - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - panic(err) - } - return d.In(loc).Format("2006-01-02"), nil + return d.In(et.Loc).Format("2006-01-02"), nil } var _ sql.Scanner = (*Date)(nil) // Scan scans the time parsing it if necessary using timeFormat. func (d *Date) Scan(src interface{}) (err error) { - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - panic(err) - } switch src := src.(type) { case time.Time: *d = NewDate(src) return nil case string: - d.Time, err = time.ParseInLocation("2006-01-02", src, loc) + d.Time, err = time.ParseInLocation("2006-01-02", src, et.Loc) return err case []byte: - d.Time, err = time.ParseInLocation("2006-01-02", string(src), loc) + d.Time, err = time.ParseInLocation("2006-01-02", string(src), et.Loc) return err case nil: d.Time = time.Time{} @@ -104,15 +90,11 @@ func (d Date) MarshalJSON() ([]byte, error) { var _ json.Unmarshaler = (*Date)(nil) func (d *Date) UnmarshalJSON(raw []byte) error { - loc, err := time.LoadLocation("Asia/Shanghai") - if err != nil { - return fmt.Errorf("unable to load time zone, %w", err) - } var s string - err = json.Unmarshal(raw, &s) + err := json.Unmarshal(raw, &s) if err != nil { return fmt.Errorf("unable to unmarshal value, %w", err) } - d.Time, err = time.ParseInLocation("2006-01-02", s, loc) + d.Time, err = time.ParseInLocation("2006-01-02", s, et.Loc) return err } diff --git a/repository/user.go b/repository/user.go index 8d59a85..8d49ce6 100644 --- a/repository/user.go +++ b/repository/user.go @@ -16,6 +16,7 @@ import ( _ "github.com/doug-martin/goqu/v9/dialect/postgres" "github.com/fufuok/utils" "github.com/georgysavva/scany/v2/pgxscan" + "github.com/samber/lo" "go.uber.org/zap" ) @@ -325,19 +326,20 @@ func (ur _UserRepository) UpdateDetail(uid string, userDetail model.UserModifica ctx, cancel := global.TimeoutContext() defer cancel() + updates := goqu.Record{ + "name": userDetail.Name, "abbr": tools.PinyinAbbr(userDetail.Name), "region": userDetail.Region, + "address": userDetail.Address, "contact": userDetail.Contact, "phone": userDetail.Phone, + "last_modified_at": time.Now(), "last_modified_by": operator, + } + if userDetail.UnitServiceFee != nil { + updates = lo.Assign(updates, goqu.Record{"unit_service_fee": userDetail.UnitServiceFee}) + } + userDetailUpdateQuery := ur.ds. Update("user_detail"). - Set(goqu.Record{ - "name": userDetail.Name, "abbr": tools.PinyinAbbr(userDetail.Name), "region": userDetail.Region, - "address": userDetail.Address, "contact": userDetail.Contact, "phone": userDetail.Phone, - "last_modified_at": time.Now(), "last_modified_by": operator, - }). + Set(updates). Where(goqu.Ex{"id": uid}) - if userDetail.UnitServiceFee != nil { - userDetailUpdateQuery = userDetailUpdateQuery.Set(goqu.Record{"unit_service_fee": userDetail.UnitServiceFee}) - } - userDetailSql, userDetailParams, _ := userDetailUpdateQuery. Prepared(true).ToSQL() diff --git a/response/base_response.go b/response/base_response.go index 01ce30b..54e1b9f 100644 --- a/response/base_response.go +++ b/response/base_response.go @@ -61,7 +61,7 @@ func (r Result) UnableToParse(msg string) error { // 用户未获得授权)响应 func (r *Result) Unauthorized(msg string) error { - return r.response(fiber.StatusOK, fiber.StatusUnauthorized, msg) + return r.response(fiber.StatusUnauthorized, fiber.StatusUnauthorized, msg) } // 简易操作成功信息 @@ -71,37 +71,37 @@ func (r *Result) Success(msg string, payloads ...map[string]interface{}) error { // 数据成功创建 func (r Result) Created(msg string, payloads ...map[string]interface{}) error { - return r.response(fiber.StatusOK, fiber.StatusCreated, msg, payloads...) + return r.response(fiber.StatusCreated, fiber.StatusCreated, msg, payloads...) } // 数据成功更新 -func (r Result) Updated(msg string) error { - return r.response(fiber.StatusOK, fiber.StatusAccepted, msg) +func (r Result) Updated(msg string, payloads ...map[string]interface{}) error { + return r.response(fiber.StatusAccepted, fiber.StatusAccepted, msg, payloads...) } // 数据已成功删除 func (r Result) Deleted(msg string) error { - return r.response(fiber.StatusOK, fiber.StatusNoContent, msg) + return r.response(fiber.StatusNoContent, fiber.StatusNoContent, msg) } // 指定操作未被接受 func (r Result) BadRequest(msg string) error { - return r.response(fiber.StatusOK, fiber.StatusBadRequest, msg) + return r.response(fiber.StatusBadRequest, fiber.StatusBadRequest, msg) } // 指定操作未被接受 func (r Result) NotAccept(msg string) error { - return r.response(fiber.StatusOK, fiber.StatusNotAcceptable, msg) + return r.response(fiber.StatusNotAcceptable, fiber.StatusNotAcceptable, msg) } // 数据未找到 func (r Result) NotFound(msg string) error { - return r.response(fiber.StatusOK, fiber.StatusNotFound, msg) + return r.response(fiber.StatusNotFound, fiber.StatusNotFound, msg) } // 数据存在冲突 func (r Result) Conflict(msg string) error { - return r.response(fiber.StatusOK, fiber.StatusConflict, msg) + return r.response(fiber.StatusConflict, fiber.StatusConflict, msg) } // 快速自由JSON格式响应 diff --git a/service/user.go b/service/user.go index d528222..4dc36fc 100644 --- a/service/user.go +++ b/service/user.go @@ -25,7 +25,7 @@ var UserService = _UserService{ log: logger.Named("Service", "User"), } -func matchUserPassword(controlCode, testCode string) bool { +func (us _UserService) MatchUserPassword(controlCode, testCode string) bool { hashedCode := utils.Sha512Hex(testCode) return controlCode == hashedCode } @@ -56,7 +56,7 @@ func (us _UserService) processUserLogin(username, password string, userType []in authErr.NeedReset = true return nil, nil, authErr } - if !matchUserPassword(user.Password, password) { + if !us.MatchUserPassword(user.Password, password) { us.log.Warn("处理用户登录失败,密码错误。", zap.String("username", username)) return nil, nil, exceptions.NewAuthenticationError(402, "用户凭据不正确。") } diff --git a/tools/time/time.go b/tools/time/time.go index 5bbd36b..b3c3fe4 100644 --- a/tools/time/time.go +++ b/tools/time/time.go @@ -4,14 +4,18 @@ import ( "time" ) -var loc *time.Location = time.FixedZone("+0800", 8*60*60) +var Loc *time.Location = time.FixedZone("+0800", 8*60*60) func Now() time.Time { - return time.Now().In(loc) + return time.Now().In(Loc) +} + +func Time(year int, month time.Month, date, hours, min, sec, nsec int) time.Time { + return time.Date(year, month, date, hours, min, sec, nsec, Loc) } func Timestamp() int64 { - startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() + startline := time.Date(2022, 2, 22, 22, 22, 22, 0, Loc).Unix() return Now().Unix() - startline } diff --git a/vo/user.go b/vo/user.go new file mode 100644 index 0000000..b09051e --- /dev/null +++ b/vo/user.go @@ -0,0 +1,126 @@ +package vo + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/tools/time" + st "time" + + "github.com/shopspring/decimal" +) + +type MGTAndOPSAccountCreationForm struct { + Username string `json:"username"` + Name string `json:"name"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + UserType int16 `json:"type"` +} + +func (u MGTAndOPSAccountCreationForm) IntoUser() *model.User { + return &model.User{ + Username: u.Username, + Password: "", + ResetNeeded: false, + UserType: u.UserType, + Enabled: true, + CreatedAt: nil, + } +} + +func (u MGTAndOPSAccountCreationForm) IntoUserDetail() *model.UserDetail { + return &model.UserDetail{ + Name: &u.Name, + Abbr: nil, + Region: nil, + Address: nil, + Contact: u.Contact, + Phone: u.Phone, + UnitServiceFee: decimal.Zero, + ServiceExpiration: model.SpecificDate(2099, st.December, 31), + CreatedAt: time.Now(), + CreatedBy: nil, + LastModifiedAt: time.Now(), + LastModifiedBy: nil, + DeletedAt: nil, + DeletedBy: nil, + } +} + +type EnterpriseAccountCreationForm struct { + Username string `json:"username"` + Name string `json:"name"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + UnitServiceFee string `json:"unitServiceFee"` +} + +func (u EnterpriseAccountCreationForm) IntoUser() *model.User { + return &model.User{ + Username: u.Username, + Password: "", + ResetNeeded: false, + UserType: model.USER_TYPE_ENT, + Enabled: true, + CreatedAt: nil, + } +} + +func (u EnterpriseAccountCreationForm) IntoUserDetail() (*model.UserDetail, error) { + unitServiceFee, err := decimal.NewFromString(u.UnitServiceFee) + if err != nil { + return nil, err + } + return &model.UserDetail{ + Name: &u.Name, + Abbr: nil, + Region: u.Region, + Address: u.Address, + Contact: u.Contact, + Phone: u.Phone, + UnitServiceFee: unitServiceFee, + ServiceExpiration: model.SpecificDate(2000, st.January, 1), + CreatedAt: time.Now(), + CreatedBy: nil, + LastModifiedAt: time.Now(), + LastModifiedBy: nil, + DeletedAt: nil, + DeletedBy: nil, + }, nil +} + +type UserDetailModificationForm struct { + Name string `json:"name"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + UnitServiceFee *string `json:"unitServiceFee"` +} + +func (u UserDetailModificationForm) IntoModificationModel() (*model.UserModificationForm, error) { + unitServiceFee, err := decimal.NewFromString(*u.UnitServiceFee) + if err != nil { + return nil, err + } + return &model.UserModificationForm{ + Name: u.Name, + Region: u.Region, + Address: u.Address, + Contact: u.Contact, + Phone: u.Phone, + UnitServiceFee: &unitServiceFee, + }, nil +} + +type UserStateChangeForm struct { + Uid string `json:"uid"` + Enabled bool `json:"enabled"` +} + +type RepasswordForm struct { + VerifyCode string `json:"verifyCode"` + Username string `json:"uname"` + NewPassword string `json:"newPass"` +} From f8ef6aba98308b4688ae8b05b098caca71be1a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 2 Jun 2023 16:27:43 +0800 Subject: [PATCH 023/141] =?UTF-8?q?enhance(user):=E6=9B=B4=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=A3=80=E7=B4=A2=E4=B8=ADlike=E7=9A=84?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=96=B9=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/user.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/repository/user.go b/repository/user.go index 8d49ce6..5293bf5 100644 --- a/repository/user.go +++ b/repository/user.go @@ -271,16 +271,16 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, pattern := fmt.Sprintf("%%%s%%", *keyword) userQuery = userQuery.Where( goqu.Or( - goqu.Ex{"u.username": goqu.Op{"like": pattern}}, - goqu.Ex{"ud.name": goqu.Op{"like": pattern}}, - goqu.Ex{"ud.abbr": goqu.Op{"like": pattern}}, + goqu.I("u.username").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), ), ) countQuery = countQuery.Where( goqu.Or( - goqu.Ex{"u.username": goqu.Op{"like": pattern}}, - goqu.Ex{"ud.name": goqu.Op{"like": pattern}}, - goqu.Ex{"ud.abbr": goqu.Op{"like": pattern}}, + goqu.I("u.username").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), ), ) } @@ -428,9 +428,9 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, pattern := fmt.Sprintf("%%%s%%", *keyword) userQuery = userQuery.Where( goqu.Or( - goqu.Ex{"u.username": goqu.Op{"like": pattern}}, - goqu.Ex{"ud.name": goqu.Op{"like": pattern}}, - goqu.Ex{"ud.abbr": goqu.Op{"like": pattern}}, + goqu.I("u.username").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), ), ) } From 98f3bdec0a37610a3cd80a89d197f466eff5824c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 3 Jun 2023 11:26:36 +0800 Subject: [PATCH 024/141] =?UTF-8?q?feat(charge):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=94=A8=E6=88=B7=E5=85=85=E5=80=BC=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=83=A8=E5=88=86=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/charge.go | 101 ++++++++++++++++++++++++++ logger/logger.go | 10 +++ model/charge.go | 30 ++++++++ repository/charge.go | 167 +++++++++++++++++++++++++++++++++++++++++++ repository/user.go | 8 +-- router/router.go | 1 + service/charge.go | 121 +++++++++++++++++++++++++++++++ 7 files changed, 434 insertions(+), 4 deletions(-) create mode 100644 controller/charge.go create mode 100644 model/charge.go create mode 100644 repository/charge.go create mode 100644 service/charge.go diff --git a/controller/charge.go b/controller/charge.go new file mode 100644 index 0000000..0a7d932 --- /dev/null +++ b/controller/charge.go @@ -0,0 +1,101 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "electricity_bill_calc/response" + "electricity_bill_calc/service" + "net/http" + + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +var chargeLog = logger.Named("Handler", "Charge") + +func InitializeChargeHandlers(router *fiber.App) { + router.Get("/charge", searchCharges) + router.Post("/charge", createNewUserChargeRecord) + router.Put("/charge/:uid/:seq", modifyUserChargeState) +} + +// 检索用户的充值记录列表 +func searchCharges(c *fiber.Ctx) error { + chargeLog.Info("检索用户的充值记录列表。") + result := response.NewResult(c) + keyword := c.Query("keyword", "") + page := c.QueryInt("page", 1) + beginTime, err := model.ParseDate(c.Query("begin")) + if err != nil { + chargeLog.Error("无法解析查询起始时间。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + endTime, err := model.ParseDate(c.Query("end")) + if err != nil { + chargeLog.Error("无法解析查询结束时间。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + charges, total, err := repository.ChargeRepository.FindCharges(uint(page), &beginTime.Time, &endTime.Time, &keyword) + if err != nil { + chargeLog.Error("检索用户的充值记录列表失败。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Success( + "已经获取到符合条件的计费记录。", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{"records": charges}, + ) +} + +// 创建一条新的用户充值记录 +func createNewUserChargeRecord(c *fiber.Ctx) error { + chargeLog.Info("创建一条新的用户充值记录。") + result := response.NewResult(c) + createionForm := new(model.ChargeRecordCreationForm) + if err := c.BodyParser(createionForm); err != nil { + chargeLog.Error("无法解析创建充值记录的请求数据。", zap.Error(err)) + return result.Error(http.StatusBadRequest, err.Error()) + } + ok, err := service.ChargeService.RecordUserCharge( + createionForm.UserId, + createionForm.Fee, + createionForm.Discount, + createionForm.Amount, + createionForm.ChargeTo, + true, + ) + if err != nil { + chargeLog.Error("创建用户充值记录失败。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !ok { + chargeLog.Error("创建用户充值记录失败。") + return result.NotAccept("创建用户充值记录失败。") + } else { + return result.Success("创建用户充值记录成功, 指定用户的服务已延期。") + } +} + +// 改变用户充值记录的状态 +func modifyUserChargeState(c *fiber.Ctx) error { + chargeLog.Info("改变用户充值记录的状态。") + result := response.NewResult(c) + uid := c.Params("uid") + seq, err := c.ParamsInt("seq") + if err != nil { + chargeLog.Error("无法解析请求参数。", zap.Error(err)) + return result.Error(http.StatusBadRequest, err.Error()) + } + ok, err := service.ChargeService.CancelUserCharge(uid, int64(seq)) + if err != nil { + chargeLog.Error("取消用户充值记录失败。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if !ok { + chargeLog.Error("取消用户充值记录失败。") + return result.NotAccept("取消用户充值记录失败。") + } else { + return result.Success("取消用户充值记录成功。") + } +} diff --git a/logger/logger.go b/logger/logger.go index 53d0493..d5a9877 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,8 +1,10 @@ package logger import ( + "electricity_bill_calc/model" "os" + "github.com/shopspring/decimal" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -130,3 +132,11 @@ func With(fields ...zap.Field) *zap.Logger { func WithSugar(fields ...zap.Field) *zap.SugaredLogger { return logger.With(fields...).Sugar() } + +func DecimalField(key string, val *decimal.Decimal) zap.Field { + return zap.String(key, val.String()) +} + +func DateField(key string, val *model.Date) zap.Field { + return zap.String(key, val.Format("2006-01-02")) +} diff --git a/model/charge.go b/model/charge.go new file mode 100644 index 0000000..d3d6599 --- /dev/null +++ b/model/charge.go @@ -0,0 +1,30 @@ +package model + +import ( + "time" + + "github.com/shopspring/decimal" +) + +type UserChargeDetail struct { + Seq int64 `json:"seq"` + UserId string `json:"user_id"` + Name string `json:"name"` + Fee *decimal.Decimal `json:"fee"` + Discount *decimal.Decimal `json:"discount"` + Amount *decimal.Decimal `json:"amount"` + ChargeTo Date `json:"charge_to"` + Settled bool `json:"settled"` + SettledAt *time.Time `json:"settled_at"` + Cancelled bool `json:"cancelled"` + CancelledAt *time.Time `json:"cancelled_at"` + CreatedAt time.Time `json:"created_at"` +} + +type ChargeRecordCreationForm struct { + UserId string `json:"userId"` + Fee *decimal.Decimal `json:"fee"` + Discount *decimal.Decimal `json:"discount"` + Amount *decimal.Decimal `json:"amount"` + ChargeTo Date `json:"chargeTo"` +} diff --git a/repository/charge.go b/repository/charge.go new file mode 100644 index 0000000..d3706cb --- /dev/null +++ b/repository/charge.go @@ -0,0 +1,167 @@ +package repository + +import ( + "context" + "electricity_bill_calc/cache" + "electricity_bill_calc/config" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/tools" + "electricity_bill_calc/tools/time" + "fmt" + st "time" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "github.com/samber/lo" + "github.com/shopspring/decimal" + "go.uber.org/zap" +) + +type _ChargeRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var ChargeRepository = &_ChargeRepository{ + log: logger.Named("Repository", "Charge"), + ds: goqu.Dialect("postgres"), +} + +// 分页查询用户的充值记录 +func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *st.Time, keyword *string) ([]*model.UserChargeDetail, int64, error) { + cr.log.Info("查询用户的充值记录。", zap.Timep("beginTime", beginTime), zap.Timep("endTime", endTime), zap.Stringp("keyword", keyword), zap.Uint("page", page)) + cacheConditions := []string{ + fmt.Sprintf("%d", page), + tools.DefaultTo(keyword, ""), + tools.CondFn(func(t *st.Time) bool { return t != nil }, beginTime, beginTime.Format("2006-01-02"), "UNDEF"), + tools.CondFn(func(t *st.Time) bool { return t != nil }, endTime, endTime.Format("2006-01-02"), "UNDEF"), + } + if charges, total, err := cache.RetrievePagedSearch[[]*model.UserChargeDetail]("charges", cacheConditions...); err == nil { + cr.log.Info("从缓存中获取用户的充值记录成功。", zap.Int("count", len(*charges)), zap.Int64("total", total)) + return *charges, total, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + chargeQuery := cr.ds. + From(goqu.T("user_charge").As("c")). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))). + Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.user_id").Eq(goqu.I("u.id")))). + Select( + "c.seq", "c.user_id", "ud.name", "c.fee", "c.discount", "c.amount", "c.charge_to", + "c.settled", "c.settled_at", "c.cancelled", "c.cancelled_at", "c.created_at", + ) + countQuery := cr.ds. + From(goqu.T("user_charge").As("c")). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))). + Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.user_id").Eq(goqu.I("u.id")))). + Select(goqu.COUNT("*")) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + chargeQuery = chargeQuery.Where(goqu.Or( + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), + goqu.I("u.username").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), + goqu.I("u.username").ILike(pattern), + )) + } + + if beginTime != nil { + chargeQuery = chargeQuery.Where(goqu.I("c.charge_to").Gte(*beginTime)) + countQuery = countQuery.Where(goqu.I("c.charge_to").Gte(*beginTime)) + } + + if endTime != nil { + chargeQuery = chargeQuery.Where(goqu.I("c.charge_to").Lte(*endTime)) + countQuery = countQuery.Where(goqu.I("c.charge_to").Lte(*endTime)) + } + + chargeQuery = chargeQuery.Order(goqu.I("c.created_by").Desc()) + + currentPostion := (page - 1) * config.ServiceSettings.ItemsPageSize + chargeQuery = chargeQuery.Offset(currentPostion).Limit(config.ServiceSettings.ItemsPageSize) + + chargeSql, chargeArgs, _ := chargeQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + + var ( + charges []*model.UserChargeDetail + total int64 + ) + if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil { + cr.log.Error("查询用户的充值记录失败。", zap.Error(err)) + return make([]*model.UserChargeDetail, 0), 0, err + } + if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { + cr.log.Error("查询用户的充值记录总数失败。", zap.Error(err)) + return make([]*model.UserChargeDetail, 0), 0, err + } + cache.CachePagedSearch(charges, total, []string{"charges"}, "charges", cacheConditions...) + return charges, total, nil +} + +// 在用户充值记录中创建一条新的记录 +func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, uid string, fee, discount, amount *decimal.Decimal, chargeTo model.Date) (bool, error) { + createQuery, createArgs, _ := cr.ds. + Insert(goqu.T("user_charge")). + Cols("user_id", "fee", "discount", "amount", "charge_to", "created_at"). + Vals(goqu.Vals{uid, fee, discount, amount, chargeTo, time.Now()}). + Prepared(true).ToSQL() + + rs, err := tx.Exec(ctx, createQuery, createArgs...) + if err != nil { + cr.log.Error("创建用户充值记录失败。", zap.Error(err)) + return false, err + } + return rs.RowsAffected() > 0, nil +} + +// 撤销用户的充值记录 +func (cr _ChargeRepository) CancelCharge(tx pgx.Tx, ctx context.Context, uid string, seq int64) (bool, error) { + updateQuerySql, updateArgs, _ := cr.ds. + Update(goqu.T("user_charge")). + Set(goqu.Record{"cancelled": true, "cancelled_at": time.Now()}). + Where(goqu.I("user_id").Eq(uid), goqu.I("seq").Eq(seq)). + Prepared(true).ToSQL() + + rs, err := tx.Exec(ctx, updateQuerySql, updateArgs...) + if err != nil { + cr.log.Error("撤销用户的充值记录失败。", zap.Error(err)) + return false, err + } + return rs.RowsAffected() > 0, nil +} + +// 检索用户最近有效的服务期限 +func (cr _ChargeRepository) LatestValidChargeTo(tx pgx.Tx, ctx context.Context, uid string) (*model.Date, error) { + searchSql, searchArgs, _ := cr.ds. + From(goqu.T("user_charge")). + Select("charge_to"). + Where( + goqu.I("settled").Eq(true), + goqu.I("cancelled").Eq(false), + goqu.I("refunded").Eq(false), + goqu.I("user_id").Eq(uid), + ). + Prepared(true).ToSQL() + + var chargeTo []*model.Date + if err := pgxscan.Select(ctx, tx, &chargeTo, searchSql, searchArgs...); err != nil { + cr.log.Error("检索用户有效服务期限列表失败。", zap.Error(err)) + return nil, err + } + if len(chargeTo) == 0 { + return nil, fmt.Errorf("无法找到用户最近的有效服务期限。") + } + lastCharge := lo.MaxBy(chargeTo, func(a, b *model.Date) bool { return a.Time.After(b.Time) }) + return lastCharge, nil +} diff --git a/repository/user.go b/repository/user.go index 5293bf5..d451673 100644 --- a/repository/user.go +++ b/repository/user.go @@ -1,6 +1,7 @@ package repository import ( + "context" "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" @@ -16,6 +17,7 @@ import ( _ "github.com/doug-martin/goqu/v9/dialect/postgres" "github.com/fufuok/utils" "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" "github.com/samber/lo" "go.uber.org/zap" ) @@ -449,10 +451,8 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, } // 更新指定用户的服务有效期限 -func (ur _UserRepository) UpdateServiceExpiration(uid string, expiration stdTime.Time) (bool, error) { +func (ur _UserRepository) UpdateServiceExpiration(tx pgx.Tx, ctx context.Context, uid string, expiration stdTime.Time) (bool, error) { ur.log.Info("更新指定用户的服务有效期限。", zap.String("user id", uid)) - ctx, cancel := global.TimeoutContext() - defer cancel() userDetailUpdateQuery := ur.ds. Update("user_detail"). @@ -462,7 +462,7 @@ func (ur _UserRepository) UpdateServiceExpiration(uid string, expiration stdTime userDetailSql, userDetailParams, _ := userDetailUpdateQuery. Prepared(true).ToSQL() - if res, err := global.DB.Exec(ctx, userDetailSql, userDetailParams...); err != nil { + if res, err := tx.Exec(ctx, userDetailSql, userDetailParams...); err != nil { ur.log.Error("向数据库更新指定用户的服务有效期限失败。", zap.String("user id", uid), zap.Error(err)) return false, err } else { diff --git a/router/router.go b/router/router.go index 795d48d..def7992 100644 --- a/router/router.go +++ b/router/router.go @@ -46,6 +46,7 @@ func App() *fiber.App { controller.InitializeUserHandlers(app) controller.InitializeRegionHandlers(app) + controller.InitializeChargeHandlers(app) return app } diff --git a/service/charge.go b/service/charge.go new file mode 100644 index 0000000..fc962fc --- /dev/null +++ b/service/charge.go @@ -0,0 +1,121 @@ +package service + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "fmt" + + "github.com/shopspring/decimal" + "go.uber.org/zap" +) + +type _ChargeService struct { + log *zap.Logger +} + +var ChargeService = &_ChargeService{ + log: logger.Named("Service", "Charge"), +} + +// 创建一条新的用户充值记录,同时更新用户的服务期限 +func (cs _ChargeService) RecordUserCharge(uid string, fee, discount, amount *decimal.Decimal, chargeTo model.Date, extendExpriationIgnoringSettle bool) (bool, error) { + cs.log.Info( + "创建一条新的用户充值记录。", + zap.String("uid", uid), + logger.DecimalField("fee", fee), + logger.DecimalField("discount", discount), + logger.DecimalField("amount", amount), + logger.DateField("chargeTo", &chargeTo), + zap.Bool("extendExpriationIgnoringSettle", extendExpriationIgnoringSettle), + ) + + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + cs.log.Error("开启数据库事务失败。", zap.Error(err)) + return false, err + } + ok, err := repository.ChargeRepository.CreateChargeRecord(tx, ctx, uid, fee, discount, amount, chargeTo) + switch { + case err == nil && !ok: + cs.log.Error("未能成功创建用户充值记录", zap.Error(err)) + tx.Rollback(ctx) + return false, fmt.Errorf("未能成功创建用户充值记录") + case err != nil: + cs.log.Error("创建用户充值记录失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + if extendExpriationIgnoringSettle { + ok, err = repository.UserRepository.UpdateServiceExpiration(tx, ctx, uid, chargeTo.Time) + switch { + case err != nil: + cs.log.Error("更新用户服务期限失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + case !ok: + cs.log.Error("未能成功更新用户服务期限", zap.Error(err)) + tx.Rollback(ctx) + return false, fmt.Errorf("未能成功更新用户服务期限") + } + } + err = tx.Commit(ctx) + if err != nil { + cs.log.Error("提交数据库事务失败。", zap.Error(err)) + return false, err + } + cache.AbolishRelation("charge") + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return true, nil +} + +// 撤销用户的某一条充值记录,同时重新设置用户的服务期限 +func (cs _ChargeService) CancelUserCharge(uid string, seq int64) (bool, error) { + cs.log.Info("撤销用户的充值记录。", zap.String("uid", uid), zap.Int64("seq", seq)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + cs.log.Error("开启数据库事务失败。", zap.Error(err)) + return false, err + } + ok, err := repository.ChargeRepository.CancelCharge(tx, ctx, uid, seq) + switch { + case err == nil && !ok: + cs.log.Error("未能成功撤销用户充值记录", zap.Error(err)) + tx.Rollback(ctx) + return false, fmt.Errorf("未能成功撤销用户充值记录") + case err != nil: + cs.log.Error("撤销用户充值记录失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + if ok { + lastValidCharge, err := repository.ChargeRepository.LatestValidChargeTo(tx, ctx, uid) + if err != nil { + cs.log.Error("查询用户最近一次有效的充值记录失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + ok, err = repository.UserRepository.UpdateServiceExpiration(tx, ctx, uid, lastValidCharge.Time) + if err != nil || !ok { + cs.log.Error("更新用户服务期限失败。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + } + err = tx.Commit(ctx) + if err != nil { + cs.log.Error("提交数据库事务失败。", zap.Error(err)) + return false, err + } + cache.AbolishRelation("charge") + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return true, nil +} From c22e7e7dc0589921874cd8784896a6ac0b3aaf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 3 Jun 2023 22:48:33 +0800 Subject: [PATCH 025/141] =?UTF-8?q?feat(park):=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=9B=AD=E5=8C=BA=E5=8A=9F=E8=83=BD=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E7=9A=84=E8=BF=81=E7=A7=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/park.go | 373 ++++++++++++++++++++++++++++++++ model/enums.go | 51 +++++ model/park.go | 32 +++ model/park_building.go | 14 ++ repository/park.go | 473 +++++++++++++++++++++++++++++++++++++++++ router/router.go | 1 + tools/utils.go | 25 +++ vo/park.go | 67 ++++++ vo/shares.go | 5 + 9 files changed, 1041 insertions(+) create mode 100644 controller/park.go create mode 100644 model/enums.go create mode 100644 model/park.go create mode 100644 model/park_building.go create mode 100644 repository/park.go create mode 100644 vo/park.go create mode 100644 vo/shares.go diff --git a/controller/park.go b/controller/park.go new file mode 100644 index 0000000..e959cd2 --- /dev/null +++ b/controller/park.go @@ -0,0 +1,373 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "electricity_bill_calc/repository" + "electricity_bill_calc/response" + "electricity_bill_calc/security" + "electricity_bill_calc/vo" + "net/http" + + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +var parkLog = logger.Named("Handler", "Park") + +func InitializeParkHandlers(router *fiber.App) { + router.Get("/park", security.EnterpriseAuthorize, listParksBelongsToCurrentUser) + router.Post("/park", security.EnterpriseAuthorize, createPark) + router.Get("/park/belongs/:uid", security.OPSAuthorize, listParksBelongsTo) + router.Get("/park/:pid", security.EnterpriseAuthorize, fetchParkDetail) + router.Put("/park/:pid", security.EnterpriseAuthorize, modifySpecificPark) + router.Delete("/park/:pid", security.EnterpriseAuthorize, deleteSpecificPark) + router.Put("/park/:pid/enabled", security.EnterpriseAuthorize, modifyParkEnabling) + router.Get("/park/:pid/building", security.EnterpriseAuthorize, listBuildingsBelongsToPark) + router.Post("/park/:pid/building", security.EnterpriseAuthorize, createBuildingInPark) + router.Put("/park/:pid/building/:bid", security.EnterpriseAuthorize, modifySpecificBuildingInPark) + router.Delete("/park/:pid/building/:bid", security.EnterpriseAuthorize, deletedParkBuilding) + router.Put("/park/:pid/building/:bid/enabled", security.EnterpriseAuthorize, modifyParkBuildingEnabling) +} + +// 列出隶属于当前用户的全部园区 +func listParksBelongsToCurrentUser(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("列出当前用的全部园区,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + parkLog.Info("列出当前用户下的全部园区", zap.String("user id", session.Uid)) + parks, err := repository.ParkRepository.RetrieveParkbelongs(session.Uid) + if err != nil { + parkLog.Error("无法获取园区列表。", zap.String("user id", session.Uid)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Success("已获取到指定用户的下的园区", fiber.Map{"parks": parks}) +} + +// 列出隶属于指定用户的全部园区 +func listParksBelongsTo(c *fiber.Ctx) error { + result := response.NewResult(c) + userId := c.Params("userId") + parkLog.Info("列出指定用户下的全部园区", zap.String("user id", userId)) + parks, err := repository.ParkRepository.RetrieveParkbelongs(userId) + if err != nil { + parkLog.Error("无法获取园区列表。", zap.String("user id", userId)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Success("已获取到指定用户的下的园区", fiber.Map{"parks": parks}) +} + +// 获取指定园区的详细信息 +func fetchParkDetail(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + parkLog.Info("获取指定园区的详细信息", zap.String("park id", parkId)) + park, err := repository.ParkRepository.RetrieveParkDetail(parkId) + if err != nil { + parkLog.Error("无法获取园区信息。", zap.String("park id", parkId)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Success("已获取到指定园区的详细信息", fiber.Map{"park": park}) +} + +// 创建一个新的园区 +func createPark(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("创建一个新的园区,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + parkLog.Info("创建一个新的园区", zap.String("user id", session.Uid)) + creationForm := new(vo.ParkInformationForm) + if err := c.BodyParser(creationForm); err != nil { + parkLog.Error("无法解析园区表单数据。", zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + park, err := creationForm.TryIntoPark() + if err != nil { + parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + ok, err := repository.ParkRepository.CreatePark(session.Uid, park) + switch { + case err == nil && !ok: + parkLog.Error("无法创建新的园区。", zap.String("user id", session.Uid)) + return result.NotAccept("无法创建新的园区。") + case err != nil: + parkLog.Error("无法创建新的园区。", zap.String("user id", session.Uid), zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + return result.Created("已创建一个新的园区") +} + +// 修改指定园区的信息 +func modifySpecificPark(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("修改指定园区的信息,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + parkForm := new(vo.ParkInformationForm) + if err := c.BodyParser(parkForm); err != nil { + parkLog.Error("无法解析园区表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + park, err := parkForm.TryIntoPark() + if err != nil { + parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + ok, err = repository.ParkRepository.UpdatePark(parkId, park) + switch { + case err == nil && !ok: + parkLog.Error("无法更新园区信息。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return result.NotAccept("无法更新园区信息。") + 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()) + } + return result.Updated("已获取到指定园区的详细信息") +} + +// 修改指定园区的可用性 +func modifyParkEnabling(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("修改指定园区的可用性,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + stateForm := new(vo.StateForm) + if err := c.BodyParser(stateForm); err != nil { + parkLog.Error("无法解析园区表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + ok, err = repository.ParkRepository.EnablingPark(parkId, stateForm.Enabled) + switch { + case err == nil && !ok: + parkLog.Error("无法更新园区可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return result.NotAccept("无法更新园区可用性。") + 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()) + } + return result.Updated("已获取到指定园区的详细信息") +} + +// 删除指定的园区 +func deleteSpecificPark(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("删除指定的园区,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + ok, err = repository.ParkRepository.DeletePark(parkId) + switch { + case err == nil && !ok: + parkLog.Error("无法删除园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return result.NotAccept("无法删除园区。") + 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()) + } + return result.Deleted("已删除指定的园区") +} + +// 列出指定园区中已经登记的建筑 +func listBuildingsBelongsToPark(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("列出指定园区中已经登记的建筑,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + buildings, err := repository.ParkRepository.RetrieveParkBuildings(parkId) + if 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()) + } + return result.Success("已获取到指定园区中的建筑列表", fiber.Map{"buildings": buildings}) +} + +// 在指定园区中创建一个新的建筑 +func createBuildingInPark(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("在指定园区中创建一个新的建筑,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + buildingForm := new(vo.ParkBuildingInformationForm) + if err := c.BodyParser(buildingForm); err != nil { + parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + ok, err = repository.ParkRepository.CreateParkBuilding(parkId, buildingForm.Name, &buildingForm.Floors) + switch { + case err == nil && !ok: + parkLog.Error("无法创建新的建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return result.NotAccept("无法创建新的建筑。") + 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()) + } + return result.Created("已创建一个新的建筑") +} + +// 修改指定园区中的指定建筑的信息 +func modifySpecificBuildingInPark(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + buildingId := c.Params("bid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("修改指定园区中的指定建筑的信息,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + buildingForm := new(vo.ParkBuildingInformationForm) + if err := c.BodyParser(buildingForm); err != nil { + parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + ok, err = repository.ParkRepository.ModifyParkBuilding(buildingId, parkId, buildingForm.Name, &buildingForm.Floors) + switch { + case err == nil && !ok: + parkLog.Error("无法更新建筑信息。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return result.NotAccept("无法更新建筑信息。") + 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()) + } + return result.Updated("已更新指定建筑的信息") +} + +// 修改指定园区中指定建筑的可用性 +func modifyParkBuildingEnabling(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + buildingId := c.Params("bid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("修改指定园区中指定建筑的可用性,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + stateForm := new(vo.StateForm) + if err := c.BodyParser(stateForm); err != nil { + parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return result.NotAccept(err.Error()) + } + ok, err = repository.ParkRepository.EnablingParkBuilding(buildingId, parkId, stateForm.Enabled) + switch { + case err == nil && !ok: + parkLog.Error("无法更新建筑可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return result.NotAccept("无法更新建筑可用性。") + 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()) + } + return result.Updated("已更新指定建筑的可用性") +} + +// 删除指定园区中的指定建筑 +func deletedParkBuilding(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Params("pid") + buildingId := c.Params("bid") + session, err := _retreiveSession(c) + if err != nil { + parkLog.Error("删除指定园区中的指定建筑,无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + ok, err = repository.ParkRepository.DeleteParkBuilding(buildingId, parkId) + switch { + case err == nil && !ok: + parkLog.Error("无法删除建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return result.NotAccept("无法删除建筑。") + 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()) + } + return result.Deleted("已删除指定的建筑") +} diff --git a/model/enums.go b/model/enums.go new file mode 100644 index 0000000..bdbb9c1 --- /dev/null +++ b/model/enums.go @@ -0,0 +1,51 @@ +package model + +const ( + ELECTRICITY_CATE_TWO_PART int16 = iota + ELECTRICITY_CATE_UNITARY_PV + ELECTRICITY_CATE_FULL_PV +) + +const ( + METER_TYPE_UNITARY int16 = iota + METER_TYPE_PV +) + +const ( + METER_INSTALLATION_TENEMENT int16 = iota + METER_INSTALLATION_PARK + METER_INSTALLATION_POOLING +) + +const ( + PRICING_POLICY_CONSUMPTION int16 = iota + PRICING_POLICY_ALL +) + +const ( + POOLING_MODE_NONE int16 = iota + POOLING_MODE_CONSUMPTION + POOLING_MODE_AREA +) + +const ( + PAYMENT_CASH int16 = iota + PAYMENT_BANK_CARD + PAYMENT_ALIPAY + PAYMENT_WECHAT + PAYMENT_UNION_PAY + PAYMENT_OTHER int16 = 99 +) + +const ( + METER_TELEMETER_HYBRID int16 = iota + METER_TELEMETER_AUTOMATIC + METER_TELEMETER_MANUAL +) + +const ( + RETRY_INTERVAL_ALGORITHM_EXPONENTIAL_BACKOFF int16 = iota + RETRY_INTERVAL_ALGORITHM_DOUBLE_LINEAR_BACKOFF + RETRY_INTERVAL_ALGORITHM_TRIPLE_LINEAR_BACKOFF + RETRY_INTERVAL_ALGORITHM_FIXED +) diff --git a/model/park.go b/model/park.go new file mode 100644 index 0000000..c020948 --- /dev/null +++ b/model/park.go @@ -0,0 +1,32 @@ +package model + +import ( + "time" + + "github.com/shopspring/decimal" +) + +type Park struct { + Id string `json:"id"` + UserId string `json:"userId"` + Name string `json:"name"` + Area decimal.NullDecimal `json:"area"` + TenementQuantity decimal.NullDecimal `json:"tenementQuantity"` + Capacity decimal.NullDecimal `json:"capacity"` + Category int16 `json:"category"` + MeterType int16 `json:"meter04kvType"` + PricePolicy int16 `json:"pricePolicy"` + BasicPooled int16 `json:"basicPooled"` + AdjustPooled int16 `json:"adjustPooled"` + LossPooled int16 `json:"lossPooled"` + PublicPooled int16 `json:"publicPooled"` + TaxRate decimal.NullDecimal `json:"taxRate"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"createdAt"` + LastModifiedAt time.Time `json:"lastModifiedAt"` + DeletedAt *time.Time `json:"deletedAt"` +} diff --git a/model/park_building.go b/model/park_building.go new file mode 100644 index 0000000..339c9ed --- /dev/null +++ b/model/park_building.go @@ -0,0 +1,14 @@ +package model + +import "time" + +type ParkBuilding struct { + Id string `json:"id"` + Park string `json:"parkId" db:"park_id"` + Name string `json:"name"` + Floors *string `json:"floors"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"createdAt"` + LastModifiedAt time.Time `json:"lastModifiedAt"` + DeletedAt *time.Time `json:"deletedAt"` +} diff --git a/repository/park.go b/repository/park.go new file mode 100644 index 0000000..eb51cc1 --- /dev/null +++ b/repository/park.go @@ -0,0 +1,473 @@ +package repository + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/tools" + "electricity_bill_calc/tools/serial" + "electricity_bill_calc/tools/time" + "fmt" + "strings" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _ParkRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var ParkRepository = _ParkRepository{ + log: logger.Named("Repository", "Park"), + ds: goqu.Dialect("postgres"), +} + +// 列出指定用户下的所有园区 +func (pr _ParkRepository) ListAllParks(uid string) ([]*model.Park, error) { + pr.log.Info("列出指定用户下的所有园区", zap.String("uid", uid)) + cacheConditions := []string{ + uid, + } + if parks, err := cache.RetrieveSearch[[]*model.Park]("park_belongs", cacheConditions...); err == nil && parks != nil { + pr.log.Info("已经从缓存获取到了指定用户下的所有园区。") + return *parks, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var parks []*model.Park + parkQuerySql, parkParams, _ := pr.ds. + From("park"). + Where( + goqu.I("user_id").Eq(uid), + goqu.I("deleted_at").IsNull(), + ). + Order(goqu.I("created_at").Asc()). + Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &parks, parkQuerySql, parkParams...); err != nil { + pr.log.Error("列出指定用户下的所有园区失败!", zap.Error(err)) + return make([]*model.Park, 0), err + } + + cache.CacheSearch(parks, []string{"park", fmt.Sprintf("park:%s", uid)}, "park_belongs", cacheConditions...) + + return parks, nil +} + +// 检查并确定指定园区的归属情况 +func (pr _ParkRepository) IsParkBelongs(pid, uid string) (bool, error) { + pr.log.Info("检查并确定指定园区的归属情况", zap.String("pid", pid), zap.String("uid", uid)) + cacheConditions := []string{ + pid, "belongs", uid, + } + if exists, err := cache.CheckExists("park", cacheConditions...); err == nil && exists { + pr.log.Info("已经从缓存获取到了指定园区的归属情况。") + return true, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var count int64 + parkQuerySql, parkParams, _ := pr.ds. + From("park"). + Select(goqu.COUNT("*")). + Where( + goqu.I("id").Eq(pid), + goqu.I("user_id").Eq(uid), + ). + Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &count, parkQuerySql, parkParams...); err != nil { + pr.log.Error("检查并确定指定园区的归属情况失败!", zap.Error(err)) + return false, err + } + + if count > 0 { + cache.CacheExists([]string{"park", fmt.Sprintf("park:%s", uid)}, "park", cacheConditions...) + } + + return count > 0, nil +} + +// 创建一个属于指定用户的新园区。该创建功能不会对园区的名称进行检查。 +func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, error) { + pr.log.Info("创建一个属于指定用户的新园区", zap.String("ownerId", ownerId)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + createSql, createArgs, _ := pr.ds. + Insert("park"). + Cols( + "id", "user_id", "name", "abbr", "area", "tenement_quantity", "capacity", "category", + "meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate", + "basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at", + ). + Vals(goqu.Vals{ + serial.GeneratePrefixedUniqueSerialString("P"), + ownerId, park.Name, tools.PinyinAbbr(park.Name), + park.Area, park.TenementQuantity, park.Capacity, park.Category, + park.MeterType, park.Region, park.Address, park.Contact, park.Phone, park.Enabled, park.PricePolicy, park.TaxRate, + park.BasicPooled, park.AdjustPooled, park.LossPooled, park.PublicPooled, timeNow, timeNow, + }). + Prepared(true).ToSQL() + rs, err := global.DB.Exec(ctx, createSql, createArgs...) + if err != nil { + pr.log.Error("创建一个属于指定用户的新园区失败!", zap.Error(err)) + return false, err + } + + cache.AbolishRelation("park") + + return rs.RowsAffected() > 0, nil +} + +// 获取指定园区的详细信息 +func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) { + pr.log.Info("获取指定园区的详细信息", zap.String("pid", pid)) + if park, err := cache.RetrieveEntity[model.Park]("park", pid); err == nil && park != nil { + pr.log.Info("已经从缓存获取到了指定园区的详细信息。") + return park, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var park model.Park + parkSql, parkArgs, _ := pr.ds. + From("park"). + Where(goqu.I("id").Eq(pid)). + Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &park, parkSql, parkArgs...); err != nil { + pr.log.Error("获取指定园区的详细信息失败!", zap.Error(err)) + return nil, err + } + + cache.CacheEntity(park, []string{"park", fmt.Sprintf("park:%s", pid)}, "park", pid) + + return &park, nil +} + +// 获取园区对应的用户ID +func (pr _ParkRepository) RetrieveParkbelongs(pid string) (string, error) { + pr.log.Info("获取园区对应的用户ID", zap.String("pid", pid)) + if uid, err := cache.RetrieveEntity[string]("park_belongs", pid); err == nil && uid != nil { + pr.log.Info("已经从缓存获取到了园区对应的用户ID。") + return *uid, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var uid string + parkSql, parkArgs, _ := pr.ds. + From("park"). + Select(goqu.I("user_id")). + Where(goqu.I("id").Eq(pid)). + Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &uid, parkSql, parkArgs...); err != nil { + pr.log.Error("获取园区对应的用户ID失败!", zap.Error(err)) + return "", err + } + + cache.CacheEntity(uid, []string{"park", fmt.Sprintf("park:%s", pid)}, "park_belongs", pid) + + return uid, nil +} + +// 更新指定园区的信息 +func (pr _ParkRepository) UpdatePark(pid string, park *model.Park) (bool, error) { + pr.log.Info("更新指定园区的信息", zap.String("pid", pid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + updateSql, updateArgs, _ := pr.ds. + Update("park"). + Set(goqu.Record{ + "name": park.Name, + "abbr": tools.PinyinAbbr(park.Name), + "area": park.Area, + "tenement_quantity": park.TenementQuantity, + "capacity": park.Capacity, + "category": park.Category, + "meter_04kv_type": park.MeterType, + "region": park.Region, + "address": park.Address, + "contact": park.Contact, + "phone": park.Phone, + "price_policy": park.PricePolicy, + "tax_rate": park.TaxRate, + "basic_pooled": park.BasicPooled, + "adjust_pooled": park.AdjustPooled, + "loss_pooled": park.LossPooled, + "public_pooled": park.PublicPooled, + "last_modified_at": timeNow, + }). + Where(goqu.I("id").Eq(pid)). + Prepared(true).ToSQL() + + ok, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + pr.log.Error("更新指定园区的信息失败!", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation("park") + cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) + } + return ok.RowsAffected() > 0, nil +} + +// 设定园区的可用状态 +func (pr _ParkRepository) EnablingPark(pid string, enabled bool) (bool, error) { + pr.log.Info("设定园区的可用状态", zap.String("pid", pid), zap.Bool("enabled", enabled)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + updateSql, updateArgs, _ := pr.ds. + Update("park"). + Set(goqu.Record{ + "enabled": enabled, + "last_modified_at": timeNow, + }). + Where(goqu.I("id").Eq(pid)). + Prepared(true).ToSQL() + + ok, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + pr.log.Error("设定园区的可用状态失败!", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation("park") + cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) + } + return ok.RowsAffected() > 0, nil +} + +// 删除指定园区(软删除) +func (pr _ParkRepository) DeletePark(pid string) (bool, error) { + pr.log.Info("删除指定园区(软删除)", zap.String("pid", pid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + updateSql, updateArgs, _ := pr.ds. + Update("park"). + Set(goqu.Record{ + "deleted_at": timeNow, + "last_modified_at": timeNow, + }). + Where(goqu.I("id").Eq(pid)). + Prepared(true).ToSQL() + + ok, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + pr.log.Error("删除指定园区(软删除)失败!", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation("park") + cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) + } + return ok.RowsAffected() > 0, nil +} + +// 检索给定的园区详细信息列表 +func (pr _ParkRepository) RetrieveParks(pids []string) ([]*model.Park, error) { + pr.log.Info("检索给定的园区详细信息列表", zap.Strings("pids", pids)) + if len(pids) == 0 { + pr.log.Info("给定要检索的园区ID列表为空,执行快速返回。") + return make([]*model.Park, 0), nil + } + cacheConditions := []string{strings.Join(pids, ",")} + if parks, err := cache.RetrieveSearch[[]*model.Park]("park", cacheConditions...); err == nil && parks != nil { + pr.log.Info("已经从缓存获取到了给定的园区详细信息列表。") + return *parks, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var parks []*model.Park + parkSql, parkArgs, _ := pr.ds. + From("park"). + Where(goqu.I("id").In(pids)). + Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &parks, parkSql, parkArgs...); err != nil { + pr.log.Error("检索给定的园区详细信息列表失败!", zap.Error(err)) + return nil, err + } + + cache.CacheSearch(parks, []string{"park", fmt.Sprintf("park:%s", strings.Join(pids, ":"))}, "park", cacheConditions...) + + return parks, nil +} + +// 获取指定园区中的建筑 +func (pr _ParkRepository) RetrieveParkBuildings(pid string) ([]*model.ParkBuilding, error) { + pr.log.Info("获取指定园区中的建筑", zap.String("pid", pid)) + if buildings, err := cache.RetrieveSearch[[]*model.ParkBuilding]("park_building", pid); err == nil && buildings != nil { + pr.log.Info("已经从缓存获取到了指定园区中的建筑。") + return *buildings, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var buildings []*model.ParkBuilding + buildingSql, buildingArgs, _ := pr.ds. + From("park_building"). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("deleted_at").IsNull(), + ). + Order(goqu.I("created_at").Asc()). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &buildings, buildingSql, buildingArgs...); err != nil { + pr.log.Error("获取指定园区中的建筑失败!", zap.Error(err)) + return nil, err + } + + cache.CacheSearch(buildings, []string{"park_building", fmt.Sprintf("park_building:%s", pid)}, "park_building", pid) + + return buildings, nil +} + +// 在指定园区中创建一个新建筑 +func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (bool, error) { + pr.log.Info("在指定园区中创建一个新建筑", zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + createSql, createArgs, _ := pr.ds. + Insert("park_building"). + Cols( + "id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at", + ). + Vals(goqu.Vals{ + serial.GeneratePrefixedUniqueSerialString("B"), + pid, name, floor, true, timeNow, timeNow, + }). + Prepared(true).ToSQL() + + rs, err := global.DB.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) { + pr.log.Info("修改指定园区中指定建筑的信息", zap.String("id", id), zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + updateSql, updateArgs, _ := pr.ds. + Update("park_building"). + Set(goqu.Record{ + "name": name, + "floors": floor, + "last_modified_at": timeNow, + }). + Where( + goqu.I("id").Eq(id), + goqu.I("park_id").Eq(pid), + ). + Prepared(true).ToSQL() + + rs, err := global.DB.Exec(ctx, updateSql, updateArgs...) + 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) EnablingParkBuilding(id, pid string, enabled bool) (bool, error) { + pr.log.Info("修改指定建筑的可以状态", zap.String("id", id), zap.String("pid", pid), zap.Bool("enabled", enabled)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + updateSql, updateArgs, _ := pr.ds. + Update("park_building"). + Set(goqu.Record{ + "enabled": enabled, + "last_modified_at": timeNow, + }). + Where( + goqu.I("id").Eq(id), + goqu.I("park_id").Eq(pid), + ). + Prepared(true).ToSQL() + + rs, err := global.DB.Exec(ctx, updateSql, updateArgs...) + 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) DeleteParkBuilding(id, pid string) (bool, error) { + pr.log.Info("删除指定建筑(软删除)", zap.String("id", id), zap.String("pid", pid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + timeNow := time.Now() + updateSql, updateArgs, _ := pr.ds. + Update("park_building"). + Set(goqu.Record{ + "deleted_at": timeNow, + "last_modified_at": timeNow, + }). + Where( + goqu.I("id").Eq(id), + goqu.I("park_id").Eq(pid), + ). + Prepared(true).ToSQL() + + rs, err := global.DB.Exec(ctx, updateSql, updateArgs...) + 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 +} diff --git a/router/router.go b/router/router.go index def7992..73e779f 100644 --- a/router/router.go +++ b/router/router.go @@ -47,6 +47,7 @@ func App() *fiber.App { controller.InitializeUserHandlers(app) controller.InitializeRegionHandlers(app) controller.InitializeChargeHandlers(app) + controller.InitializeParkHandlers(app) return app } diff --git a/tools/utils.go b/tools/utils.go index a0333d9..544f904 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -7,6 +7,7 @@ import ( "github.com/mozillazg/go-pinyin" "github.com/samber/lo" + "github.com/shopspring/decimal" ) func ContainsInsensitive(element string, slice []string) bool { @@ -86,3 +87,27 @@ func CondFn[T, R any](exprFn func(val T) bool, value T, trueValue, falseValue R) func CondOr[T any](exprFn func(val T) bool, value, elseValue T) T { return CondFn(exprFn, value, value, elseValue) } + +// 将指定的字符串指针解析为一个可空的`decimal.NullDecimal`类型的值。 +func NewNullDecimalFromString(val *string) (decimal.NullDecimal, error) { + if val == nil { + return decimal.NullDecimal{Valid: false}, nil + } + nd, err := decimal.NewFromString(*val) + if err != nil { + return decimal.NullDecimal{Valid: false}, err + } + return decimal.NullDecimal{Decimal: nd, Valid: true}, nil +} + +// 将指定的字符串指针解析为一个`decimal.Decimal`类型的值,必须提供一个默认值,以用来替换解析失败以及空指针的情况。 +func NewDecimalFromString(val *string, defaultValue decimal.Decimal) decimal.Decimal { + if val == nil { + return defaultValue + } + nd, err := decimal.NewFromString(*val) + if err != nil { + return defaultValue + } + return nd +} diff --git a/vo/park.go b/vo/park.go new file mode 100644 index 0000000..a557f4b --- /dev/null +++ b/vo/park.go @@ -0,0 +1,67 @@ +package vo + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/tools" +) + +type ParkInformationForm struct { + Name string `json:"name"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + Area *string `json:"area"` + Capacity *string `json:"capacity"` + TenementQuantity *string `json:"tenement"` + TaxRate *string `json:"taxRate"` + Category int16 `json:"category"` + MeterType int16 `json:"submeter"` + PricePolicy int16 `json:"pricePolicy"` + BasicPooled int16 `json:"basicDiluted"` + AdjustPooled int16 `json:"adjustDiluted"` + LossPooled int16 `json:"lossDiluted"` + PublicPooled int16 `json:"publicDiluted"` +} + +func (pcf ParkInformationForm) TryIntoPark() (*model.Park, error) { + area, err := tools.NewNullDecimalFromString(pcf.Area) + if err != nil { + return nil, err + } + tenementQuantity, err := tools.NewNullDecimalFromString(pcf.TenementQuantity) + if err != nil { + return nil, err + } + capacity, err := tools.NewNullDecimalFromString(pcf.Capacity) + if err != nil { + return nil, err + } + taxRate, err := tools.NewNullDecimalFromString(pcf.TaxRate) + if err != nil { + return nil, err + } + return &model.Park{ + Name: pcf.Name, + Region: pcf.Region, + Address: pcf.Address, + Contact: pcf.Contact, + Phone: pcf.Phone, + Area: area, + TenementQuantity: tenementQuantity, + Capacity: capacity, + Category: pcf.Category, + MeterType: pcf.MeterType, + PricePolicy: pcf.PricePolicy, + BasicPooled: pcf.BasicPooled, + AdjustPooled: pcf.AdjustPooled, + LossPooled: pcf.LossPooled, + PublicPooled: pcf.PublicPooled, + TaxRate: taxRate, + }, nil +} + +type ParkBuildingInformationForm struct { + Name string `json:"name"` + Floors string `json:"floors"` +} diff --git a/vo/shares.go b/vo/shares.go new file mode 100644 index 0000000..92d6f95 --- /dev/null +++ b/vo/shares.go @@ -0,0 +1,5 @@ +package vo + +type StateForm struct { + Enabled bool `json:"enabled"` +} From 85f4d04a7faad8b999c81deca37c352603a51a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 5 Jun 2023 21:53:05 +0800 Subject: [PATCH 026/141] =?UTF-8?q?refactor(types):=E5=B0=86=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E6=97=B6=E9=97=B4=E7=B1=BB=E5=9E=8B=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E5=88=B0=E5=85=AC=E5=85=B1=E7=9A=84=E7=B1=BB=E5=9E=8B=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=8C=85=E4=B8=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/charge.go | 5 +- logger/logger.go | 17 ++++-- main.go | 3 +- model/charge.go | 5 +- model/types.go | 100 ----------------------------------- model/user.go | 21 ++++---- repository/charge.go | 9 ++-- service/charge.go | 4 +- types/date.go | 123 +++++++++++++++++++++++++++++++++++++++++++ types/datetime.go | 118 +++++++++++++++++++++++++++++++++++++++++ vo/user.go | 6 ++- 11 files changed, 285 insertions(+), 126 deletions(-) delete mode 100644 model/types.go create mode 100644 types/date.go create mode 100644 types/datetime.go diff --git a/controller/charge.go b/controller/charge.go index 0a7d932..2425588 100644 --- a/controller/charge.go +++ b/controller/charge.go @@ -6,6 +6,7 @@ import ( "electricity_bill_calc/repository" "electricity_bill_calc/response" "electricity_bill_calc/service" + "electricity_bill_calc/types" "net/http" "github.com/gofiber/fiber/v2" @@ -26,12 +27,12 @@ func searchCharges(c *fiber.Ctx) error { result := response.NewResult(c) keyword := c.Query("keyword", "") page := c.QueryInt("page", 1) - beginTime, err := model.ParseDate(c.Query("begin")) + beginTime, err := types.ParseDate(c.Query("begin")) if err != nil { chargeLog.Error("无法解析查询起始时间。", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } - endTime, err := model.ParseDate(c.Query("end")) + endTime, err := types.ParseDate(c.Query("end")) if err != nil { chargeLog.Error("无法解析查询结束时间。", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) diff --git a/logger/logger.go b/logger/logger.go index d5a9877..bf872da 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,7 +1,7 @@ package logger import ( - "electricity_bill_calc/model" + "electricity_bill_calc/types" "os" "github.com/shopspring/decimal" @@ -137,6 +137,17 @@ func DecimalField(key string, val *decimal.Decimal) zap.Field { return zap.String(key, val.String()) } -func DateField(key string, val *model.Date) zap.Field { - return zap.String(key, val.Format("2006-01-02")) +func NullDecimalField(key string, val *decimal.NullDecimal) zap.Field { + if val.Valid { + return DecimalField(key, &val.Decimal) + } + return zap.String(key, "null") +} + +func DateField(key string, val *types.Date) zap.Field { + return val.Log(key) +} + +func DateTimeField(key string, val *types.DateTime) zap.Field { + return val.Log(key) } diff --git a/main.go b/main.go index 3363cc3..ebe27c6 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "electricity_bill_calc/repository" "electricity_bill_calc/router" "electricity_bill_calc/service" + "electricity_bill_calc/types" "fmt" "time" @@ -54,7 +55,7 @@ func intializeSingularity() error { return nil } singularityId := "000" - singularityExpires, err := model.ParseDate("2099-12-31") + singularityExpires, err := types.ParseDate("2099-12-31") if err != nil { l.Error("奇点用户账号过期时间解析失败。", zap.Error(err)) return fmt.Errorf("奇点用户账号过期时间解析失败: %w", err) diff --git a/model/charge.go b/model/charge.go index d3d6599..27bdbbd 100644 --- a/model/charge.go +++ b/model/charge.go @@ -1,6 +1,7 @@ package model import ( + "electricity_bill_calc/types" "time" "github.com/shopspring/decimal" @@ -13,7 +14,7 @@ type UserChargeDetail struct { Fee *decimal.Decimal `json:"fee"` Discount *decimal.Decimal `json:"discount"` Amount *decimal.Decimal `json:"amount"` - ChargeTo Date `json:"charge_to"` + ChargeTo types.Date `json:"charge_to"` Settled bool `json:"settled"` SettledAt *time.Time `json:"settled_at"` Cancelled bool `json:"cancelled"` @@ -26,5 +27,5 @@ type ChargeRecordCreationForm struct { Fee *decimal.Decimal `json:"fee"` Discount *decimal.Decimal `json:"discount"` Amount *decimal.Decimal `json:"amount"` - ChargeTo Date `json:"chargeTo"` + ChargeTo types.Date `json:"chargeTo"` } diff --git a/model/types.go b/model/types.go deleted file mode 100644 index 7a74be8..0000000 --- a/model/types.go +++ /dev/null @@ -1,100 +0,0 @@ -package model - -import ( - "database/sql" - "database/sql/driver" - "encoding/json" - "fmt" - "time" - - et "electricity_bill_calc/tools/time" -) - -type Date struct { - time.Time -} - -func NewDate(t time.Time) Date { - t = t.In(et.Loc) - return Date{ - Time: time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, et.Loc), - } -} - -func SpecificDate(year int, month time.Month, date int) Date { - return NewDate(et.Time(year, month, date, 0, 0, 0, 0)) -} - -func NewEmptyDate() Date { - return Date{ - Time: time.Time{}.In(et.Loc), - } -} - -func ParseDate(t string) (Date, error) { - d, err := time.ParseInLocation("2006-01-02", t, et.Loc) - if err != nil { - return NewEmptyDate(), fmt.Errorf("unable to parse given time, %w", err) - } - return Date{ - Time: d, - }, nil -} - -func (d Date) IsEmpty() bool { - return d.Time.IsZero() -} - -func (d Date) Format(fmt string) string { - return d.Time.Format(fmt) -} - -func (d Date) ToString() string { - return d.Time.Format("2006-01-02") -} - -var _ driver.Valuer = (*Date)(nil) - -func (d Date) Value() (driver.Value, error) { - return d.In(et.Loc).Format("2006-01-02"), nil -} - -var _ sql.Scanner = (*Date)(nil) - -// Scan scans the time parsing it if necessary using timeFormat. -func (d *Date) Scan(src interface{}) (err error) { - switch src := src.(type) { - case time.Time: - *d = NewDate(src) - return nil - case string: - d.Time, err = time.ParseInLocation("2006-01-02", src, et.Loc) - return err - case []byte: - d.Time, err = time.ParseInLocation("2006-01-02", string(src), et.Loc) - return err - case nil: - d.Time = time.Time{} - return nil - default: - return fmt.Errorf("unsupported data type: %T", src) - } -} - -var _ json.Marshaler = (*Date)(nil) - -func (d Date) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Time.Format("2006-01-02")) -} - -var _ json.Unmarshaler = (*Date)(nil) - -func (d *Date) UnmarshalJSON(raw []byte) error { - var s string - err := json.Unmarshal(raw, &s) - if err != nil { - return fmt.Errorf("unable to unmarshal value, %w", err) - } - d.Time, err = time.ParseInLocation("2006-01-02", s, et.Loc) - return err -} diff --git a/model/user.go b/model/user.go index d79edd2..672205e 100644 --- a/model/user.go +++ b/model/user.go @@ -1,6 +1,7 @@ package model import ( + "electricity_bill_calc/types" "time" "github.com/shopspring/decimal" @@ -13,14 +14,14 @@ const ( ) type ManagementAccountCreationForm struct { - Id *string `json:"id"` - Username string `json:"username"` - Name string `json:"name"` - Contact *string `json:"contact"` - Phone *string `json:"phone"` - Type int16 `json:"type"` - Enabled bool `json:"enabled"` - Expires Date `json:"expires"` + Id *string `json:"id"` + Username string `json:"username"` + Name string `json:"name"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + Type int16 `json:"type"` + Enabled bool `json:"enabled"` + Expires types.Date `json:"expires"` } func (m ManagementAccountCreationForm) IntoUser() *User { @@ -83,7 +84,7 @@ type UserDetail struct { Contact *string `json:"contact"` Phone *string `json:"phone"` UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"` - ServiceExpiration Date `json:"serviceExpiration"` + ServiceExpiration types.Date `json:"serviceExpiration"` CreatedAt time.Time `json:"createdAt"` CreatedBy *string `json:"createdBy"` LastModifiedAt time.Time `json:"lastModifiedAt"` @@ -105,7 +106,7 @@ type UserWithDetail struct { Contact *string `json:"contact"` Phone *string `json:"phone"` UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"` - ServiceExpiration Date `json:"serviceExpiration"` + ServiceExpiration types.Date `json:"serviceExpiration"` CreatedAt time.Time `json:"createdAt"` CreatedBy *string `json:"createdBy"` LastModifiedAt time.Time `json:"lastModifiedAt"` diff --git a/repository/charge.go b/repository/charge.go index d3706cb..1908913 100644 --- a/repository/charge.go +++ b/repository/charge.go @@ -9,6 +9,7 @@ import ( "electricity_bill_calc/model" "electricity_bill_calc/tools" "electricity_bill_calc/tools/time" + "electricity_bill_calc/types" "fmt" st "time" @@ -110,7 +111,7 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *st.Time, } // 在用户充值记录中创建一条新的记录 -func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, uid string, fee, discount, amount *decimal.Decimal, chargeTo model.Date) (bool, error) { +func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, uid string, fee, discount, amount *decimal.Decimal, chargeTo types.Date) (bool, error) { createQuery, createArgs, _ := cr.ds. Insert(goqu.T("user_charge")). Cols("user_id", "fee", "discount", "amount", "charge_to", "created_at"). @@ -142,7 +143,7 @@ func (cr _ChargeRepository) CancelCharge(tx pgx.Tx, ctx context.Context, uid str } // 检索用户最近有效的服务期限 -func (cr _ChargeRepository) LatestValidChargeTo(tx pgx.Tx, ctx context.Context, uid string) (*model.Date, error) { +func (cr _ChargeRepository) LatestValidChargeTo(tx pgx.Tx, ctx context.Context, uid string) (*types.Date, error) { searchSql, searchArgs, _ := cr.ds. From(goqu.T("user_charge")). Select("charge_to"). @@ -154,7 +155,7 @@ func (cr _ChargeRepository) LatestValidChargeTo(tx pgx.Tx, ctx context.Context, ). Prepared(true).ToSQL() - var chargeTo []*model.Date + var chargeTo []*types.Date if err := pgxscan.Select(ctx, tx, &chargeTo, searchSql, searchArgs...); err != nil { cr.log.Error("检索用户有效服务期限列表失败。", zap.Error(err)) return nil, err @@ -162,6 +163,6 @@ func (cr _ChargeRepository) LatestValidChargeTo(tx pgx.Tx, ctx context.Context, if len(chargeTo) == 0 { return nil, fmt.Errorf("无法找到用户最近的有效服务期限。") } - lastCharge := lo.MaxBy(chargeTo, func(a, b *model.Date) bool { return a.Time.After(b.Time) }) + lastCharge := lo.MaxBy(chargeTo, func(a, b *types.Date) bool { return a.Time.After(b.Time) }) return lastCharge, nil } diff --git a/service/charge.go b/service/charge.go index fc962fc..4135a8a 100644 --- a/service/charge.go +++ b/service/charge.go @@ -4,8 +4,8 @@ import ( "electricity_bill_calc/cache" "electricity_bill_calc/global" "electricity_bill_calc/logger" - "electricity_bill_calc/model" "electricity_bill_calc/repository" + "electricity_bill_calc/types" "fmt" "github.com/shopspring/decimal" @@ -21,7 +21,7 @@ var ChargeService = &_ChargeService{ } // 创建一条新的用户充值记录,同时更新用户的服务期限 -func (cs _ChargeService) RecordUserCharge(uid string, fee, discount, amount *decimal.Decimal, chargeTo model.Date, extendExpriationIgnoringSettle bool) (bool, error) { +func (cs _ChargeService) RecordUserCharge(uid string, fee, discount, amount *decimal.Decimal, chargeTo types.Date, extendExpriationIgnoringSettle bool) (bool, error) { cs.log.Info( "创建一条新的用户充值记录。", zap.String("uid", uid), diff --git a/types/date.go b/types/date.go new file mode 100644 index 0000000..e9c8e8d --- /dev/null +++ b/types/date.go @@ -0,0 +1,123 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "fmt" + "time" + + "go.uber.org/zap" +) + +type Date struct { + time.Time +} + +func NewDate(year int, month time.Month, day int) Date { + return Date{ + Time: time.Date(year, month, day, 0, 0, 0, 0, loc), + } +} + +func NewEmptyDate() Date { + return Date{ + Time: time.Time{}.In(loc), + } +} + +func NowDate() Date { + return Now().Date() +} + +func ParseDate(t string) (Date, error) { + d, err := time.ParseInLocation("2006-01-02", t, loc) + if err != nil { + return NewEmptyDate(), fmt.Errorf("无法解析给定的日期, %w", err) + } + return Date{ + Time: d, + }, nil +} + +var _ driver.Valuer = (*Date)(nil) + +func (dt Date) Value() (driver.Value, error) { + return dt.In(loc).Format("2006-01-02"), nil +} + +var _ sql.Scanner = (*Date)(nil) + +func (d *Date) Scan(src interface{}) (err error) { + switch src := src.(type) { + case time.Time: + d.Time = src + case string: + t, err := time.ParseInLocation("2006-01-02", src, loc) + if err != nil { + return err + } + *d = Date{Time: t} + case []byte: + d.Time, err = time.ParseInLocation("2006-01-02", string(src), loc) + return err + case nil: + d = nil + default: + return fmt.Errorf("该数据类型不支持解析到日期: %T", src) + } + return nil +} + +var _ json.Marshaler = (*Date)(nil) + +func (d Date) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Format("2006-01-02")) +} + +var _ json.Unmarshaler = (*Date)(nil) + +func (d *Date) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return fmt.Errorf("不能解析指定的日期时间值: %w", err) + } + t, err := time.ParseInLocation("2006-01-02", str, loc) + d.Time = t + return err +} + +func (d Date) DifferenceInMonth(d2 *Date) int { + var differYear, differMonth int + differYear = d.Year() - d2.Year() + differMonth = int(d.Month() - d2.Month()) + return differYear*12 + differMonth +} + +func (d Date) IsNextMonth(d2 *Date) bool { + return d.DifferenceInMonth(d2) == 1 +} + +func (d Date) ToBeginningOfDate() DateTime { + return FromTime(time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, loc)) +} + +func (d Date) ToEndingOfDate() DateTime { + return FromTime(time.Date(d.Year(), d.Month(), d.Day(), 23, 59, 59, 999999, loc)) +} + +func (d Date) IsEmpty() bool { + return d.Time.IsZero() +} + +func (d Date) ToString() string { + return d.Time.Format("2006-01-02") +} + +func (d Date) ToDateTime() DateTime { + return FromTime(d.Time) +} + +func (d Date) Log(fieldName string) zap.Field { + return zap.String(fieldName, d.ToString()) +} diff --git a/types/datetime.go b/types/datetime.go new file mode 100644 index 0000000..74bbd5a --- /dev/null +++ b/types/datetime.go @@ -0,0 +1,118 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "fmt" + "time" + + "go.uber.org/zap" +) + +var loc *time.Location = time.FixedZone("+0800", 8*60*60) + +type DateTime struct { + time.Time +} + +func Now() DateTime { + return DateTime{ + Time: time.Now().In(loc), + } +} + +func Timestamp() int64 { + startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() + return Now().Unix() - startline +} + +func FromTime(t time.Time) DateTime { + return DateTime{ + Time: t, + } +} + +var _ driver.Valuer = (*DateTime)(nil) + +func (dt DateTime) Value() (driver.Value, error) { + return dt.In(loc).Format("2006-01-02 15:04:05"), nil +} + +var _ sql.Scanner = (*DateTime)(nil) + +func (dt *DateTime) Scan(src interface{}) (err error) { + switch src := src.(type) { + case time.Time: + dt.Time = src + case string: + t, err := time.ParseInLocation("2006-01-02 15:04:05", src, loc) + if err != nil { + return err + } + *dt = DateTime{Time: t} + case []byte: + dt.Time, err = time.ParseInLocation("2006-01-02 15:04:05", string(src), loc) + return err + case nil: + dt = nil + default: + return fmt.Errorf("该数据类型不支持解析到日期时间: %T", src) + } + return nil +} + +var _ json.Marshaler = (*DateTime)(nil) + +func (dt DateTime) MarshalJSON() ([]byte, error) { + return json.Marshal(dt.Format("2006-01-02 15:04:05")) +} + +var _ json.Unmarshaler = (*DateTime)(nil) + +func (dt *DateTime) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return fmt.Errorf("不能解析指定的日期时间值: %w", err) + } + t, err := time.ParseInLocation("2006-01-02 15:04:05", str, loc) + dt.Time = t + return err +} + +func (dt DateTime) DifferenceInMonth(d DateTime) int { + var differYear, differMonth int + differYear = dt.Year() - d.Year() + differMonth = int(dt.Month() - d.Month()) + return differYear*12 + differMonth +} + +func (dt DateTime) IsNextMonth(target DateTime) bool { + return dt.DifferenceInMonth(target) == 1 +} + +func (dt *DateTime) ToBeginningOfDate() { + dt.Time = time.Date(dt.Year(), dt.Month(), dt.Day(), 0, 0, 0, 0, loc) +} + +func (dt *DateTime) ToEndingOfDate() { + dt.Time = time.Date(dt.Year(), dt.Month(), dt.Day(), 23, 59, 59, 999999, loc) +} + +func (dt DateTime) Date() Date { + return Date{ + Time: time.Date(dt.Year(), dt.Month(), dt.Day(), 0, 0, 0, 0, loc), + } +} + +func (dt DateTime) IsEmpty() bool { + return dt.Time.IsZero() +} + +func (dt DateTime) ToString() string { + return dt.Time.Format("2006-01-02 15:04:05") +} + +func (dt DateTime) Log(fieldName string) zap.Field { + return zap.String(fieldName, dt.ToString()) +} diff --git a/vo/user.go b/vo/user.go index b09051e..8c7d7e4 100644 --- a/vo/user.go +++ b/vo/user.go @@ -3,6 +3,7 @@ package vo import ( "electricity_bill_calc/model" "electricity_bill_calc/tools/time" + "electricity_bill_calc/types" st "time" "github.com/shopspring/decimal" @@ -29,6 +30,7 @@ func (u MGTAndOPSAccountCreationForm) IntoUser() *model.User { func (u MGTAndOPSAccountCreationForm) IntoUserDetail() *model.UserDetail { return &model.UserDetail{ + Id: "", Name: &u.Name, Abbr: nil, Region: nil, @@ -36,7 +38,7 @@ func (u MGTAndOPSAccountCreationForm) IntoUserDetail() *model.UserDetail { Contact: u.Contact, Phone: u.Phone, UnitServiceFee: decimal.Zero, - ServiceExpiration: model.SpecificDate(2099, st.December, 31), + ServiceExpiration: types.NewDate(2099, st.December, 31), CreatedAt: time.Now(), CreatedBy: nil, LastModifiedAt: time.Now(), @@ -80,7 +82,7 @@ func (u EnterpriseAccountCreationForm) IntoUserDetail() (*model.UserDetail, erro Contact: u.Contact, Phone: u.Phone, UnitServiceFee: unitServiceFee, - ServiceExpiration: model.SpecificDate(2000, st.January, 1), + ServiceExpiration: types.NewDate(2000, st.January, 1), CreatedAt: time.Now(), CreatedBy: nil, LastModifiedAt: time.Now(), From 1fd5e7b9aa5dcff5276891f6eb2bbcb088d5f8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 5 Jun 2023 21:53:57 +0800 Subject: [PATCH 027/141] =?UTF-8?q?enhance(park):=E5=B0=86=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E5=9B=AD=E5=8C=BA=E5=BD=92=E5=B1=9E=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=88=B0=E6=8E=A7=E5=88=B6=E5=99=A8=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/park.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/controller/park.go b/controller/park.go index e959cd2..1c70ff6 100644 --- a/controller/park.go +++ b/controller/park.go @@ -2,6 +2,7 @@ package controller import ( "electricity_bill_calc/logger" + "electricity_bill_calc/model" "electricity_bill_calc/repository" "electricity_bill_calc/response" "electricity_bill_calc/security" @@ -29,6 +30,24 @@ func InitializeParkHandlers(router *fiber.App) { router.Put("/park/:pid/building/:bid/enabled", security.EnterpriseAuthorize, modifyParkBuildingEnabling) } +// 检查当前用户是否拥有指定园区,在判断完成之后直接产生响应 +func checkParkBelongs(logger *zap.Logger, parkId string, session *model.Session, result *response.Result) (bool, error) { + if session == nil { + logger.Error("用户会话无效。") + return false, result.Unauthorized("用户会话无效。") + } + ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid) + switch { + case err != nil: + logger.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return false, result.Error(http.StatusInternalServerError, err.Error()) + case err == nil && !ok: + logger.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return false, result.Forbidden("您无权访问该园区。") + } + return true, nil +} + // 列出隶属于当前用户的全部园区 func listParksBelongsToCurrentUser(c *fiber.Ctx) error { result := response.NewResult(c) From 8aa3a054b07fe26e32be23f221fb54d45f5a92a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 5 Jun 2023 22:09:42 +0800 Subject: [PATCH 028/141] =?UTF-8?q?refactor(time):=E5=BD=BB=E5=BA=95?= =?UTF-8?q?=E9=87=8D=E6=9E=84time=E7=B1=BB=E5=9E=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/charge.go | 2 +- logger/logger.go | 41 ++++++++++++++++++++++++++++++++++----- model/user.go | 14 ++++++------- repository/charge.go | 14 ++++++------- service/charge.go | 8 ++++---- service/user.go | 5 +++-- tools/serial/algorithm.go | 14 ++++++------- tools/time/time.go | 31 ----------------------------- tools/utils.go | 8 ++++++++ vo/user.go | 15 +++++++------- 10 files changed, 79 insertions(+), 73 deletions(-) delete mode 100644 tools/time/time.go diff --git a/controller/charge.go b/controller/charge.go index 2425588..f03750b 100644 --- a/controller/charge.go +++ b/controller/charge.go @@ -37,7 +37,7 @@ func searchCharges(c *fiber.Ctx) error { chargeLog.Error("无法解析查询结束时间。", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } - charges, total, err := repository.ChargeRepository.FindCharges(uint(page), &beginTime.Time, &endTime.Time, &keyword) + charges, total, err := repository.ChargeRepository.FindCharges(uint(page), &beginTime, &endTime, &keyword) if err != nil { chargeLog.Error("检索用户的充值记录列表失败。", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) diff --git a/logger/logger.go b/logger/logger.go index bf872da..ddf414d 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -133,21 +133,52 @@ func WithSugar(fields ...zap.Field) *zap.SugaredLogger { return logger.With(fields...).Sugar() } -func DecimalField(key string, val *decimal.Decimal) zap.Field { +func DecimalField(key string, val decimal.Decimal) zap.Field { return zap.String(key, val.String()) } -func NullDecimalField(key string, val *decimal.NullDecimal) zap.Field { +func DecimalFieldp(key string, val *decimal.Decimal) zap.Field { + if val == nil { + return zap.String(key, "null") + } + return DecimalField(key, *val) +} + +func NullDecimalField(key string, val decimal.NullDecimal) zap.Field { if val.Valid { - return DecimalField(key, &val.Decimal) + return DecimalField(key, val.Decimal) } return zap.String(key, "null") } -func DateField(key string, val *types.Date) zap.Field { +func NullDecimalFieldp(key string, val *decimal.NullDecimal) zap.Field { + if val == nil { + return zap.String(key, "null") + } + if val.Valid { + return DecimalField(key, val.Decimal) + } + return zap.String(key, "null") +} + +func DateField(key string, val types.Date) zap.Field { return val.Log(key) } -func DateTimeField(key string, val *types.DateTime) zap.Field { +func DateFieldp(key string, val *types.Date) zap.Field { + if val == nil { + return zap.String(key, "null") + } + return DateField(key, *val) +} + +func DateTimeField(key string, val types.DateTime) zap.Field { return val.Log(key) } + +func DateTimeFieldp(key string, val *types.DateTime) zap.Field { + if val == nil { + return zap.String(key, "null") + } + return DateTimeField(key, *val) +} diff --git a/model/user.go b/model/user.go index 672205e..7815c29 100644 --- a/model/user.go +++ b/model/user.go @@ -47,9 +47,9 @@ func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail { Phone: m.Phone, UnitServiceFee: decimal.Zero, ServiceExpiration: m.Expires, - CreatedAt: time.Now(), + CreatedAt: types.Now(), CreatedBy: nil, - LastModifiedAt: time.Now(), + LastModifiedAt: types.Now(), LastModifiedBy: nil, DeletedAt: nil, DeletedBy: nil, @@ -85,11 +85,11 @@ type UserDetail struct { Phone *string `json:"phone"` UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"` ServiceExpiration types.Date `json:"serviceExpiration"` - CreatedAt time.Time `json:"createdAt"` + CreatedAt types.DateTime `json:"createdAt"` CreatedBy *string `json:"createdBy"` - LastModifiedAt time.Time `json:"lastModifiedAt"` + LastModifiedAt types.DateTime `json:"lastModifiedAt"` LastModifiedBy *string `json:"lastModifiedBy"` - DeletedAt *time.Time `json:"deletedAt"` + DeletedAt *types.DateTime `json:"deletedAt"` DeletedBy *string `json:"deletedBy"` } @@ -107,8 +107,8 @@ type UserWithDetail struct { Phone *string `json:"phone"` UnitServiceFee decimal.Decimal `db:"unit_service_fee" json:"unitServiceFee"` ServiceExpiration types.Date `json:"serviceExpiration"` - CreatedAt time.Time `json:"createdAt"` + CreatedAt types.DateTime `json:"createdAt"` CreatedBy *string `json:"createdBy"` - LastModifiedAt time.Time `json:"lastModifiedAt"` + LastModifiedAt types.DateTime `json:"lastModifiedAt"` LastModifiedBy *string `json:"lastModifiedBy"` } diff --git a/repository/charge.go b/repository/charge.go index 1908913..eb1419a 100644 --- a/repository/charge.go +++ b/repository/charge.go @@ -8,10 +8,8 @@ import ( "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/tools" - "electricity_bill_calc/tools/time" "electricity_bill_calc/types" "fmt" - st "time" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" @@ -33,13 +31,13 @@ var ChargeRepository = &_ChargeRepository{ } // 分页查询用户的充值记录 -func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *st.Time, keyword *string) ([]*model.UserChargeDetail, int64, error) { - cr.log.Info("查询用户的充值记录。", zap.Timep("beginTime", beginTime), zap.Timep("endTime", endTime), zap.Stringp("keyword", keyword), zap.Uint("page", page)) +func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Date, keyword *string) ([]*model.UserChargeDetail, int64, error) { + cr.log.Info("查询用户的充值记录。", logger.DateFieldp("beginTime", beginTime), logger.DateFieldp("endTime", endTime), zap.Stringp("keyword", keyword), zap.Uint("page", page)) cacheConditions := []string{ fmt.Sprintf("%d", page), tools.DefaultTo(keyword, ""), - tools.CondFn(func(t *st.Time) bool { return t != nil }, beginTime, beginTime.Format("2006-01-02"), "UNDEF"), - tools.CondFn(func(t *st.Time) bool { return t != nil }, endTime, endTime.Format("2006-01-02"), "UNDEF"), + tools.CondFn(func(t *types.Date) bool { return t != nil }, beginTime, beginTime.Format("2006-01-02"), "UNDEF"), + tools.CondFn(func(t *types.Date) bool { return t != nil }, endTime, endTime.Format("2006-01-02"), "UNDEF"), } if charges, total, err := cache.RetrievePagedSearch[[]*model.UserChargeDetail]("charges", cacheConditions...); err == nil { cr.log.Info("从缓存中获取用户的充值记录成功。", zap.Int("count", len(*charges)), zap.Int64("total", total)) @@ -115,7 +113,7 @@ func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, u createQuery, createArgs, _ := cr.ds. Insert(goqu.T("user_charge")). Cols("user_id", "fee", "discount", "amount", "charge_to", "created_at"). - Vals(goqu.Vals{uid, fee, discount, amount, chargeTo, time.Now()}). + Vals(goqu.Vals{uid, fee, discount, amount, chargeTo, types.Now()}). Prepared(true).ToSQL() rs, err := tx.Exec(ctx, createQuery, createArgs...) @@ -130,7 +128,7 @@ func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, u func (cr _ChargeRepository) CancelCharge(tx pgx.Tx, ctx context.Context, uid string, seq int64) (bool, error) { updateQuerySql, updateArgs, _ := cr.ds. Update(goqu.T("user_charge")). - Set(goqu.Record{"cancelled": true, "cancelled_at": time.Now()}). + Set(goqu.Record{"cancelled": true, "cancelled_at": types.Now()}). Where(goqu.I("user_id").Eq(uid), goqu.I("seq").Eq(seq)). Prepared(true).ToSQL() diff --git a/service/charge.go b/service/charge.go index 4135a8a..4bf7b92 100644 --- a/service/charge.go +++ b/service/charge.go @@ -25,10 +25,10 @@ func (cs _ChargeService) RecordUserCharge(uid string, fee, discount, amount *dec cs.log.Info( "创建一条新的用户充值记录。", zap.String("uid", uid), - logger.DecimalField("fee", fee), - logger.DecimalField("discount", discount), - logger.DecimalField("amount", amount), - logger.DateField("chargeTo", &chargeTo), + logger.DecimalFieldp("fee", fee), + logger.DecimalFieldp("discount", discount), + logger.DecimalFieldp("amount", amount), + logger.DateField("chargeTo", chargeTo), zap.Bool("extendExpriationIgnoringSettle", extendExpriationIgnoringSettle), ) diff --git a/service/user.go b/service/user.go index 4dc36fc..09587bf 100644 --- a/service/user.go +++ b/service/user.go @@ -9,7 +9,8 @@ import ( "electricity_bill_calc/repository" "electricity_bill_calc/tools" "electricity_bill_calc/tools/serial" - "electricity_bill_calc/tools/time" + "electricity_bill_calc/types" + "time" "github.com/fufuok/utils" "github.com/google/uuid" @@ -85,7 +86,7 @@ func (us _UserService) ProcessEnterpriseUserLogin(username, password string) (*m Name: user.Username, Type: user.UserType, Token: token.String(), - ExpiresAt: time.Now().Add(config.ServiceSettings.MaxSessionLife), + ExpiresAt: types.Now().Add(config.ServiceSettings.MaxSessionLife), } if userDetail != nil && userDetail.Name != nil { userSession.Name = *userDetail.Name diff --git a/tools/serial/algorithm.go b/tools/serial/algorithm.go index 9ce2354..ed07449 100644 --- a/tools/serial/algorithm.go +++ b/tools/serial/algorithm.go @@ -4,9 +4,9 @@ import ( "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" - "electricity_bill_calc/tools/time" + "electricity_bill_calc/types" "fmt" - stdTime "time" + "time" "github.com/rueian/rueidis" "github.com/samber/lo" @@ -18,7 +18,7 @@ var log = logger.Named("Algorithm", "Unique Serial") // 在防止服务器时间回拨的情况下,生成一个用于生成唯一编号的精简时间戳,该时间戳的起始时间为`2022-02-22 22:22:22.000000 +0800`,时间戳精确到秒。 func generateTimestamp() int64 { for { - timestamp := time.Timestamp() + timestamp := types.Timestamp() cmds := make(rueidis.Commands, 0, 2) cmds = append(cmds, global.Rd.B().Set().Key("LAST_TIMESTAMP").Value(fmt.Sprintf("%d", timestamp)).Nx().ExSeconds(1).Build()) cmds = append(cmds, global.Rd.B().Get().Key("LAST_TIMESTAMP").Build()) @@ -26,14 +26,14 @@ func generateTimestamp() int64 { store_res, err := lo.Last(results) if err != nil { log.Error("从Redis缓存中获取上一次的时间戳失败。", zap.Error(err)) - stdTime.Sleep(1 * stdTime.Second) + time.Sleep(1 * time.Second) continue } if stored_timestamp, err := store_res.AsInt64(); err == nil && timestamp >= stored_timestamp { return timestamp } else { log.Error("转换从Redis缓存中获取的时间戳失败。", zap.Error(err)) - stdTime.Sleep(1 * stdTime.Second) + time.Sleep(1 * time.Second) continue } } @@ -50,14 +50,14 @@ func GenerateUniqueSerial() int64 { store_res, err := lo.Last(results) if err != nil { log.Error("从Redis缓存中获取上一次的序列号失败。", zap.Error(err)) - stdTime.Sleep(1 * stdTime.Second) + time.Sleep(1 * time.Second) continue } if stored_serial, err := store_res.AsInt64(); err == nil { return (timestamp << 20) | ((config.ServiceSettings.HostSerial & 0xffff) << 16) | (stored_serial & 0xffff_ffff) } else { log.Error("转换从Redis缓存中获取的序列号失败。", zap.Error(err)) - stdTime.Sleep(1 * stdTime.Second) + time.Sleep(1 * time.Second) continue } } diff --git a/tools/time/time.go b/tools/time/time.go deleted file mode 100644 index b3c3fe4..0000000 --- a/tools/time/time.go +++ /dev/null @@ -1,31 +0,0 @@ -package time - -import ( - "time" -) - -var Loc *time.Location = time.FixedZone("+0800", 8*60*60) - -func Now() time.Time { - return time.Now().In(Loc) -} - -func Time(year int, month time.Month, date, hours, min, sec, nsec int) time.Time { - return time.Date(year, month, date, hours, min, sec, nsec, Loc) -} - -func Timestamp() int64 { - startline := time.Date(2022, 2, 22, 22, 22, 22, 0, Loc).Unix() - return Now().Unix() - startline -} - -func DifferenceInMonth(t1, t2 time.Time) int { - var differYear, differMonth int - differYear = t1.Year() - t2.Year() - differMonth = int(t1.Month() - t2.Month()) - return differYear*12 + differMonth -} - -func IsNextMonth(origin, t time.Time) bool { - return DifferenceInMonth(t, origin) == 1 -} diff --git a/tools/utils.go b/tools/utils.go index 544f904..4779a6a 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -70,6 +70,14 @@ func DefaultStrTo[T any](format string, originValue *T, defaultStr string) strin return fmt.Sprintf(format, originValue) } +// 判断指定字符串指针是否为`nil`或者字符串长度为空,如果是则返回给定的默认字符串,否则返回指针所指内容的字符串形式。 +func DefaultOrEmptyStr(originValue *string, defaultStr string) string { + if originValue == nil || len(*originValue) == 0 { + return defaultStr + } + return *originValue +} + // 判断指定表达式的值,根据表达式的值返回指定的值。相当于其他语言中的三目运算符。 func Cond[T any](expr bool, trueValue, falseValue T) T { if expr { diff --git a/vo/user.go b/vo/user.go index 8c7d7e4..14f2724 100644 --- a/vo/user.go +++ b/vo/user.go @@ -2,9 +2,8 @@ package vo import ( "electricity_bill_calc/model" - "electricity_bill_calc/tools/time" "electricity_bill_calc/types" - st "time" + "time" "github.com/shopspring/decimal" ) @@ -38,10 +37,10 @@ func (u MGTAndOPSAccountCreationForm) IntoUserDetail() *model.UserDetail { Contact: u.Contact, Phone: u.Phone, UnitServiceFee: decimal.Zero, - ServiceExpiration: types.NewDate(2099, st.December, 31), - CreatedAt: time.Now(), + ServiceExpiration: types.NewDate(2099, time.December, 31), + CreatedAt: types.Now(), CreatedBy: nil, - LastModifiedAt: time.Now(), + LastModifiedAt: types.Now(), LastModifiedBy: nil, DeletedAt: nil, DeletedBy: nil, @@ -82,10 +81,10 @@ func (u EnterpriseAccountCreationForm) IntoUserDetail() (*model.UserDetail, erro Contact: u.Contact, Phone: u.Phone, UnitServiceFee: unitServiceFee, - ServiceExpiration: types.NewDate(2000, st.January, 1), - CreatedAt: time.Now(), + ServiceExpiration: types.NewDate(2000, time.January, 1), + CreatedAt: types.Now(), CreatedBy: nil, - LastModifiedAt: time.Now(), + LastModifiedAt: types.Now(), LastModifiedBy: nil, DeletedAt: nil, DeletedBy: nil, From bfb59a3626ee94e1670a710b72ead17893ac4bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 5 Jun 2023 22:16:14 +0800 Subject: [PATCH 029/141] =?UTF-8?q?refactor(park):=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=88=A0=E9=99=A4=E5=8E=9Ftime=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E4=BB=A5=E5=90=8E=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/park.go | 18 +++++++++--------- repository/user.go | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/repository/park.go b/repository/park.go index eb51cc1..8aa2126 100644 --- a/repository/park.go +++ b/repository/park.go @@ -7,7 +7,7 @@ import ( "electricity_bill_calc/model" "electricity_bill_calc/tools" "electricity_bill_calc/tools/serial" - "electricity_bill_calc/tools/time" + "electricity_bill_calc/types" "fmt" "strings" @@ -99,7 +99,7 @@ func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, er ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() createSql, createArgs, _ := pr.ds. Insert("park"). Cols( @@ -183,7 +183,7 @@ func (pr _ParkRepository) UpdatePark(pid string, park *model.Park) (bool, error) ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() updateSql, updateArgs, _ := pr.ds. Update("park"). Set(goqu.Record{ @@ -228,7 +228,7 @@ func (pr _ParkRepository) EnablingPark(pid string, enabled bool) (bool, error) { ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() updateSql, updateArgs, _ := pr.ds. Update("park"). Set(goqu.Record{ @@ -257,7 +257,7 @@ func (pr _ParkRepository) DeletePark(pid string) (bool, error) { ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() updateSql, updateArgs, _ := pr.ds. Update("park"). Set(goqu.Record{ @@ -346,7 +346,7 @@ func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (b ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() createSql, createArgs, _ := pr.ds. Insert("park_building"). Cols( @@ -378,7 +378,7 @@ func (pr _ParkRepository) ModifyParkBuilding(id, pid, name string, floor *string ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() updateSql, updateArgs, _ := pr.ds. Update("park_building"). Set(goqu.Record{ @@ -412,7 +412,7 @@ func (pr _ParkRepository) EnablingParkBuilding(id, pid string, enabled bool) (bo ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() updateSql, updateArgs, _ := pr.ds. Update("park_building"). Set(goqu.Record{ @@ -445,7 +445,7 @@ func (pr _ParkRepository) DeleteParkBuilding(id, pid string) (bool, error) { ctx, cancel := global.TimeoutContext() defer cancel() - timeNow := time.Now() + timeNow := types.Now() updateSql, updateArgs, _ := pr.ds. Update("park_building"). Set(goqu.Record{ diff --git a/repository/user.go b/repository/user.go index d451673..b0ed5c8 100644 --- a/repository/user.go +++ b/repository/user.go @@ -8,10 +8,10 @@ import ( "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/tools" - "electricity_bill_calc/tools/time" + "electricity_bill_calc/types" "fmt" "strings" - stdTime "time" + "time" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" @@ -181,7 +181,7 @@ func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, o return false, err } - createdTime := time.Now() + createdTime := types.Now() userSql, userParams, _ := ur.ds. Insert("user"). Rows( @@ -331,7 +331,7 @@ func (ur _UserRepository) UpdateDetail(uid string, userDetail model.UserModifica updates := goqu.Record{ "name": userDetail.Name, "abbr": tools.PinyinAbbr(userDetail.Name), "region": userDetail.Region, "address": userDetail.Address, "contact": userDetail.Contact, "phone": userDetail.Phone, - "last_modified_at": time.Now(), "last_modified_by": operator, + "last_modified_at": types.Now(), "last_modified_by": operator, } if userDetail.UnitServiceFee != nil { updates = lo.Assign(updates, goqu.Record{"unit_service_fee": userDetail.UnitServiceFee}) @@ -451,7 +451,7 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, } // 更新指定用户的服务有效期限 -func (ur _UserRepository) UpdateServiceExpiration(tx pgx.Tx, ctx context.Context, uid string, expiration stdTime.Time) (bool, error) { +func (ur _UserRepository) UpdateServiceExpiration(tx pgx.Tx, ctx context.Context, uid string, expiration time.Time) (bool, error) { ur.log.Info("更新指定用户的服务有效期限。", zap.String("user id", uid)) userDetailUpdateQuery := ur.ds. From 6bf40093387b5b4ba8c98a85425e67b88562e2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 7 Jun 2023 21:25:28 +0800 Subject: [PATCH 030/141] =?UTF-8?q?refactor(serial):=E5=94=AF=E4=B8=80?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8F=B7=E7=94=9F=E6=88=90=E5=99=A8=E6=94=B9?= =?UTF-8?q?=E8=BF=9B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/park.go | 8 ++- service/user.go | 3 +- tools/serial/algorithm.go | 120 ++++++++++++++++++++------------------ 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/repository/park.go b/repository/park.go index 8aa2126..516552d 100644 --- a/repository/park.go +++ b/repository/park.go @@ -100,6 +100,8 @@ func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, er defer cancel() timeNow := types.Now() + serial.StringSerialRequestChan <- 1 + code := serial.Prefix("P", <-serial.StringSerialResponseChan) createSql, createArgs, _ := pr.ds. Insert("park"). Cols( @@ -108,7 +110,7 @@ func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, er "basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at", ). Vals(goqu.Vals{ - serial.GeneratePrefixedUniqueSerialString("P"), + code, ownerId, park.Name, tools.PinyinAbbr(park.Name), park.Area, park.TenementQuantity, park.Capacity, park.Category, park.MeterType, park.Region, park.Address, park.Contact, park.Phone, park.Enabled, park.PricePolicy, park.TaxRate, @@ -347,13 +349,15 @@ func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (b defer cancel() 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{ - serial.GeneratePrefixedUniqueSerialString("B"), + code, pid, name, floor, true, timeNow, timeNow, }). Prepared(true).ToSQL() diff --git a/service/user.go b/service/user.go index 09587bf..7a18799 100644 --- a/service/user.go +++ b/service/user.go @@ -134,7 +134,8 @@ func (us _UserService) CreateUserAccount(user *model.User, detail *model.UserDet } else { prefix = "S" } - user.Id = serial.GeneratePrefixedUniqueSerialString(prefix) + serial.StringSerialRequestChan <- 1 + user.Id = serial.Prefix(prefix, <-serial.StringSerialResponseChan) detail.Id = user.Id } verifyCode := tools.RandStr(10) diff --git a/tools/serial/algorithm.go b/tools/serial/algorithm.go index ed07449..8bd08ec 100644 --- a/tools/serial/algorithm.go +++ b/tools/serial/algorithm.go @@ -2,75 +2,83 @@ package serial import ( "electricity_bill_calc/config" - "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/types" "fmt" "time" - - "github.com/rueian/rueidis" - "github.com/samber/lo" - "go.uber.org/zap" ) -var log = logger.Named("Algorithm", "Unique Serial") +var ( + log = logger.Named("Algorithm", "Unique Serial") + SerialRequestChan = make(chan int64, 500) + StringSerialRequestChan = make(chan int64, 500) + SerialResponseChan = make(chan int64, 500) + StringSerialResponseChan = make(chan string, 500) +) -// 在防止服务器时间回拨的情况下,生成一个用于生成唯一编号的精简时间戳,该时间戳的起始时间为`2022-02-22 22:22:22.000000 +0800`,时间戳精确到秒。 -func generateTimestamp() int64 { +func init() { + go func() { + var ( + lastTimestamp int64 = 0 + lastSerial int64 = 0 + ) + log.Info("唯一序列号生成服务已经启动。") + + for { + select { + case <-SerialRequestChan: + log.Info("收到生成数字型唯一序列号的请求。") + timestamp := generateTimestamp(lastTimestamp) + if timestamp != lastTimestamp { + lastSerial = 0 + } + lastSerial := lastSerial + 1 + uniqueId := generateSerial(timestamp, lastSerial) + SerialResponseChan <- uniqueId + lastTimestamp = timestamp + case <-StringSerialRequestChan: + log.Info("收到生成字符串型唯一序列号的请求。") + timestamp := generateTimestamp(lastTimestamp) + if timestamp != lastTimestamp { + lastSerial = 0 + } + lastSerial := lastSerial + 1 + uniqueId := generateStringSerial(timestamp, lastSerial) + StringSerialResponseChan <- uniqueId + lastTimestamp = timestamp + } + } + }() +} + +// 生成一个能够对抗服务器时间回拨的时间戳 +func generateTimestamp(base int64) int64 { for { timestamp := types.Timestamp() - cmds := make(rueidis.Commands, 0, 2) - cmds = append(cmds, global.Rd.B().Set().Key("LAST_TIMESTAMP").Value(fmt.Sprintf("%d", timestamp)).Nx().ExSeconds(1).Build()) - cmds = append(cmds, global.Rd.B().Get().Key("LAST_TIMESTAMP").Build()) - results := global.Rd.DoMulti(global.Ctx, cmds...) - store_res, err := lo.Last(results) - if err != nil { - log.Error("从Redis缓存中获取上一次的时间戳失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue - } - if stored_timestamp, err := store_res.AsInt64(); err == nil && timestamp >= stored_timestamp { + if timestamp >= base { return timestamp - } else { - log.Error("转换从Redis缓存中获取的时间戳失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue } + time.Sleep(1 * time.Second) } } -// 生成一个基于精简改进的雪花算法的数字类型唯一编号。 -func GenerateUniqueSerial() int64 { - for { - timestamp := generateTimestamp() - cmds := make(rueidis.Commands, 0, 2) - cmds = append(cmds, global.Rd.B().Set().Key("SERIAL").Value(fmt.Sprintf("%d", 0)).Nx().ExSeconds(1).Build()) - cmds = append(cmds, global.Rd.B().Incr().Key("SERIAL").Build()) - results := global.Rd.DoMulti(global.Ctx, cmds...) - store_res, err := lo.Last(results) - if err != nil { - log.Error("从Redis缓存中获取上一次的序列号失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue - } - if stored_serial, err := store_res.AsInt64(); err == nil { - return (timestamp << 20) | ((config.ServiceSettings.HostSerial & 0xffff) << 16) | (stored_serial & 0xffff_ffff) - } else { - log.Error("转换从Redis缓存中获取的序列号失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue - } +// 生成一个唯一的数字型序列号 +func generateSerial(timestamp, serial int64) int64 { + return (timestamp << 20) | ((config.ServiceSettings.HostSerial & 0xffff) << 16) | (serial & 0xffff_ffff) +} + +// 生成一个唯一的字符串型序列号 +func generateStringSerial(timestamp, serial int64) string { + return fmt.Sprintf("%017d", generateSerial(timestamp, serial)) +} + +// 生成一个带前缀字符串的唯一字符串型序列号 +func Prefix(prefix string, serial interface{}) string { + switch serial := serial.(type) { + case int64: + return fmt.Sprintf("%s%017d", prefix, serial) + case string: + return fmt.Sprintf("%s%s", prefix, serial) } -} - -// 生成一个基于精简改进的雪花算法的字符串类型唯一编号。 -func GenerateUniqueSerialString() string { - uniqueId := GenerateUniqueSerial() - return fmt.Sprintf("%d", uniqueId) -} - -// 生成一个带有前缀的基于精简改进的雪花算法的字符串类型唯一编号。 -func GeneratePrefixedUniqueSerialString(prefix string) string { - uniqueId := GenerateUniqueSerial() - return fmt.Sprintf("%s%d", prefix, uniqueId) + return "" } From 28b1478e9ab52758490bae3c32a3a5e7d8ce1e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 20:55:56 +0800 Subject: [PATCH 031/141] =?UTF-8?q?enhance(meter):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=A1=A8=E8=AE=A1=E5=A4=A7=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E7=9A=84=E6=94=BE=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/meter_04kv_template.xlsx | Bin 10523 -> 10453 bytes controller/meter.go | 180 +++++ model/cunsumption.go | 10 + model/meter.go | 78 +++ model/reading.go | 56 ++ repository/meter.go | 1091 +++++++++++++++++++++++++++++++ router/router.go | 1 + service/meter.go | 539 +++++++++++++++ vo/meter.go | 38 ++ vo/reading.go | 16 + 10 files changed, 2009 insertions(+) create mode 100644 controller/meter.go create mode 100644 model/cunsumption.go create mode 100644 model/meter.go create mode 100644 model/reading.go create mode 100644 repository/meter.go create mode 100644 service/meter.go create mode 100644 vo/meter.go create mode 100644 vo/reading.go diff --git a/assets/meter_04kv_template.xlsx b/assets/meter_04kv_template.xlsx index c1cccde54f09ba21f12ffaac6b217d1de07bf315..687fafc903bee1abf9a6ebbca146e2d7b063a7bf 100644 GIT binary patch delta 6442 zcmZWuWmr|+);)A9AR&E#Lw6mzqz=uYq@}w-8n%?ubx`S$mTqZKI;2|xX*@`ylf6QmBvE~?a@8?-_?o91jcntytquuZj+Yph4Lj5Q>MP1_)FG<8N*cbW} zsagbyt|4Lo1q`^zu@kBWZU^W1dqU0182m7;Y|4~a6R>e*PPJhjkMoxoA=K}>#fmXF zR0tRwFk|~KkKz3s!>VZW#TDHmIM5@A3tP3K{ho;eUsDiH%;KPU1Nlx7eDt!wa4qIo zsR@)t=ov#Q!klLH0p+EhlDv4~8ckgQzXBd1wHyj=15huLV0p9g+&uNSSDs#g2GiHp zlWZT4Yv-P3%N;YrMG9nV!uN*wZ>IwnApy^)^$Chxefm(g($`X{%&P=1)3vl!k!oJU z?wSrw2E54PkZ)c%>p|IWW-DD%tI%pXid(J#M`O!aOri?|Z)3~qFGb{U>v4u3r9kK@ zUT#{GJrr}u(0)Rj(fn{bGi@2KQT0fg@B9O=vYj;yi;pJjXH=lf%C^)E<)yHeDjGTo z2n50gz0z2hnNEsRN6@0N16p;OUVB1v%ROe8y^&!X`>GkANiqrr_jbXZZ4}-l$4YKG!fHWb($AL$Re6n>h?Lf-aJtP*u?+?hndM5MHQ^USIkri-7oe> zTk$q4?$)XFo^>nFwxaprn3WTs9h-{jVc3_(1R+4vF1AQ>&WM2U#j5HHQ(Uc#P4s=s zh4%Dq9~`f|-Y0XV9y}Lj;))&f(MKD(m5ufwW*~^Gmg#8 zwAyF1n`mcRd9Ta@zB4_u3qLbHqxq~Au;N?X|4xXk`1h{=>ZOP68EkIk3WD@4Gx!NGF=a@&~F`Lf_;^1<^plwRZO!Ln!krx#6I zt!2wsod8uO^2frvOJ9G{PU~MSrh)OOh{mx@;a9&|X8bgTf3;Q&9|Qw!Xj+rm*VKsv zlOC%HbK*)Uk5WzIV!6c|OglQp+9Wz#4xX-!*amrZusM*8O)05+bj3)kR&}P0Sq9`a z2kDRvkcA|)-kg`eW1lmv&F>&qvVGWIg%s|;$N~zZ9agB+MuNV+c8jejW&fTv1-+Vk zkdlGYUqW=WW6{tSG9R=kpSdn2t!t|P6krwpy20KVhxMcU>cSr`CE*iM_n1q4`nf3_ z>x$MzwLWvhKC`R7ut`i=(IEqJy64%%yE?WK@@%q+8QGf4Ags2&w5W)_D*O&PDP)wu zZ3`gs$DBPJ<=?PSs+%&lK8PnP@V-r+6?m#XpC15KVLpxRvk}WqJJfuj@#)N0c0NBy zsx=KFcbv-HqmFef=|S$@9L(67LzTTfGrC_kIW13KMP&L)abz;6MdbWyL9ro8lF0PV zser{b3aa-^WB#s4B7b5Z`KXEiZH;u;DKF4;-3TzJm28ha*P&yK;i@OR{u1HnsUYc~ zN_8&4SZzre-V~(zeK~XJ{DMmonEQM@>I?aje3_6c?ixIhDxP|n<}r|cnCA8BCk@`g z#TQ=__W`ff+RX&=&W#lD5TrOUUs)HPL2pGTzCmw82vJSe&T}<9z$kc$qhz*AgbV0| zKRs?AXR4>lG0fLQh6Gw`Yv~(gsT>r_g?AI~Ued|%vdh(=f?=|TD1`rt*=J%A#tjoz zfUxh8AoRTnFfTen>eKX5JE5Wt_I-qnl>-#!ZBO_(%-`ae=$}yB9Z(pYB8(Iyi#v?_ z0u0blqQ5jZEBS5s&vSN*zD3lCNV3KMb(VV#(OCJg!{F&pqz$xK$6QMjt`TY>nWz|v zNZUWzk*-a~Ox`4cP*{$)iwo2iWnn27Dp}EKSfX;sSbE_rgBP8}n6=>=vc!9n27StE zt{5<9Ad4OcJ&Q3pMx`PQGF6aWl54DCxqgq=Ay5YIP)F;g`eVt7Q{EGaYrg0$G8(11C7Jez1PbvZTNL6<4;X6M4y^DUars&nEYT+i6g|`sx=BqP9WMq6 zJ@SHR!>e$WyFd9x;?!A~z#eA0iEu-5sDP*J?6tTF9Fj}^r~>Aw-1mt-R0iYFMCHO=bc^JkI9@NSSKjOe(92Apa!;aN z###c7KC`b_hcKU@LZCRw^0roUzHmIy*+Yp89RZmLd_ ze1M?72k|3+v>{QrPG$rQb#XVR@83cPgp!5h0ySo4nCTNZ>iN8AP4JLC9yb4=0sh0o zm+~(~TWLC<1(N(@JaLo!Xyb8Na9AAg(#cqo7t=(?)73@)od~hUOxfE~4bjBRKgbln z=$Jopa=J8&p*R+~BKIgG_MJaC4?TfnX!)!1HNy>5&Wf6FZ**h@|U`M{V$dmSu zA;BWi(e2$?3y@yX`_t5m-rbuzRq!|S1{E_)b^hiPIvNB@l`LOuGY6!Nn;rhA2K0}? zwTn>@ey;kzi8BXowK- z*|w?pFIY5AkHtXFD*2>;&PCiGS*JRp4^v(YBse7=?f3M(OyKDpDo?4}21AYJU(sZ1 z7oI6o0Ic6v4)@wC*_XK!PTs#y9Dff4{p!#0mzW#aY5ScYXxQL#Yo54eM65SGL-6Pa zeBCABd8V4Jsu)T^=qUU&e;ASZEI6>VZ8hlP+qm@at!L7A=Z9UlM_;9-f_`b9PHO_! z$E(0qPDtGwmU>^Drl~a) zr>&vSG&G-i%q&=qcQcxH2$P;d3^letk#`WcSBZ<=5kLbA1uny+(hJ^;v{oZO-+mMu zy^c3uFeCkHLHFY{c(R@S4yGzEt1rxy-S>;5E9TepX(9i`^e%B{WLsxR%kuOc#fQs> zNwoDG1dNzKV{B-CJT3@C3I~A*?(a~3&OBb;{?0aD+&b&~` z{F#xfTXivT_ohRCvN2?0w~>;jdS~X0v84Lh>dnbxzx#b?G*!AT8&PxrvRF1da@wMfFToX)PIpeEyXC|LU~>`EJVe!$iAcXT=ASYg#6M1^`50H8eum9B0?F)u{tPF%VX`eCOVCiD!5q zPZ0K1;ry=He*CvRw`UT8u+=5Ei(nacMoxoGias`=9{E!uS7MZpLMP<}*%w)tlbetm z%zt_Mdx-q7Us+c`$@U_!P3UXW52~#nK{@V*_ISZa{5N0=yA#7FqHVp^$H#1tI$#J! znTs)%lK)MQv%m*7*k(Smec`_M0bAC7=i9S)FPOZ)fibB~z(jV-y?zjd%%xic%$R*WWVkb`N%5=iDBcwIOon~tj`2F$CEL`tPfx|T4qZ#)Sld2(d! zwUI}Jp0HZ~O>twsIpIMb{&(3T4c)=oUu5kaJHiS02vjKw*;V8Ru6KQAG~~~;Jg~Ug zR`QMBf9!fvWW--i%BX30c=GnE*@&ojUIx!31Lvza*n9W5pvR>VCnMla%T$2=PA$reYFfnvx!y_Pl(1HU)a8Hzn3QUh&{=s$y`DPTaY^c` z14ezXtD0YxS=Aw@sEUNmyAuFxDj$=|)w=(BP-$yX#gi>1*Y(-QT78}$3mn}>txcX4 z`6u>FCvQzXyaeE+?Zhy~%RT5GYw%n-+c`G2_Rg6QjrGSGpZQbcnuEe)rd;+o*2RLc z_=M>Pd61K@9lADC=Ei8(r?Rx#)gBP|CqR!%V6Xs`-v zhh)B~rg`QIkai=m#hSCKraiGt>0eoq70JfNX;;?0<}p^Nm&dDrno5&Fwm;n`Xq!d| zyFus1mwenQv4ypF&eZlofjj-J+;gU7zlkQb6Z&(x1oRw6W_k<-a*U@>P$M6bv%a2* zKP8DO_CPE}v8F6WVG}O_QLLXGH%RvVAVhKs5b~+frIsXli@hfq^-J%=lo}o+3Fbsu z&V9yUAkpW}OS5CtY))#=)F|;nY);cPC}1|jaHX9K^!ygoWc0O%)+JjaqE$kMri9uS zzu0?!=`fq$>$$4fz;Z=Y96-O#S*X^%`r+5aD@Rxtx$?MvmJ$mK$Syb}Xl+Yyo0i1Z z#kum1^^u(X9j_I-J4w|^E;9DwAZ9S!bY~)&v)yajVoa<+g+b}#f@EfbAW;-<3X6ri zFw2*+$Yv0(?euf(aNDN!5ufjzD#V=aPza(ldTFBcU7Po^pd&Z$PEJu?I5gJF_2pNG zFsJ&(ms+D^qdrg=pp4d(ZL^Bt1rN8UDB7O3qDbblazmb0)v3yZ1!WVR`^o1b8Ru=F zdwB^|+ydW~r*@8T$)Uv7x~iMX0`*nshmkp{o)1_hO1fWjudI1U~t>>;DdmH&08M{iY;w&w@eVa-H&TgrtPv;et}QToj#1+n9db5na48X z5R0DM%&#kG(cz_dH?z;F`rL0a1w5pNou_(hKRcEVJ(d?j$8avD z{!VQcZ0B@rPtkd!Qhu~#agF+B1CGO&T_<UW%x-lbyV=U?`oK5BCtPxF_cI5;Rhp)0@lxr-6hT6 z{XJYegl`-cZCm{_g^7_IhwIqqW<{jhXFP9d1OsI{WitIXxg;3c>^JeP)aj&x#X@eN zA14mFe{>%Uyx}oF{VFKSc1Pf%P8D)_d6D}|5fGu3@)x|^4L^31n$6oamqAaYXI5y9 zbjygP7yvM?O!_0$-pjx5h9o~Bk0cArN!^Lc$<`qdhI(uajMF_h%#9!pC4qN`kVMLJ zXt)s$oH3L7LYifKkC>ax+)?Z-x%Y#v8-CvTO9WlTYH?GHD|zlQ|8S_q^~DJ-42T-U zJHuZR{5>KqNq2kov_NwO&*lkHV$^PQmP3ZY2N@tp)C6W1CO^Hw7QOhQO_Ykl=gpK2 z?nC*2!Km>14>1=58E8{2wERcr0T-Ok=s`oM%qO_ch431R+}rc5Jw|%Nm$YiC>@X7A zDBZVktiWgzd}d$Za((X+z0OM-PHYaCB~>7a1VzXjVzf^PyC)TK5(~8+WVIj7>vr%( zDSQAwwuQLc=Mt^g3ase~`V0+q1+&xkG$B6hD{tW#ROY%2Qk~*gBSS|$hp&De#!KC3 zPYi2?MXFsEx>-&|}A9g7#VCDQLDN?p7n zWVI?To#7(`y*r+amrXWaOzc%_dyV8;!u|?`5E9j%-nXh&8B9RhUG0QHe*!GB zxDADIk$11^Xz{40gM6G;=5tp2of&J+flD0rtaom(nAC5_lijneYq!L^K;Y!)a@Ut7 zEHsYQhS4U6n1nyUTpO0}R58S$NJvuF$hsJpd*gC(AkMFdt8<|p#t*HJ;eHR68w0eP z=YTsJb9T`JHRC+?^5C!>SJy&Z9qQRMc$^)=n=H${I)u}VxE}1 z)(`M(L~UaFVG1sh6=U?;HkNCZ+T9Tq()5%I+IdI*6_p=ttdXpiR&0yzi>M5125RV3 zr6hHvp)nE?k$g7D7(Fv<-3c-z3gBXvkL5HcU`WSKuI4nABb`C|VoJ)sn}VfP_Swu& zP~4koSA4M^u45bZ#HudUaSqwI3uCtrzU?pSO7|wuk(+8{s&~A@fgKNDxvo`1kG^Ky z)@N6GJ`Ws#&a+=u4rl1O$2iTd&;2x-eD8LDnVfA;ufC%;!oTe3c6#+v==5o3sA=UKY$qLsHeUbKaFZyI%>xm|uSsJa>3HB?s;RtsMLPF*^7TcWsm zwf+lV`0n9iHIc75|B1$l@vc^JzCT7X_Q7n!K+py1GJarng0T1K6jaSeFypv#gd<6@a0H-t!oF61;kMN-I$mvME?3w+YWQ zC#{&X2GctePWKa#?*BNba7K;hr^yUJm=}gw$;+A=n`yJ!r0iP0M~&t4#e3oi7z|E2 z#DBoXtJjoJva>v+OlqZ_Qg@{9&0;n|Y!U44LNQZhgGPH-o`WwJq>d(k3Qv$YI^j-V zG>k1j!r)gA&E1S8`%Qm{q1V7~EOCAc|LDduOh9Y0{nXATBNFBCfj|EkU3IysPwvU2 z@*?RWJmk?>K={qKmi@Z9G7&K((EeGBr^`fmv$D-UAO~Yeg5YjOBH=>BEbT(6E1tYD z*6&14?%3uB+JBn}oQ^f>-kT_n3IaX+-;Ti7&C|)!&CTggPY|Cx<{HjR7;*Ue>Ooas zBMNbp5xRWYRbUxI%FXsznXu)lMgSAcH#4C~+)AynZuQj$?@YF-HTty+kxC zik<`aOj91+VdA`Ss=)w>oi+#SuCPHXD-X|!S?1T0Iq@)%4c?N-yp4r@Ii`^qmC??4#JdT_^Ue-&1`}~ivaQ1VnNQkCsp{kxjtNZ z%_V_6$~+|VzXhUPHryjm1*3!9{C@r>q`ZEqGPAZj#>3G66zlZkMyR{< zh87;i7Z%yU^eY9I2ilE@*F?iEZ$4P(R&rW;rmYPTaSb>;?ctBAUt@!%NJwntEek{n z)U+8Ab!tltI8pre2hY)mpJQ3yvS0h9_v4inTyj)m}G jl|YF=RI$=i{WW(6fgb;X+^_#tQiJ%;%7OU-@+a~?cG>*n delta 6461 zcma)BbyQUCw;mcqLJ&zQVdyUDM!I1Jbm&%k=om@_X+}bZZlps}36*9*K$-#R?(U1< z@4NRGcdfhb`Qx0m_WSJpp6A)mI%~cAjOieBtKeu@l|AZsV0bDT4e8rN4bAVokHgNC zXBiHqs^K3#f#Ka~^e}rhj(G62z~0dSymnZZtV4i;Lrqe_RY4n&mLj)#L5$MuUI1W? z^KpT794KvICRlzLwYCb{=_-pL%t5@)~2bAV=zCP!Ou^6O7 z(tx6+YeavC#Y-9fPBn{Nz@{)1XPlgv35zD;3!Q=;}}~H zmE^xPXdMOaO1<3IoNY=m+>Q2tyzrRr+P|>$mu_mdFAdifX;}<9RCnnk;02{!tFx9A zcod3Sdu!R(gL$`rfeQk7i~dQKrgLpl^W&9tl($^I4li@%Lvj5%r;{exy*4^mpwWk6 zOe)IQ45A+sMZ*f&n3r>WuFP5^ry#`|A+X3Ia}up3T8&G=ot;MwL0^yo=R$|h+G-e> zj{yJxF5sQ!qRfT)>rZex40afCjh6dHfSvece^8^gOL~CH=tnuk2d1Y_e|U+6#x8z~ z;U>CUSh^bO9p@Rvjg>>l_R{Fa$W<43DNC+b~tZ{(P z2|5!GtXX3RzhMF0)K80!4_9ya$sWhICr$AyYa-J0!#;H?n3*xIEC}Bq}sosR@3_o2m(XU9T{spqCq- zTed5qn0$m;i_}9(bkABqbWKr=R53sB`^B2iHmj5atW#UBPDd-=&%Iv-k6(GDtJ}Eh zZ;PDN&$g8MsHjpp#Z`cUF|;_0CP!9n=d}fr#s#$hCWxRwZ4rG*K@( zt69{Id|lli2fx(b-bh+&o?79ObTldKme*UJsjvrWj844~LanSwd6GIIDnIH-OQ#^@ z#t@?wlMPPFdip+Ux7`zmF55)`(}81O9N}3TU_Ux-{oN#UlN#J>&y~WGxD$8rsb4K6 zdiZgNHomZ>r)y5652=jGAani_Pg`?3aKsvktPSe3TE6eReSgZo<}al+_-@In))pU} zz3gCIH?=W2E}qoj;}4t22&PNhxIxwD8`Kp^IlN!&F*erL=Xli|>%5qG1J0t0I$Zh8oHQ23J$m`eaHaL!@4!(1 zS5<7~HnrpC`@)ekJJ2+TM6Blpi|rl|*vq5=w-RkLDckn)`wFXA*DV^+Xn1oyju+=1 zs!Iz~h_j`IN*b9|hKu%_6DRqb6PK&oD#WG8f})J}YQX_3*;qum-GM(H5(CSz$2_Qx zC%t%BK())jjG+p{;7c-K8Tf)MPM>d}jmiWu@_?XUVWO)_9rT^w$RVXSuxh9ItGk(K5KJAN zIoQ)os7|8i0YNF6OQqlDL7F^9H3eA4X~fSo=E9h^UK{0kj|t{Vu#bN zVn_kVgx`d{+p;QITLwH=$!7XV7z8il1xT8UPF42h3?Smqaq` zpf#(rHkeDs{|YOhi9at7=dAjd{qWc&-i8J8(b8~d-4ObbsJAoHv(u;HMPLeHNAB5R zh)CE*_M79X0oTWEJ?M(ri-*;JYIss^AEMqQ$2K-aD2m{sxTk9sNXUqhm?*214(@rR z&1M85$`}R>-M1D|(Pxab3300FGE3RBsrC4QROD`!#jQm!u_q`9$ch+QR`q+;Qi?CG z=8~i6qo*`T#%{zKV|ga>|0MPQmE?J!#M>;(`X5OjXIda`oUS_2PQ-)Tz{o}JS0bQL zr=Kf`$^0D+dgLn@AP-N0AEBH}q9RBtm>`7R;x9to@yv<)<1j%@8j49Lk|_&8C9CG5=l)Hl3EAxxOz77F zfcLXoqv@axtG9C%^C|l`96++KwlemBqvA_xQRNRJ?wjEjd*50|fAUnEdw7(}KV(9M zKmdPa?OQkz_A(-vp#B#{V1IexpOPS%|7`HLdAhuUeX8(zJ3RhVz@Fx&vM!H%1^*51 zLf&Y#CozeY`ny>+)9Whafx{oAAb+)V^x4+x2=#=cI2#FWJ{9wqf@cRgY_I zI#v?-xHk3bp9URr|c7p&pvDRrG~@V|Xq)PWZ%L z%gaaiGPL|o_gA3Q&zrGN67{YhKn4MUpp~n*-SNrS0fEzWnmcEm2dQn1GgU7_n(a5c zLU%n6Eq$+22cAqoMRj}vw1YzZz zMnFsZ>7B58Zqe>swfD1Tu7K=k*HX?Vs>T(cybH(DG*){pd?e{w7mHm*nqHXLZ-wrF zfxRbhreMRg$2m?LPIv27pi83Y5T|q<5RK%7$5y5{X)=nf!D%n*6dFNzkz+TPa#skK zH3y3@->fNm#ZCwx&2a+#piW*Ab=m;PZO-Z7PoB= znH#mra zS*K*4yUad56;PGoD23ri#A=U+?&d|$W~G0ve$H!shN_pC_r&2cf`F`gsi3nJl9SH8 z^KCHuG9jy8q5;_n=xp#BswI%9#RhbO>6O1-yI?RrRG1bmJ8gRg< zsTWZ>n*XT#*H+5*7tU>qbiT<`CwMyZC3f#?bT$yDGIP^353iJ@_X}dhX)w8(y0}#q zMCLc@tsp4{@h@j~Lf9Mfoo}J**p;_urK7NYB*#dSGo&fEjH8ch!=R%<+PME#YPsgg zZIfpV|HUbnO2v;$bL%?)J}U>t+eE!*FY!E#BYUtuY?86@Kfi>cwT?Y*t$t0pwT#{! z5PS6N5}SuUn$i`@Ecb2PX6PwX&`*6C%%+;p8*L0I;iv7NRCo6pW4Bb$WC)36D<2NP zTFK`4#IBG>MMUcmoWeB>UHNaHG%Iw{mAp=Og12-Ia-!K(`g|QL%4}`nMOo>08*>-r zdxa`xxF4She7X6W{Dz|I0?}*{S$5vIEeHfY#k);oo zQiqiB4#w3Mmr*Sn#Cs?_KBiWq)yUhNeIq~lgekfnU#d>BeyM7((U>`g_|?=jOhPcU z{SX`5KR)+NB#tPDfJ&xFbGtn34B6y2FA!%CW7z4B*}63e$?XiJ7BSUebmM8ZA!;5^ zz|y$F-ucX~Jn_Z1AR_oe*$ggP&P!_lGHSX(8y z*KT{u!t&|RSu|?LriH?k{DV|a0S6wNYL6k<4Y@F`x4gp$-(n{|I;_ChUWS_ap-*uQIf_{4BS6>Fa< z#Kc&0eslV}6kK42V%^Fl4>sXZP%%+QVBDUyW>hc6~*`}Tm(Z{u(gABxA(RUV)gn??iK8^~HFWx9%irznc z9K+~yav7r8`{hZrgMoT0!7Y-NRB|0UIlY=RI%SU(r9jWngM-~+3RY7TNiSLFp7y&s z#rs}{(w6=fV_SG3S*Jqy4zDMfW$Fuyvw04`FLm5@NK-Qwf9=O5t8Es8wb46BHW_1!0uAG&=2ru)4@?_yw+%Six zMe5A0k09W;eJX9uL>eLb(nPIJpimzs6FNG7JUA$k>BXuciob>QA!~woZx2}$ zX*bPb$+c?8w*rtZ7w9zNd%0UEvQLkOt;9mx?QKAkJfrRdnCG_Y137-et{6&JOHn;6 zg4qJ(s1cefUOD^C#Jk^99Hr3+?b8@jpnx`gGL45XZ*D8;m+p^DKq3AU}RP~^6b5S zzb0R>6Bik^HUIsqcO8N0U7P1ZLEapC{Za>?99_prYFm#{HU82s zW+FetLGpDc5!UkfqV=-kP}LBsp7W;@KVc+s+UWh4V#&txk_Oz)ST+sUJ*!}eYol>MEr0Qv%ugq7O zci!jEB+8nVm)CHN57&F)y?G5f*n0$yIJBvFTUa!CX~0SCR)%twJRa$+>!scEGnTw= zq9N%^v%@VdW(v;lP{8OTIF%$s1gWUDgdVEc^A`5dHj=$ciRq_eHdY>s239UnmR<~z zuws{1rX&SQJcb#aEjP%+C0IdH!#h3Gqm+FRapC8?v%Fz= zgGH0o6>Cnm7P>|hSz{C)Q&cLRPZI5QMj`B=+cwYi#_hV-uUefyokijJAlN=lzfs2j zStCpaOa9fT;~rlp;orLv1VrMHb^c%+5=rkL9kq=i=S684BhMtge|NZ+Mpc>b7f_c* zIxkSf1RefxV)oj&ekns)au<*MbpqB3o!FqgPv8&Pz-@71_jtQoPe z&M?DXI(BIrs-wwA*i4@X1g4HT&2o7;oALdHn_*`cqgUDjL-R16Ct7Fvq;h;H+bqq& zRxz0O>EcZT%UzS#?eD4chN5V+I9Bt-@Jx%x60v5kGTE;`^>QlhM~O|M3>Xl-OKq`p z0q?qTXCv|Q<~`G>kh&Zk)VXF>?Qftk*ZSKnXMmoKsA;BI5!lR!T};*lJX$L8(?L!+ zq_1z2x(+RUQ*NR_DQE)?XelgY?l}Yq=QPhjGX&@I>FQL(my0T}b3W8DXW!MIcuqAuFWnArTaJC0BXpn>Tc?g9*__7r z5urnML-4J+)L9yl)@{3~{rVKw#aheE$?9N5PPx#Bovt*K&knDV)2D;R8qUsp+Y}m# zdehEwUGekyUwrd71==Vl8^mFmTPK2$4^oF!jf;J7V5ruB|CazvTUQyWHmKD*pw?jUfWUS1v$ifP zx5|K;%0Tas+Gm4L>Rg634NEIEY~0*BfK4Pvy+I!<8(Y=L1dryRGE#$cyq`xIiP4w8 zLcRh_1~e&dTy|n9mPE33QkIl3H;G;frk7#v zFOGmMs*58?T8dgOvre5GhIy)MhjAih6tc9YF6&;O92h zSPNPmQ`10}Q^=3TCs9Rw;vs+?eq*2c0Sb3TEFmw9(V#BG$Yh$|2haXjmat%a5ZbwM zHB&50*{benCU5PXd1}oEtl5tirlF^jKSCC1cUn}1V^@!K*}-SjkLCnRcS6*{ZKrJ$cCH=AswFYObAolN2A-L)7e0C3H;o1y2fZ;=h3?H3~;5 z1h)OO=o^x{!7uVuXuP+hhb^#z%PL4vP5uN65BUzXvxadqchnan|C)z$1D~Kh zgewANa4azYvN2qel?Gl6Bt)lTgf{|(&}A9ndq7T_|1|u)=K*K{`ujU6EnI{JNb}br z?*RZnrUL*F{8_!f{sE97h5N9Gq7x9n>sW-)1qt9=EX>q@I|87E4ge7S1O4A{I0vf) jS}5F(m4W7;V`u= lower(r.period)"), + ), + ). + Select( + "m.*", goqu.I("b.name").As("building_name"), + ). + Order(goqu.I("m.seq").Asc()). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil { + mr.log.Error("查询表计信息失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + + return meters, nil +} + +// 分页列出指定园区下的表计信息 +func (mr _MeterRepository) MetersIn(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) { + mr.log.Info("分页列出指定园区下的表计信息", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, ""))) + cacheConditions := []string{ + pid, + tools.DefaultOrEmptyStr(keyword, "UNDEF"), + fmt.Sprintf("%d", page), + } + if meters, total, err := cache.RetrievePagedSearch[[]*model.MeterDetail]("meter", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了指定园区中的表计信息", zap.Int("count", len(*meters)), zap.Int64("total", total)) + return *meters, total, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + meterQuery := mr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))). + Select( + "m.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("m.park_id").Eq(pid), + goqu.I("m.detachedAt").IsNull(), + ) + countQuery := mr.ds. + From(goqu.T("meter_04kv").As("m")). + Select(goqu.COUNT("*")). + Where( + goqu.I("m.park_id").Eq(pid), + goqu.I("m.detachedAt").IsNull(), + ) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + meterQuery = meterQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + countQuery = countQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + meterQuery = meterQuery.Order(goqu.I("m.seq").Asc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + + var ( + meters []*model.MeterDetail + total int64 + ) + if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil { + mr.log.Error("查询表计信息失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), 0, err + } + if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { + mr.log.Error("查询表计数量失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), 0, err + } + + cache.CachePagedSearch(meters, total, []string{fmt.Sprintf("meter:%s", pid)}, "meter", cacheConditions...) + + return meters, total, nil +} + +// 列出指定园区中指定列表中所有表计的详细信息,将忽略所有表计的当前状态 +func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.MeterDetail, error) { + mr.log.Info("列出指定园区中指定列表中所有表计的详细信息", zap.String("park id", pid), zap.Strings("meter ids", ids)) + cacheConditions := []string{ + pid, + strings.Join(ids, ","), + } + if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("meter_slice", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了指定园区中所需的表计信息", zap.Int("count", len(*meters))) + return *meters, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var meters []*model.MeterDetail + metersSql, metersArgs, _ := mr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))). + Select( + "m.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("m.park_id").Eq(pid), + goqu.I("m.id").Eq(goqu.Func("any", ids)), + ). + Order(goqu.I("m.seq").Asc()). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil { + mr.log.Error("查询表计信息失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + + cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", pid), fmt.Sprintf("meter_slice:%s", pid)}, "meter_slice", cacheConditions...) + + return meters, nil +} + +// 获取指定表计的详细信息 +func (mr _MeterRepository) FetchMeterDetail(pid, code string) (*model.MeterDetail, error) { + mr.log.Info("获取指定表计的详细信息", zap.String("park id", pid), zap.String("meter code", code)) + cacheConditions := fmt.Sprintf("%s:%s", pid, code) + if meter, err := cache.RetrieveEntity[*model.MeterDetail]("meter", cacheConditions); err == nil { + mr.log.Info("从缓存中获取到了指定表计的详细信息", zap.String("code", (**meter).Code)) + return *meter, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var meter model.MeterDetail + meterSql, meterArgs, _ := mr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))). + Select( + "m.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("m.park_id").Eq(pid), + goqu.I("m.code").Eq(code), + ). + Prepared(true).ToSQL() + + if err := pgxscan.Get(ctx, global.DB, &meter, meterSql, meterArgs...); err != nil { + mr.log.Error("查询表计信息失败", zap.Error(err)) + return nil, err + } + + cache.CacheEntity(meter, []string{fmt.Sprintf("meter:%s", pid), "park"}, "meter", cacheConditions) + + return &meter, nil +} + +// 创建一条新的表计信息 +func (mr _MeterRepository) CreateMeter(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, + }, + ). + Prepared(true).ToSQL() + + ok, err := tx.Exec(ctx, meterSql, meterArgs...) + if err != nil { + mr.log.Error("创建表计信息失败", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, meter.Code)) + } + + return ok.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) { + mr.log.Info("记录一条表计的抄表信息", zap.String("park id", pid), zap.String("meter code", code)) + readAt := tools.DefaultTo(reading.ReadAt, types.Now()) + readingSql, readingArgs, _ := mr.ds. + Insert(goqu.T("meter_reading")). + Cols( + "park_id", "meter_id", "read_at", "meter_type", "ratio", "overall", "critical", "peak", "flat", "valley", + ). + Vals( + goqu.Vals{pid, code, readAt, meterType, ratio, reading.Overall, reading.Critical, reading.Peak, reading.Flat, reading.Valley}, + ). + Prepared(true).ToSQL() + + ok, err := tx.Exec(ctx, readingSql, readingArgs...) + if err != nil { + mr.log.Error("记录表计抄表信息失败", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation(fmt.Sprintf("meter_reading:%s", pid)) + } + + return ok.RowsAffected() > 0, nil +} + +// 更新一条表计的详细信息 +func (mr _MeterRepository) UpdateMeter(tx pgx.Tx, ctx context.Context, pid, code string, detail *vo.MeterModificationForm) (bool, error) { + mr.log.Info("更新一条表计的详细信息", zap.String("park id", pid), zap.String("meter code", code)) + timeNow := types.Now() + meterSql, meterArgs, _ := mr.ds. + Update(goqu.T("meter_04kv")). + Set( + goqu.Record{ + "address": detail.Address, + "seq": detail.Seq, + "ratio": detail.Ratio, + "enabled": detail.Enabled, + "meter_type": detail.MeterType, + "building": detail.Building, + "on_floor": detail.OnFloor, + "area": detail.Area, + "last_modified_at": timeNow, + }, + ). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("code").Eq(code), + ). + Prepared(true).ToSQL() + + ok, err := tx.Exec(ctx, meterSql, meterArgs...) + if err != nil { + mr.log.Error("更新表计信息失败", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + } + + return ok.RowsAffected() > 0, nil +} + +// 列出指定园区中已经存在的表计编号,无论该表计是否已经不再使用。 +func (mr _MeterRepository) ListMeterCodes(pid string) ([]string, error) { + mr.log.Info("列出指定园区中已经存在的表计编号", zap.String("park id", pid)) + cacheConditions := []string{pid} + if codes, err := cache.RetrieveSearch[[]string]("meter_codes", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了指定园区中的表计编号", zap.Int("count", len(*codes))) + return *codes, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var codes []string + codesSql, codesArgs, _ := mr.ds. + From(goqu.T("meter_04kv")). + Select("code"). + Where( + goqu.I("park_id").Eq(pid), + ). + Order(goqu.I("seq").Asc()). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &codes, codesSql, codesArgs...); err != nil { + mr.log.Error("查询表计编号失败", zap.Error(err)) + return make([]string, 0), err + } + + cache.CacheSearch(codes, []string{fmt.Sprintf("meter:%s", pid), fmt.Sprintf("park:%s", pid)}, "meter_codes", cacheConditions...) + + return codes, nil +} + +// 解除指定园区中指定表计的使用 +func (mr _MeterRepository) DetachMeter(tx pgx.Tx, ctx context.Context, pid, code string) (bool, error) { + mr.log.Info("解除指定园区中指定表计的使用", zap.String("park id", pid), zap.String("meter code", code)) + timeNow := types.Now() + meterSql, meterArgs, _ := mr.ds. + Update(goqu.T("meter_04kv")). + Set( + goqu.Record{ + "detached_at": timeNow, + "last_modified_at": timeNow, + }, + ). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("code").Eq(code), + ). + Prepared(true).ToSQL() + + ok, err := tx.Exec(ctx, meterSql, meterArgs...) + if err != nil { + mr.log.Error("解除表计使用失败", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, code)) + } + + return ok.RowsAffected() > 0, nil +} + +// 将商户表计绑定到公摊表计上 +func (mr _MeterRepository) BindMeter(tx pgx.Tx, ctx context.Context, pid, masterMeter, slaveMeter string) (bool, error) { + mr.log.Info("将商户表计绑定到公摊表计上", zap.String("master meter code", masterMeter), zap.String("slave meter code", slaveMeter)) + masterDetail, err := mr.FetchMeterDetail(pid, masterMeter) + if err != nil { + mr.log.Error("查询公摊表计信息失败", zap.Error(err)) + return false, err + } + if masterDetail.MeterType != model.METER_INSTALLATION_POOLING { + mr.log.Error("给定的公摊表计不是公摊表计", zap.Error(err)) + return false, fmt.Errorf("给定的公摊表计不是公摊表计") + } + slaveDetail, err := mr.FetchMeterDetail(pid, slaveMeter) + if err != nil { + mr.log.Error("查询商户表计信息失败", zap.Error(err)) + return false, err + } + if slaveDetail.MeterType != model.METER_INSTALLATION_TENEMENT { + mr.log.Error("给定的商户表计不是商户表计", zap.Error(err)) + return false, fmt.Errorf("给定的商户表计不是商户表计") + } + + timeNow := types.Now() + serial.StringSerialRequestChan <- 1 + code := serial.Prefix("PB", <-serial.StringSerialResponseChan) + relationSql, relationArgs, _ := mr.ds. + Insert(goqu.T("meter_relations")). + Cols( + "id", "park_id", "master_meter_id", "slave_meter_id", "established_at", + ). + Vals( + goqu.Vals{ + code, + pid, + masterMeter, + slaveMeter, + timeNow, + }, + ). + Prepared(true).ToSQL() + + ok, err := tx.Exec(ctx, relationSql, relationArgs...) + if err != nil { + mr.log.Error("绑定表计关系失败", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, masterMeter)) + } + + return ok.RowsAffected() > 0, nil +} + +// 解除两个表计之间的关联 +func (mr _MeterRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, masterMeter, slaveMeter string) (bool, error) { + mr.log.Info("解除两个表计之间的关联", zap.String("master meter code", masterMeter), zap.String("slave meter code", slaveMeter)) + relationSql, relationArgs, _ := mr.ds. + Update(goqu.T("meter_relations")). + Set( + goqu.Record{ + "revoked_at": types.Now(), + }, + ). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("master_meter_id").Eq(masterMeter), + goqu.I("slave_meter_id").Eq(slaveMeter), + goqu.I("revoke_at").IsNull(), + ). + Prepared(true).ToSQL() + + ok, err := tx.Exec(ctx, relationSql, relationArgs...) + if err != nil { + mr.log.Error("解除表计关系失败", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, masterMeter)) + } + + return ok.RowsAffected() > 0, nil +} + +// 列出指定公摊表计的所有关联表计关系 +func (mr _MeterRepository) ListPooledMeterRelations(pid, code string) ([]*model.MeterRelation, error) { + mr.log.Info("列出指定公摊表计的所有关联表计关系", zap.String("park id", pid), zap.String("meter code", code)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + var relations []*model.MeterRelation + relationsSql, relationsArgs, _ := mr.ds. + From(goqu.T("meter_relations")). + Select("*"). + Where( + goqu.I("r.park_id").Eq(pid), + goqu.I("r.master_meter_id").Eq(code), + goqu.I("r.revoke_at").IsNull(), + ). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil { + mr.log.Error("查询表计关系失败", zap.Error(err)) + return make([]*model.MeterRelation, 0), err + } + + return relations, nil +} + +// 列出指定公摊表计列表所包含的全部关联表计关系 +func (mr _MeterRepository) ListPooledMeterRelationsByCodes(pid string, codes []string) ([]*model.MeterRelation, error) { + mr.log.Info("列出指定公摊表计列表所包含的全部关联表计关系", zap.String("park id", pid), zap.Strings("meter codes", codes)) + cacheConditions := []string{ + pid, + strings.Join(codes, ","), + } + if relations, err := cache.RetrieveSearch[[]*model.MeterRelation]("meter_relations", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了所需的关联表计信息", zap.Int("count", len(*relations))) + return *relations, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var relations []*model.MeterRelation + relationsSql, relationsArgs, _ := mr.ds. + From(goqu.T("meter_relations")). + Select("*"). + Where( + goqu.I("r.park_id").Eq(pid), + goqu.I("r.master_meter_id").Eq(goqu.Func("any", codes)), + goqu.I("r.revoke_at").IsNull(), + ). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil { + mr.log.Error("查询表计关系失败", zap.Error(err)) + return make([]*model.MeterRelation, 0), err + } + + return relations, nil +} + +// 列出指定商户表计、园区表计与公摊表计之间的关联关系 +func (mr _MeterRepository) ListMeterRelations(pid, code string) ([]*model.MeterRelation, error) { + mr.log.Info("列出指定商户表计、园区表计与公摊表计之间的关联关系", zap.String("park id", pid), zap.String("meter code", code)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + var relations []*model.MeterRelation + relationsSql, relationsArgs, _ := mr.ds. + From(goqu.T("meter_relations")). + Select("*"). + Where( + goqu.I("r.park_id").Eq(pid), + goqu.I("r.slave_meter_id").Eq(code), + goqu.I("r.revoke_at").IsNull(), + ). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil { + mr.log.Error("查询表计关系失败", zap.Error(err)) + return make([]*model.MeterRelation, 0), err + } + + return relations, nil +} + +// 列出指定园区中的所有公摊表计 +func (mr _MeterRepository) ListPoolingMeters(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) { + mr.log.Info("列出指定园区中的所有公摊表计", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, ""))) + cacheConditions := []string{ + pid, + tools.DefaultOrEmptyStr(keyword, "UNDEF"), + fmt.Sprintf("%d", page), + } + if meters, total, err := cache.RetrievePagedSearch[[]*model.MeterDetail]("pooling_meters", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了指定园区中的公摊表计信息", zap.Int("count", len(*meters)), zap.Int64("total", total)) + return *meters, total, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + meterQuery := mr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))). + Select( + "m.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("m.park_id").Eq(pid), + goqu.I("m.enabled").IsTrue(), + goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_POOLING), + ) + countQuery := mr.ds. + From(goqu.T("meter_04kv").As("m")). + Select(goqu.COUNT("*")). + Where( + goqu.I("m.park_id").Eq(pid), + goqu.I("m.enabled").IsTrue(), + goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_POOLING), + ) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + meterQuery = meterQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + countQuery = countQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + meterQuery = meterQuery.Order(goqu.I("m.code").Asc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + + var ( + meters []*model.MeterDetail + total int64 + ) + if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil { + mr.log.Error("查询公摊表计信息失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), 0, err + } + if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { + mr.log.Error("查询公摊表计数量失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), 0, err + } + + cache.CachePagedSearch(meters, total, []string{fmt.Sprintf("meter:%s", pid), "park"}, "pooling_meters", cacheConditions...) + + return meters, total, nil +} + +// 列出目前尚未绑定到公摊表计的商户表计 +func (mr _MeterRepository) ListUnboundMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) { + mr.log.Info("列出目前尚未绑定到公摊表计的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0))) + cacheConditions := []string{ + tools.DefaultTo(pid, "UNDEF"), + tools.DefaultOrEmptyStr(keyword, "UNDEF"), + tools.DefaultStrTo("%d", limit, "0"), + } + if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("unbound_pooling_meters", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了指定园区中的商户表计信息", zap.Int("count", len(*meters))) + return *meters, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + meterQuery := mr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))). + Select( + "m.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_TENEMENT), + goqu.I("m.enabled").IsTrue(), + ) + + if pid != nil && len(*pid) > 0 { + meterQuery = meterQuery.Where( + goqu.I("m.park_id").Eq(*pid), + ) + } + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + meterQuery = meterQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + } + + slaveMeterQuery := mr.ds. + From("meter_relations"). + Select("id") + if pid != nil && len(*pid) > 0 { + slaveMeterQuery = slaveMeterQuery.Where( + goqu.I("park_id").Eq(*pid), + ) + } else { + slaveMeterQuery = slaveMeterQuery.Where( + goqu.I("park_id").In( + mr.ds. + From("park"). + Select("id"). + Where(goqu.I("user_id").Eq(uid)), + )) + } + slaveMeterQuery = slaveMeterQuery.Where( + goqu.I("revoke_at").IsNull(), + ) + meterQuery = meterQuery.Where( + goqu.I("m.code").NotIn(slaveMeterQuery), + ). + Order(goqu.I("m.attached_at").Asc()) + + if limit != nil && *limit > 0 { + meterQuery = meterQuery.Limit(*limit) + } + + meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL() + var meters []*model.MeterDetail + if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil { + mr.log.Error("查询商户表计信息失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + + cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", tools.DefaultTo(pid, "ALL")), "park"}, "unbound_pooling_meters", cacheConditions...) + + return meters, nil +} + +// 列出目前未绑定到商户的商户表计 +func (mr _MeterRepository) ListUnboundTenementMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) { + mr.log.Info("列出目前未绑定到商户的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0))) + cacheConditions := []string{ + tools.DefaultTo(pid, "UNDEF"), + tools.DefaultOrEmptyStr(keyword, "UNDEF"), + tools.DefaultStrTo("%d", limit, "0"), + } + if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("unbound_tenement_meters", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了指定园区中的商户表计信息", zap.Int("count", len(*meters))) + return *meters, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + meterQuery := mr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))). + Select( + "m.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_TENEMENT), + goqu.I("m.enabled").IsTrue(), + ) + + if pid != nil && len(*pid) > 0 { + meterQuery = meterQuery.Where( + goqu.I("m.park_id").Eq(*pid), + ) + } + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + meterQuery = meterQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + } + + subMeterQuery := mr.ds. + From("tenement_meter"). + Select("meter_id") + if pid != nil && len(*pid) > 0 { + subMeterQuery = subMeterQuery.Where( + goqu.I("park_id").Eq(*pid), + ) + } else { + subMeterQuery = subMeterQuery.Where( + goqu.I("park_id").In( + mr.ds. + From("park"). + Select("id"). + Where(goqu.I("user_id").Eq(uid)), + )) + } + subMeterQuery = subMeterQuery.Where( + goqu.I("disassociated_at").IsNull(), + ) + meterQuery = meterQuery.Where( + goqu.I("m.code").NotIn(subMeterQuery), + ). + Order(goqu.I("m.attached_at").Asc()) + + if limit != nil && *limit > 0 { + meterQuery = meterQuery.Limit(*limit) + } + + meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL() + var meters []*model.MeterDetail + if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil { + mr.log.Error("查询商户表计信息失败", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + + cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", tools.DefaultTo(pid, "ALL")), "park"}, "unbound_tenement_meters", cacheConditions...) + + return meters, nil +} + +// 查询指定园区中的符合条件的抄表记录 +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, ""))) + cacheConditions := []string{ + pid, + tools.DefaultOrEmptyStr(keyword, "UNDEF"), + fmt.Sprintf("%d", page), + tools.CondFn(func(val *types.Date) bool { + return val != nil + }, start, start.ToString(), "UNDEF"), + 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 { + mr.log.Info("从缓存中获取到了指定园区中的抄表记录", zap.Int("count", len(*readings)), zap.Int64("total", total)) + return *readings, total, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + readingQuery := mr.ds. + 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")))). + Where( + goqu.I("r.park_id").Eq(pid), + ) + countQuery := mr.ds. + 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")))). + Select(goqu.COUNT("*")). + Where( + goqu.I("r.park_id").Eq(pid), + ) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + readingQuery = readingQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + countQuery = countQuery.Where( + goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + ), + ) + } + + if start != nil { + readingQuery = readingQuery.Where( + goqu.I("r.read_at").Gte(start.ToBeginningOfDate()), + ) + countQuery = countQuery.Where( + goqu.I("r.read_at").Gte(start.ToBeginningOfDate()), + ) + } + + if end != nil { + readingQuery = readingQuery.Where( + goqu.I("r.read_at").Lte(end.ToEndingOfDate()), + ) + countQuery = countQuery.Where( + goqu.I("r.read_at").Lte(end.ToEndingOfDate()), + ) + } + + if buidling != nil && len(*buidling) > 0 { + readingQuery = readingQuery.Where( + goqu.I("m.building").Eq(*buidling), + ) + countQuery = countQuery.Where( + goqu.I("m.building").Eq(*buidling), + ) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + readingQuery = readingQuery.Order(goqu.I("r.read_at").Desc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + readingSql, readingArgs, _ := readingQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + + var ( + readings []*model.MeterReading + total int64 + ) + if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil { + mr.log.Error("查询抄表记录失败", zap.Error(err)) + return make([]*model.MeterReading, 0), 0, err + } + if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { + mr.log.Error("查询抄表记录数量失败", zap.Error(err)) + return make([]*model.MeterReading, 0), 0, err + } + + cache.CachePagedSearch(readings, total, []string{fmt.Sprintf("meter_reading:%s", pid), "park"}, "meter_reading", cacheConditions...) + + return readings, total, nil +} + +// 修改指定表计的指定抄表记录 +func (mr _MeterRepository) UpdateMeterReading(pid, mid string, readAt types.DateTime, reading *vo.MeterReadingForm) (bool, error) { + mr.log.Info("修改指定表计的指定抄表记录", zap.String("park id", pid), zap.String("meter id", mid), logger.DateTimeField("read at", readAt), zap.Any("reading", reading)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + updateSql, updateArgs, _ := mr.ds. + Update(goqu.T("meter_reading")). + Set( + goqu.Record{ + "overall": reading.Overall, + "critical": reading.Critical, + "peak": reading.Peak, + "flat": reading.Flat, + "valley": reading.Valley, + }, + ). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("meter_id").Eq(mid), + goqu.I("read_at").Eq(readAt), + ). + Prepared(true).ToSQL() + + ok, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + mr.log.Error("更新抄表记录失败", zap.Error(err)) + return false, err + } + + if ok.RowsAffected() > 0 { + cache.AbolishRelation(fmt.Sprintf("meter_reading:%s", pid)) + } + + return ok.RowsAffected() > 0, nil +} + +// 列出指定园区中指定时间区域内的所有表计抄表记录 +func (mr _MeterRepository) ListMeterReadingsByTimeRange(pid string, start, end types.Date) ([]*model.MeterReading, error) { + mr.log.Info("列出指定园区中指定时间区域内的所有表计抄表记录", zap.String("park id", pid), zap.Time("start", start.Time), zap.Time("end", end.Time)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + var readings []*model.MeterReading + readingSql, readingArgs, _ := mr.ds. + From(goqu.T("meter_reading").As("r")). + Select("*"). + Where( + goqu.I("r.park_id").Eq(pid), + goqu.I("r.read_at").Gte(start.ToBeginningOfDate()), + goqu.I("r.read_at").Lte(end.ToEndingOfDate()), + ). + Order(goqu.I("r.read_at").Desc()). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil { + mr.log.Error("查询抄表记录失败", zap.Error(err)) + return make([]*model.MeterReading, 0), err + } + + return readings, nil +} + +// 列出指定园区中在指定日期之前的最后一次抄表记录 +func (mr _MeterRepository) ListLastMeterReading(pid string, date types.Date) ([]*model.MeterReading, error) { + mr.log.Info("列出指定园区中在指定日期之前的最后一次抄表记录", zap.String("park id", pid), zap.Time("date", date.Time)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + var readings []*model.MeterReading + readingSql, readingArgs, _ := mr.ds. + From(goqu.T("meter_reading")). + Select( + goqu.MAX("read_at").As("read_at"), + "park_id", "meter_id", "overall", "critical", "peak", "flat", "valley", + ). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("read_at").Lt(date.ToEndingOfDate()), + ). + GroupBy("park_id", "meter_id", "overall", "critical", "peak", "flat", "valley"). + Order(goqu.I("read_at").Desc()). + Limit(1). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil { + mr.log.Error("查询抄表记录失败", zap.Error(err)) + return make([]*model.MeterReading, 0), err + } + + return readings, nil +} + +// 列出指定园区中的表计与商户的关联详细记录,用于写入Excel模板文件 +func (mr _MeterRepository) ListMeterDocForTemplate(pid string) ([]*model.SimpleMeterDocument, error) { + mr.log.Info("列出指定园区中的表计与商户的关联详细记录", zap.String("park id", pid)) + cacheConditions := []string{pid} + if docs, err := cache.RetrieveSearch[[]*model.SimpleMeterDocument]("simple_meter_doc", cacheConditions...); err == nil { + mr.log.Info("从缓存中获取到了指定园区中的表计与商户的关联详细记录", zap.Int("count", len(*docs))) + return *docs, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var docs []*model.SimpleMeterDocument + docSql, docArgs, _ := mr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin( + goqu.T("tenement_meter").As("tm"), + goqu.On( + goqu.I("m.code").Eq(goqu.I("tm.meter_id")), + goqu.I("m.park_id").Eq(goqu.I("tm.park_id")), + ), + ). + LeftJoin( + goqu.T("tenement").As("t"), + goqu.On( + goqu.I("tm.tenement_id").Eq(goqu.I("t.id")), + goqu.I("tm.park_id").Eq(goqu.I("t.park_id")), + ), + ). + Select( + "m.code", "m.address", "m.ratio", "m.seq", goqu.I("t.full_name").As("tenement_name"), + ). + Where( + goqu.I("m.park_id").Eq(pid), + goqu.I("m.enabled").IsTrue(), + goqu.I("m.disassociated_at").IsNull(), + ). + Order(goqu.I("m.seq").Asc()). + Prepared(true).ToSQL() + + if err := pgxscan.Select(ctx, global.DB, &docs, docSql, docArgs...); err != nil { + mr.log.Error("查询表计与商户关联信息失败", zap.Error(err)) + return make([]*model.SimpleMeterDocument, 0), err + } + + cache.CacheSearch(docs, []string{fmt.Sprintf("park:%s", pid), fmt.Sprintf("meter:%s", pid), "park"}, "simple_meter_doc", cacheConditions...) + + return docs, nil +} diff --git a/router/router.go b/router/router.go index 73e779f..02b8f2f 100644 --- a/router/router.go +++ b/router/router.go @@ -48,6 +48,7 @@ func App() *fiber.App { controller.InitializeRegionHandlers(app) controller.InitializeChargeHandlers(app) controller.InitializeParkHandlers(app) + controller.InitializeMeterHandlers(app) return app } diff --git a/service/meter.go b/service/meter.go new file mode 100644 index 0000000..ebb428e --- /dev/null +++ b/service/meter.go @@ -0,0 +1,539 @@ +package service + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "electricity_bill_calc/tools" + "electricity_bill_calc/types" + "electricity_bill_calc/vo" + "fmt" + "mime/multipart" + + "github.com/samber/lo" + "github.com/shopspring/decimal" + "go.uber.org/zap" +) + +type _MeterService struct { + log *zap.Logger +} + +var MeterService = _MeterService{ + log: logger.Named("Service", "Meter"), +} + +// 创建一条新的表计记录 +func (ms _MeterService) CreateMeterRecord(pid string, form *vo.MeterCreationForm) error { + ms.log.Info("创建一条新的表计记录", zap.String("park id", pid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ms.log.Error("无法启动数据库事务。", zap.Error(err)) + return err + } + + ok, err := repository.MeterRepository.CreateMeter(tx, ctx, pid, *form) + if err != nil { + ms.log.Error("无法创建一条新的表计记录。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + if !ok { + ms.log.Error("数据库未能记录新的表计记录。") + tx.Rollback(ctx) + return err + } + + ok, err = repository.MeterRepository.RecordReading(tx, ctx, pid, form.Code, form.MeterType, form.Ratio, &form.MeterReadingForm) + if err != nil { + ms.log.Error("无法记录表计读数。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + if !ok { + ms.log.Error("数据库未能记录表计读数。") + tx.Rollback(ctx) + return err + } + + err = tx.Commit(ctx) + if err != nil { + ms.log.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} + +// 更新指定表计的信息 +func (ms _MeterService) UpdateMeterRecord(pid string, code string, form *vo.MeterModificationForm) error { + ms.log.Info("更新指定表计的信息", zap.String("park id", pid), zap.String("meter code", code)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ms.log.Error("无法启动数据库事务。", zap.Error(err)) + return err + } + + ok, err := repository.MeterRepository.UpdateMeter(tx, ctx, pid, code, form) + if err != nil { + ms.log.Error("无法更新指定表计的信息。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + if !ok { + ms.log.Error("数据库未能更新指定表计的信息。") + tx.Rollback(ctx) + return err + } + + err = tx.Commit(ctx) + if err != nil { + ms.log.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} + +// 处理上传的Excel格式表计档案文件,根据表号自动更新数据库 +func (ms _MeterService) BatchImportMeters(pid string, file multipart.FileHeader) error { + return nil +} + +// 更换系统中的表计 +func (ms _MeterService) ReplaceMeter( + pid string, + oldMeterCode string, + oldMeterReading *vo.MeterReadingForm, + newMeterCode string, + newMeterRatio decimal.Decimal, + newMeterReading *vo.MeterReadingForm, +) error { + ms.log.Info("更换系统中的表计", zap.String("park id", pid), zap.String("old meter code", oldMeterCode), zap.String("new meter code", newMeterCode)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ms.log.Error("无法启动数据库事务。", zap.Error(err)) + return err + } + + // 步骤1:读取旧表信息 + oldMeter, err := repository.MeterRepository.FetchMeterDetail(pid, oldMeterCode) + if err != nil { + ms.log.Error("无法读取旧表信息。", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("要替换的旧表计不存在:%w", err) + } + + // 步骤2:写入旧表读数 + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, oldMeterCode, oldMeter.MeterType, oldMeter.Ratio, oldMeterReading) + switch { + case err != nil: + ms.log.Error("无法写入旧表读数。", zap.Error(err)) + tx.Rollback(ctx) + return err + case !ok: + ms.log.Error("数据库未能写入旧表读数。") + tx.Rollback(ctx) + return fmt.Errorf("旧表计读数未能成功保存到数据库。") + } + + // 步骤3:从系统移除旧表计 + ok, err = repository.MeterRepository.DetachMeter(tx, ctx, pid, oldMeterCode) + switch { + case err != nil: + ms.log.Error("无法从系统移除旧表计。", zap.Error(err)) + tx.Rollback(ctx) + return err + case !ok: + ms.log.Error("未能从系统移除旧表计。") + tx.Rollback(ctx) + return fmt.Errorf("旧表计未能成功从系统移除。") + } + + // 步骤4:获取旧表计的关联信息 + var oldRelations []*model.MeterRelation + switch oldMeter.MeterType { + case model.METER_INSTALLATION_POOLING: + oldRelations, err = repository.MeterRepository.ListPooledMeterRelations(pid, oldMeterCode) + if err != nil { + ms.log.Error("无法获取旧表计的关联信息。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + default: + oldRelations, err = repository.MeterRepository.ListMeterRelations(pid, oldMeterCode) + if err != nil { + ms.log.Error("无法获取旧表计的关联信息。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + } + + // 步骤5:将旧表计的关联信息设置为解除 + for _, relation := range oldRelations { + ok, err = repository.MeterRepository.UnbindMeter(tx, ctx, pid, relation.MasterMeter, relation.SlaveMeter) + switch { + case err != nil: + ms.log.Error("无法将旧表计的关联信息设置为解除。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", relation.SlaveMeter), zap.Error(err)) + tx.Rollback(ctx) + return err + case !ok: + ms.log.Error("未能将旧表计的关联信息设置为解除。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", relation.SlaveMeter)) + tx.Rollback(ctx) + return fmt.Errorf("旧表计的关联信息未能成功设置为解除。") + } + } + + // 步骤6:将旧表计的部分信息赋予新表计 + newMeterCreationForm := vo.MeterCreationForm{ + Code: newMeterCode, + Address: oldMeter.Address, + MeterType: oldMeter.MeterType, + Ratio: newMeterRatio, + Seq: oldMeter.Seq, + Enabled: oldMeter.Enabled, + Building: oldMeter.Building, + OnFloor: oldMeter.OnFloor, + Area: oldMeter.Area, + MeterReadingForm: *newMeterReading, + } + + // 步骤7:将新表计写入系统 + ok, err = repository.MeterRepository.CreateMeter(tx, ctx, pid, newMeterCreationForm) + switch { + case err != nil: + ms.log.Error("无法将新表计写入系统。", zap.Error(err)) + tx.Rollback(ctx) + return err + case !ok: + ms.log.Error("未能将新表计写入系统。") + tx.Rollback(ctx) + return fmt.Errorf("新表计未能成功写入系统。") + } + + // 步骤8:将新表计的读数写入系统 + ok, err = repository.MeterRepository.RecordReading(tx, ctx, pid, newMeterCode, newMeterCreationForm.MeterType, newMeterCreationForm.Ratio, &newMeterCreationForm.MeterReadingForm) + switch { + case err != nil: + ms.log.Error("无法将新表计的读数写入系统。", zap.Error(err)) + tx.Rollback(ctx) + return err + case !ok: + ms.log.Error("未能将新表计的读数写入系统。") + tx.Rollback(ctx) + return fmt.Errorf("新表计的读数未能成功写入系统。") + } + + // 步骤9:将旧表计的关联信息复制一份赋予新表计 + switch oldMeter.MeterType { + case model.METER_INSTALLATION_POOLING: + for _, relation := range oldRelations { + ok, err = repository.MeterRepository.BindMeter(tx, ctx, pid, newMeterCode, relation.SlaveMeter) + switch { + case err != nil: + ms.log.Error("无法将旧表计的关联信息赋予新表计。", zap.String("master meter", newMeterCode), zap.String("slave meter", relation.SlaveMeter), zap.Error(err)) + tx.Rollback(ctx) + return err + case !ok: + ms.log.Error("未能将旧表计的关联信息赋予新表计。", zap.String("master meter", newMeterCode), zap.String("slave meter", relation.SlaveMeter)) + tx.Rollback(ctx) + return fmt.Errorf("旧表计的关联信息未能成功赋予新表计。") + } + } + default: + for _, relation := range oldRelations { + ok, err = repository.MeterRepository.BindMeter(tx, ctx, pid, relation.MasterMeter, newMeterCode) + switch { + case err != nil: + ms.log.Error("无法将旧表计的关联信息赋予新表计。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", newMeterCode), zap.Error(err)) + tx.Rollback(ctx) + return err + case !ok: + ms.log.Error("未能将旧表计的关联信息赋予新表计。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", newMeterCode)) + tx.Rollback(ctx) + return fmt.Errorf("旧表计的关联信息未能成功赋予新表计。") + } + } + } + + // 步骤10:提交事务 + err = tx.Commit(ctx) + if err != nil { + ms.log.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + + return nil +} + +// 列出园区中指定公摊表计下的所有关联表计 +func (ms _MeterService) ListPooledMeterRelations(pid, masterMeter string) ([]*model.MeterDetail, error) { + ms.log.Info("列出园区中指定公摊表计下的所有关联表计", zap.String("park id", pid), zap.String("meter code", masterMeter)) + relations, err := repository.MeterRepository.ListPooledMeterRelations(pid, masterMeter) + if err != nil { + ms.log.Error("无法列出园区中指定公摊表计下的所有关联关系。", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + relatedMeterCodes := lo.Map(relations, func(element *model.MeterRelation, _ int) string { + return element.SlaveMeter + }) + meters, err := repository.MeterRepository.ListMetersByIDs(pid, relatedMeterCodes) + if err != nil { + ms.log.Error("无法列出园区中指定公摊表计下的所有关联表计详细信息。", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + + return meters, nil +} + +// 列出指定园区中所有的公摊表计 +func (ms _MeterService) SearchPooledMetersDetail(pid string, page uint, keyword *string) ([]*model.PooledMeterDetailCompound, int64, error) { + ms.log.Info("列出指定园区中所有的公摊表计", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", *keyword)) + cacheConditions := []string{ + pid, + fmt.Sprintf("%d", page), + tools.DefaultTo(keyword, "UNDEFINED"), + } + if meters, total, err := cache.RetrievePagedSearch[[]*model.PooledMeterDetailCompound]("assemble_pooled_meters_detail", cacheConditions...); err == nil { + ms.log.Info("已经从缓存中获取到了指定园区中所有的公摊表计。", zap.Int("count", len(*meters)), zap.Int64("total", total)) + return *meters, total, nil + } + + poolingMeters, total, err := repository.MeterRepository.ListPoolingMeters(pid, page, keyword) + if err != nil { + ms.log.Error("无法列出指定园区中所有的公摊表计。", zap.Error(err)) + return make([]*model.PooledMeterDetailCompound, 0), 0, err + } + poolingMeterIds := lo.Map(poolingMeters, func(element *model.MeterDetail, _ int) string { + return element.Code + }) + relations, err := repository.MeterRepository.ListPooledMeterRelationsByCodes(pid, poolingMeterIds) + if err != nil { + ms.log.Error("无法列出指定园区中所有的公摊表计关联关系。", zap.Error(err)) + return make([]*model.PooledMeterDetailCompound, 0), 0, err + } + slaveMeters, err := repository.MeterRepository.ListMetersByIDs(pid, lo.Map(relations, func(element *model.MeterRelation, _ int) string { + return element.SlaveMeter + })) + if err != nil { + ms.log.Error("无法列出指定园区中所有的公摊表计的关联表计详细信息。", zap.Error(err)) + return make([]*model.PooledMeterDetailCompound, 0), 0, err + } + var assembled []*model.PooledMeterDetailCompound = make([]*model.PooledMeterDetailCompound, 0) + for _, meter := range poolingMeters { + slaveIDs := lo.Map(lo.Filter( + relations, + func(element *model.MeterRelation, _ int) bool { + return element.MasterMeter == meter.Code + }), + func(element *model.MeterRelation, _ int) string { + return element.SlaveMeter + }, + ) + slaves := lo.Map(lo.Filter( + slaveMeters, + func(element *model.MeterDetail, _ int) bool { + return lo.Contains(slaveIDs, element.Code) + }), + func(element *model.MeterDetail, _ int) model.MeterDetail { + return *element + }, + ) + assembled = append(assembled, &model.PooledMeterDetailCompound{ + MeterDetail: *meter, + BindMeters: slaves, + }) + } + + cache.CachePagedSearch(assembled, total, []string{fmt.Sprintf("meter:%s", pid), fmt.Sprintf("meter_relation:%s", pid)}, "assemble_pooled_meter_detail", cacheConditions...) + + return assembled, total, nil +} + +// 批量向园区中指定公摊表计下绑定关联表计 +func (ms _MeterService) BindMeter(pid, masterMeter string, slaveMeters []string) (bool, error) { + ms.log.Info("批量向园区中指定公摊表计下绑定关联表计", zap.String("park id", pid), zap.String("master meter", masterMeter), zap.Strings("slave meters", slaveMeters)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ms.log.Error("无法启动数据库事务。", zap.Error(err)) + return false, err + } + + for _, slave := range slaveMeters { + ok, err := repository.MeterRepository.BindMeter(tx, ctx, pid, masterMeter, slave) + switch { + case err != nil: + ms.log.Error("无法向园区中指定公摊表计下绑定关联表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave), zap.Error(err)) + tx.Rollback(ctx) + return false, err + case !ok: + ms.log.Error("未能向园区中指定公摊表计下绑定关联表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave)) + tx.Rollback(ctx) + return false, fmt.Errorf("未能成功向园区中指定公摊表计下绑定关联表计。") + } + } + + err = tx.Commit(ctx) + if err != nil { + ms.log.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return true, nil +} + +// 批量解绑园区中指定表计下的指定表计 +func (ms _MeterService) UnbindMeter(pid, masterMeter string, slaveMeters []string) (bool, error) { + ms.log.Info("批量解绑园区中指定表计下的指定表计", zap.String("park id", pid), zap.String("master meter", masterMeter), zap.Strings("slave meters", slaveMeters)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ms.log.Error("无法启动数据库事务。", zap.Error(err)) + return false, err + } + + for _, slave := range slaveMeters { + ok, err := repository.MeterRepository.UnbindMeter(tx, ctx, pid, masterMeter, slave) + switch { + case err != nil: + ms.log.Error("无法解绑园区中指定表计下的指定表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave), zap.Error(err)) + tx.Rollback(ctx) + return false, err + case !ok: + ms.log.Error("未能解绑园区中指定表计下的指定表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave)) + tx.Rollback(ctx) + return false, fmt.Errorf("未能成功解绑园区中指定表计下的指定表计。") + } + } + + err = tx.Commit(ctx) + if err != nil { + ms.log.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return true, nil +} + +// 查询符合条件的表计读数记录 +func (ms _MeterService) SearchMeterReadings(pid string, building *string, start, end *types.Date, page uint, keyword *string) ([]*model.DetailedMeterReading, int64, error) { + ms.log.Info( + "查询符合条件的表计读数记录", + zap.String("park id", pid), + zap.Stringp("building", building), + logger.DateFieldp("start", start), + logger.DateFieldp("end", end), + zap.Uint("page", page), + zap.Stringp("keyword", keyword), + ) + readings, total, err := repository.MeterRepository.ListMeterReadings(pid, keyword, page, start, end, building) + if err != nil { + ms.log.Error("无法查询符合条件的表计读数记录。", zap.Error(err)) + return make([]*model.DetailedMeterReading, 0), 0, err + } + + meterCodes := lo.Map(readings, func(element *model.MeterReading, _ int) string { + return element.Meter + }) + meterDetails, err := repository.MeterRepository.ListMetersByIDs(pid, meterCodes) + if err != nil { + ms.log.Error("无法查询符合条件的表计读数记录的表计详细信息。", zap.Error(err)) + return make([]*model.DetailedMeterReading, 0), 0, err + } + assembles := lo.Map( + readings, + func(element *model.MeterReading, _ int) *model.DetailedMeterReading { + meter, _ := lo.Find(meterDetails, func(detail *model.MeterDetail) bool { + return detail.Code == element.Meter + }) + return &model.DetailedMeterReading{ + Detail: *meter, + Reading: *element, + } + }, + ) + + return assembles, total, nil +} + +// 创建一条新的表计抄表记录 +func (ms _MeterService) RecordReading(pid, meterCode string, form *vo.MeterReadingForm) error { + ms.log.Info("创建一条新的表计抄表记录", zap.String("park id", pid), zap.String("meter code", meterCode)) + meter, err := repository.MeterRepository.FetchMeterDetail(pid, meterCode) + if err != nil || meter == nil { + ms.log.Error("无法找到指定的表计", zap.Error(err)) + return fmt.Errorf("无法找到指定的表计:%w", err) + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ms.log.Error("无法启动数据库事务。", zap.Error(err)) + return err + } + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meter.MeterType, meter.Ratio, form) + if err != nil { + ms.log.Error("无法创建一条新的表计抄表记录。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + if !ok { + ms.log.Error("未能创建一条新的表计抄表记录。") + tx.Rollback(ctx) + return fmt.Errorf("未能成功创建一条新的表计抄表记录。") + } + + err = tx.Commit(ctx) + if err != nil { + ms.log.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + + return nil +} + +// 获取指定园区的全部待抄表计列表,并将其输出到Excel文件模板中,提供生成文件的二进制内容 +func (ms _MeterService) GenerateParkMeterReadingTemplate(pid string, meters []*model.SimpleMeterDocument) ([]byte, error) { + return nil, nil +} + +// 处理上传的Excel格式的表计抄表记录,所有满足审查条件的记录都将被保存到数据库中。 +// 无论峰谷表计还是普通表计,只要抄表记录中不存在峰谷数据,都将自动使用平段配平。 +func (ms _MeterService) BatchImportReadings(pid string, uploadContent []byte) error { + // 步骤1:将解析到的数据转换成创建表单数据 + // 步骤2:对目前已经解析到的数据进行合法性检测,检测包括表计编号在同一抄表时间是否重复 + // 步骤3:从数据库中获取当前园区中已有的表计编号 + // 步骤4.0:启动数据库事务 + // 步骤4.1:对比检查数据库中的表计编号与上传文件中的表计编号是否存在差异。非差异内容将直接保存 + // 步骤4.1.1:抄表的表计在数据库中已经存在,可以直接保存起数据。 + // 步骤4.1.2:抄表表计在数据库中不存在,需要将其记录进入错误。 + // 步骤4.3:如果批处理过程中存在错误,撤销全部导入动作。 + // 步骤5:执行事务,更新数据库,获取完成更改的行数。 + return nil +} diff --git a/vo/meter.go b/vo/meter.go new file mode 100644 index 0000000..9671097 --- /dev/null +++ b/vo/meter.go @@ -0,0 +1,38 @@ +package vo + +import "github.com/shopspring/decimal" + +type MeterCreationForm struct { + Code string `json:"code"` + Address *string `json:"address"` + Ratio decimal.Decimal `json:"ratio"` + Seq int64 `json:"seq"` + MeterType int16 `json:"meterType"` + Building *string `json:"building"` + OnFloor *string `json:"onFloor"` + Area decimal.NullDecimal `json:"area"` + Enabled bool `json:"enabled"` + MeterReadingForm `json:"-"` +} + +type MeterModificationForm struct { + Address *string `json:"address"` + Seq int64 `json:"seq"` + Ratio decimal.Decimal `json:"ratio"` + Enabled bool `json:"enabled"` + MeterType int16 `json:"meterType"` + Building *string `json:"building"` + OnFloor *string `json:"onFloor"` + Area decimal.NullDecimal `json:"area"` +} + +type NewMeterForReplacingForm struct { + Code string `json:"code"` + Ratio decimal.Decimal `json:"ratio"` + Reading MeterReadingForm `json:"reading"` +} + +type MeterReplacingForm struct { + OldReading MeterReadingForm `json:"oldReading"` + NewMeter NewMeterForReplacingForm `json:"newMeter"` +} diff --git a/vo/reading.go b/vo/reading.go new file mode 100644 index 0000000..0ce357b --- /dev/null +++ b/vo/reading.go @@ -0,0 +1,16 @@ +package vo + +import ( + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type MeterReadingForm struct { + 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"` + ReadAt *types.DateTime `json:"readAt"` +} From 62560e4eeba32f5f0d9e8b369b3b28ecde043964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 21:08:36 +0800 Subject: [PATCH 032/141] =?UTF-8?q?fix(user):=E8=A1=A5=E5=85=85=E6=B8=85?= =?UTF-8?q?=E7=90=86=E7=94=A8=E6=88=B7=E9=83=A8=E5=88=86=E7=9A=84=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repository/user.go b/repository/user.go index b0ed5c8..04547e4 100644 --- a/repository/user.go +++ b/repository/user.go @@ -48,7 +48,7 @@ func (ur _UserRepository) FindUserByUsername(username string) (*model.User, erro ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) return nil, err } - cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", username)}, "user", username) + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", username), fmt.Sprintf("user:%s", user.Id)}, "user", username) return &user, nil } From 40abbcfb8c7168d5160531799f1eca928ad02c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 21:35:34 +0800 Subject: [PATCH 033/141] =?UTF-8?q?fix(charge):=E6=94=B9=E8=BF=9B=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=85=85=E5=80=BC=E9=83=A8=E5=88=86=E7=9A=84=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/charge.go | 12 ++---------- model/charge.go | 39 +++++++++++++++++++-------------------- repository/charge.go | 13 ++++++------- service/charge.go | 9 ++++----- types/date.go | 22 ++++++++++++++++++++++ 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/controller/charge.go b/controller/charge.go index f03750b..86b83cb 100644 --- a/controller/charge.go +++ b/controller/charge.go @@ -27,16 +27,8 @@ func searchCharges(c *fiber.Ctx) error { result := response.NewResult(c) keyword := c.Query("keyword", "") page := c.QueryInt("page", 1) - beginTime, err := types.ParseDate(c.Query("begin")) - if err != nil { - chargeLog.Error("无法解析查询起始时间。", zap.Error(err)) - return result.Error(http.StatusInternalServerError, err.Error()) - } - endTime, err := types.ParseDate(c.Query("end")) - if err != nil { - chargeLog.Error("无法解析查询结束时间。", zap.Error(err)) - return result.Error(http.StatusInternalServerError, err.Error()) - } + beginTime := types.ParseDateWithDefault(c.Query("begin"), types.NewEmptyDate()) + endTime := types.ParseDateWithDefault(c.Query("end"), types.MaxDate()) charges, total, err := repository.ChargeRepository.FindCharges(uint(page), &beginTime, &endTime, &keyword) if err != nil { chargeLog.Error("检索用户的充值记录列表失败。", zap.Error(err)) diff --git a/model/charge.go b/model/charge.go index 27bdbbd..d09783c 100644 --- a/model/charge.go +++ b/model/charge.go @@ -2,30 +2,29 @@ package model import ( "electricity_bill_calc/types" - "time" - - "github.com/shopspring/decimal" ) type UserChargeDetail struct { - Seq int64 `json:"seq"` - UserId string `json:"user_id"` - Name string `json:"name"` - Fee *decimal.Decimal `json:"fee"` - Discount *decimal.Decimal `json:"discount"` - Amount *decimal.Decimal `json:"amount"` - ChargeTo types.Date `json:"charge_to"` - Settled bool `json:"settled"` - SettledAt *time.Time `json:"settled_at"` - Cancelled bool `json:"cancelled"` - CancelledAt *time.Time `json:"cancelled_at"` - CreatedAt time.Time `json:"created_at"` + Seq int64 `json:"seq"` + UserId string `json:"userId" db:"user_id"` + Name string `json:"name"` + Fee *float64 `json:"fee"` + Discount *float64 `json:"discount"` + Amount *float64 `json:"amount"` + ChargeTo types.Date `json:"chargeTo"` + Settled bool `json:"settled"` + SettledAt *types.DateTime `json:"settledAt"` + Cancelled bool `json:"cancelled"` + CancelledAt *types.DateTime `json:"cancelledAt"` + Refunded bool `json:"refunded"` + RefundedAt *types.DateTime `json:"refundedAt"` + CreatedAt types.DateTime `json:"createdAt"` } type ChargeRecordCreationForm struct { - UserId string `json:"userId"` - Fee *decimal.Decimal `json:"fee"` - Discount *decimal.Decimal `json:"discount"` - Amount *decimal.Decimal `json:"amount"` - ChargeTo types.Date `json:"chargeTo"` + UserId string `json:"userId"` + Fee *float64 `json:"fee"` + Discount *float64 `json:"discount"` + Amount *float64 `json:"amount"` + ChargeTo types.Date `json:"chargeTo"` } diff --git a/repository/charge.go b/repository/charge.go index eb1419a..1857b66 100644 --- a/repository/charge.go +++ b/repository/charge.go @@ -16,7 +16,6 @@ import ( "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "github.com/samber/lo" - "github.com/shopspring/decimal" "go.uber.org/zap" ) @@ -39,7 +38,7 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Dat tools.CondFn(func(t *types.Date) bool { return t != nil }, beginTime, beginTime.Format("2006-01-02"), "UNDEF"), tools.CondFn(func(t *types.Date) bool { return t != nil }, endTime, endTime.Format("2006-01-02"), "UNDEF"), } - if charges, total, err := cache.RetrievePagedSearch[[]*model.UserChargeDetail]("charges", cacheConditions...); err == nil { + if charges, total, err := cache.RetrievePagedSearch[[]*model.UserChargeDetail]("charges", cacheConditions...); err == nil && charges != nil { cr.log.Info("从缓存中获取用户的充值记录成功。", zap.Int("count", len(*charges)), zap.Int64("total", total)) return *charges, total, nil } @@ -49,15 +48,15 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Dat chargeQuery := cr.ds. From(goqu.T("user_charge").As("c")). Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))). - Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.user_id").Eq(goqu.I("u.id")))). + Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.id").Eq(goqu.I("u.id")))). Select( "c.seq", "c.user_id", "ud.name", "c.fee", "c.discount", "c.amount", "c.charge_to", - "c.settled", "c.settled_at", "c.cancelled", "c.cancelled_at", "c.created_at", + "c.settled", "c.settled_at", "c.cancelled", "c.cancelled_at", "c.refunded", "c.refunded_at", "c.created_at", ) countQuery := cr.ds. From(goqu.T("user_charge").As("c")). Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))). - Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.user_id").Eq(goqu.I("u.id")))). + Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.id").Eq(goqu.I("u.id")))). Select(goqu.COUNT("*")) if keyword != nil && len(*keyword) > 0 { @@ -84,7 +83,7 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Dat countQuery = countQuery.Where(goqu.I("c.charge_to").Lte(*endTime)) } - chargeQuery = chargeQuery.Order(goqu.I("c.created_by").Desc()) + chargeQuery = chargeQuery.Order(goqu.I("c.created_at").Desc()) currentPostion := (page - 1) * config.ServiceSettings.ItemsPageSize chargeQuery = chargeQuery.Offset(currentPostion).Limit(config.ServiceSettings.ItemsPageSize) @@ -109,7 +108,7 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Dat } // 在用户充值记录中创建一条新的记录 -func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, uid string, fee, discount, amount *decimal.Decimal, chargeTo types.Date) (bool, error) { +func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, uid string, fee, discount, amount *float64, chargeTo types.Date) (bool, error) { createQuery, createArgs, _ := cr.ds. Insert(goqu.T("user_charge")). Cols("user_id", "fee", "discount", "amount", "charge_to", "created_at"). diff --git a/service/charge.go b/service/charge.go index 4bf7b92..807e617 100644 --- a/service/charge.go +++ b/service/charge.go @@ -8,7 +8,6 @@ import ( "electricity_bill_calc/types" "fmt" - "github.com/shopspring/decimal" "go.uber.org/zap" ) @@ -21,13 +20,13 @@ var ChargeService = &_ChargeService{ } // 创建一条新的用户充值记录,同时更新用户的服务期限 -func (cs _ChargeService) RecordUserCharge(uid string, fee, discount, amount *decimal.Decimal, chargeTo types.Date, extendExpriationIgnoringSettle bool) (bool, error) { +func (cs _ChargeService) RecordUserCharge(uid string, fee, discount, amount *float64, chargeTo types.Date, extendExpriationIgnoringSettle bool) (bool, error) { cs.log.Info( "创建一条新的用户充值记录。", zap.String("uid", uid), - logger.DecimalFieldp("fee", fee), - logger.DecimalFieldp("discount", discount), - logger.DecimalFieldp("amount", amount), + zap.Float64p("fee", fee), + zap.Float64p("discount", discount), + zap.Float64p("amount", amount), logger.DateField("chargeTo", chargeTo), zap.Bool("extendExpriationIgnoringSettle", extendExpriationIgnoringSettle), ) diff --git a/types/date.go b/types/date.go index e9c8e8d..6713219 100644 --- a/types/date.go +++ b/types/date.go @@ -26,11 +26,22 @@ func NewEmptyDate() Date { } } +func MinDate() Date { + return NewDate(1, 1, 1) +} + +func MaxDate() Date { + return NewDate(9999, 12, 31) +} + func NowDate() Date { return Now().Date() } func ParseDate(t string) (Date, error) { + if len(t) == 0 { + return NewEmptyDate(), fmt.Errorf("不能解析空白的日期时间。") + } d, err := time.ParseInLocation("2006-01-02", t, loc) if err != nil { return NewEmptyDate(), fmt.Errorf("无法解析给定的日期, %w", err) @@ -40,6 +51,17 @@ func ParseDate(t string) (Date, error) { }, nil } +func ParseDateWithDefault(t string, defaultDate Date) Date { + if len(t) == 0 { + return defaultDate + } + d, err := ParseDate(t) + if err != nil { + return defaultDate + } + return d +} + var _ driver.Valuer = (*Date)(nil) func (dt Date) Value() (driver.Value, error) { From 33ccd7e0b643a3b00bb6e39bdcb9cdc8012b42b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 21:40:54 +0800 Subject: [PATCH 034/141] =?UTF-8?q?fix(charge):=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=85=85=E5=80=BC=E9=83=A8=E5=88=86=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E7=9B=B8=E5=85=B3=E6=9F=A5=E8=AF=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/charge.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repository/charge.go b/repository/charge.go index 1857b66..573badb 100644 --- a/repository/charge.go +++ b/repository/charge.go @@ -74,13 +74,13 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Dat } if beginTime != nil { - chargeQuery = chargeQuery.Where(goqu.I("c.charge_to").Gte(*beginTime)) - countQuery = countQuery.Where(goqu.I("c.charge_to").Gte(*beginTime)) + chargeQuery = chargeQuery.Where(goqu.I("c.created_at").Gte(beginTime.ToBeginningOfDate())) + countQuery = countQuery.Where(goqu.I("c.created_at").Gte(beginTime.ToBeginningOfDate())) } if endTime != nil { - chargeQuery = chargeQuery.Where(goqu.I("c.charge_to").Lte(*endTime)) - countQuery = countQuery.Where(goqu.I("c.charge_to").Lte(*endTime)) + chargeQuery = chargeQuery.Where(goqu.I("c.created_at").Lte(endTime.ToEndingOfDate())) + countQuery = countQuery.Where(goqu.I("c.created_at").Lte(endTime.ToEndingOfDate())) } chargeQuery = chargeQuery.Order(goqu.I("c.created_at").Desc()) @@ -92,7 +92,7 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Dat countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() var ( - charges []*model.UserChargeDetail + charges []*model.UserChargeDetail = make([]*model.UserChargeDetail, 0) total int64 ) if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil { From 0c725e99a42527bc00b8abe56030c78ad6bc1b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 21:47:04 +0800 Subject: [PATCH 035/141] =?UTF-8?q?fix(charge):=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=85=85=E5=80=BC=E8=AE=B0=E5=BD=95=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99=E5=AF=B9=E4=BA=8E=E6=95=B0=E5=80=BC?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=86=85=E5=AE=B9=E8=A7=A3=E6=9E=90=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/charge.go | 9 ++++++--- model/charge.go | 12 +++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/controller/charge.go b/controller/charge.go index 86b83cb..2d057b1 100644 --- a/controller/charge.go +++ b/controller/charge.go @@ -50,11 +50,14 @@ func createNewUserChargeRecord(c *fiber.Ctx) error { chargeLog.Error("无法解析创建充值记录的请求数据。", zap.Error(err)) return result.Error(http.StatusBadRequest, err.Error()) } + fee, _ := createionForm.Fee.Float64() + discount, _ := createionForm.Discount.Float64() + amount, _ := createionForm.Amount.Float64() ok, err := service.ChargeService.RecordUserCharge( createionForm.UserId, - createionForm.Fee, - createionForm.Discount, - createionForm.Amount, + &fee, + &discount, + &amount, createionForm.ChargeTo, true, ) diff --git a/model/charge.go b/model/charge.go index d09783c..b8357b4 100644 --- a/model/charge.go +++ b/model/charge.go @@ -2,6 +2,8 @@ package model import ( "electricity_bill_calc/types" + + "github.com/shopspring/decimal" ) type UserChargeDetail struct { @@ -22,9 +24,9 @@ type UserChargeDetail struct { } type ChargeRecordCreationForm struct { - UserId string `json:"userId"` - Fee *float64 `json:"fee"` - Discount *float64 `json:"discount"` - Amount *float64 `json:"amount"` - ChargeTo types.Date `json:"chargeTo"` + UserId string `json:"userId"` + Fee *decimal.Decimal `json:"fee"` + Discount *decimal.Decimal `json:"discount"` + Amount *decimal.Decimal `json:"amount"` + ChargeTo types.Date `json:"chargeTo"` } From 7f43965f1cf0d9187bf25a037ae67b01d3b9e8e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 21:59:01 +0800 Subject: [PATCH 036/141] =?UTF-8?q?fix(charge):=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=94=A8=E6=88=B7=E5=85=85=E5=80=BC=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=9A=84=E6=B5=8B=E8=AF=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/charge.go | 6 +++--- model/charge.go | 10 +++++----- repository/user.go | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/controller/charge.go b/controller/charge.go index 2d057b1..ce5282c 100644 --- a/controller/charge.go +++ b/controller/charge.go @@ -50,9 +50,9 @@ func createNewUserChargeRecord(c *fiber.Ctx) error { chargeLog.Error("无法解析创建充值记录的请求数据。", zap.Error(err)) return result.Error(http.StatusBadRequest, err.Error()) } - fee, _ := createionForm.Fee.Float64() - discount, _ := createionForm.Discount.Float64() - amount, _ := createionForm.Amount.Float64() + fee, _ := createionForm.Fee.Decimal.Float64() + discount, _ := createionForm.Discount.Decimal.Float64() + amount, _ := createionForm.Amount.Decimal.Float64() ok, err := service.ChargeService.RecordUserCharge( createionForm.UserId, &fee, diff --git a/model/charge.go b/model/charge.go index b8357b4..f0d1c18 100644 --- a/model/charge.go +++ b/model/charge.go @@ -24,9 +24,9 @@ type UserChargeDetail struct { } type ChargeRecordCreationForm struct { - UserId string `json:"userId"` - Fee *decimal.Decimal `json:"fee"` - Discount *decimal.Decimal `json:"discount"` - Amount *decimal.Decimal `json:"amount"` - ChargeTo types.Date `json:"chargeTo"` + UserId string `json:"userId"` + Fee decimal.NullDecimal `json:"fee"` + Discount decimal.NullDecimal `json:"discount"` + Amount decimal.NullDecimal `json:"amount"` + ChargeTo types.Date `json:"chargeTo"` } diff --git a/repository/user.go b/repository/user.go index 04547e4..7610603 100644 --- a/repository/user.go +++ b/repository/user.go @@ -401,7 +401,7 @@ func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) { } // 检索条目数量有限的用户详细信息 -func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) ([]*model.UserDetail, error) { +func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) ([]*model.UserWithDetail, error) { ur.log.Info("检索条目数量有限的用户详细信息。", zap.Int16p("user type", userType), zap.Uint("limit", limit), zap.Stringp("keyword", keyword)) actualUserType := tools.DefaultTo(userType, model.USER_TYPE_ENT) cacheConditions := []string{ @@ -409,13 +409,13 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, tools.DefaultTo(keyword, ""), fmt.Sprintf("%d", limit), } - if users, err := cache.RetrieveSearch[[]*model.UserDetail]("user_with_detail_limited", cacheConditions...); err == nil && users != nil { + if users, err := cache.RetrieveSearch[[]*model.UserWithDetail]("user_with_detail_limited", cacheConditions...); err == nil && users != nil { return *users, nil } ctx, cancel := global.TimeoutContext() defer cancel() - var users []*model.UserDetail + var users = make([]*model.UserWithDetail, 0) userQuery := ur.ds. From(goqu.T("user").As("u")). Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})). @@ -442,7 +442,7 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, userQuery.Order(goqu.I("u.created_at").Desc()).Limit(limit) userSql, userParams, _ := userQuery.Prepared(true).ToSQL() - if err := pgxscan.Select(ctx, global.DB, users, userSql, userParams...); err != nil { + if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) return nil, err } From c302b32367316eb1175469f647c0bf560bf12874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 22:08:10 +0800 Subject: [PATCH 037/141] =?UTF-8?q?fix(park):=E4=BF=AE=E6=94=B9=E5=9B=AD?= =?UTF-8?q?=E5=8C=BA=E4=BF=A1=E6=81=AF=E7=BC=96=E8=BE=91=E6=97=B6=E7=BB=99?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E6=8F=90=E7=A4=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/park.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/park.go b/controller/park.go index 1c70ff6..eb68a84 100644 --- a/controller/park.go +++ b/controller/park.go @@ -159,7 +159,7 @@ func modifySpecificPark(c *fiber.Ctx) error { parkLog.Error("无法更新园区信息。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } - return result.Updated("已获取到指定园区的详细信息") + return result.Updated("已更新指定园区的详细信息") } // 修改指定园区的可用性 From ea1c1d78298cd99f1143ac2a22fe821c5837e8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 22:14:27 +0800 Subject: [PATCH 038/141] =?UTF-8?q?fix(park):=E4=BF=AE=E6=AD=A3=E5=9B=AD?= =?UTF-8?q?=E5=8C=BA=E8=AF=A6=E7=BB=86=E4=BF=A1=E6=81=AF=E7=9A=84=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/park.go | 12 ++++++------ repository/park.go | 6 ++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/model/park.go b/model/park.go index c020948..b3af1fb 100644 --- a/model/park.go +++ b/model/park.go @@ -11,15 +11,15 @@ type Park struct { UserId string `json:"userId"` Name string `json:"name"` Area decimal.NullDecimal `json:"area"` - TenementQuantity decimal.NullDecimal `json:"tenementQuantity"` + TenementQuantity decimal.NullDecimal `json:"tenement"` Capacity decimal.NullDecimal `json:"capacity"` Category int16 `json:"category"` - MeterType int16 `json:"meter04kvType"` + MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"` PricePolicy int16 `json:"pricePolicy"` - BasicPooled int16 `json:"basicPooled"` - AdjustPooled int16 `json:"adjustPooled"` - LossPooled int16 `json:"lossPooled"` - PublicPooled int16 `json:"publicPooled"` + BasicPooled int16 `json:"basicDiluted"` + AdjustPooled int16 `json:"adjustDiluted"` + LossPooled int16 `json:"lossDiluted"` + PublicPooled int16 `json:"publicDiluted"` TaxRate decimal.NullDecimal `json:"taxRate"` Region *string `json:"region"` Address *string `json:"address"` diff --git a/repository/park.go b/repository/park.go index 516552d..03f7f4a 100644 --- a/repository/park.go +++ b/repository/park.go @@ -141,6 +141,12 @@ func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) { var park model.Park parkSql, parkArgs, _ := pr.ds. From("park"). + Select( + "id", "user_id", "name", "area", "tenement_quantity", "capacity", "category", + "meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate", + "basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at", + "deleted_at", + ). Where(goqu.I("id").Eq(pid)). Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &park, parkSql, parkArgs...); err != nil { From 2f17853dc0dfab1395f38157f55ec5e7a9aef5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 22:15:30 +0800 Subject: [PATCH 039/141] =?UTF-8?q?fix(park):=E4=BF=AE=E6=94=B9=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=8C=87=E5=AE=9A=E5=9B=AD=E5=8C=BA=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E6=80=A7=E6=97=B6=E7=9A=84=E6=8F=90=E7=A4=BA=E8=AF=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/park.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/park.go b/controller/park.go index eb68a84..0415709 100644 --- a/controller/park.go +++ b/controller/park.go @@ -194,7 +194,7 @@ func modifyParkEnabling(c *fiber.Ctx) error { parkLog.Error("无法更新园区可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) } - return result.Updated("已获取到指定园区的详细信息") + return result.Updated("已更新指定园区的可用性。") } // 删除指定的园区 From 2d17bd5f0d635085e185a1b9697ac0de058fae65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 8 Jun 2023 22:23:56 +0800 Subject: [PATCH 040/141] =?UTF-8?q?fix(park):=E4=BF=AE=E6=AD=A3=E5=9B=AD?= =?UTF-8?q?=E5=8C=BA=E4=BF=A1=E6=81=AF=E6=A3=80=E7=B4=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/park.go | 4 ++-- repository/park.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/controller/park.go b/controller/park.go index 0415709..68f27c2 100644 --- a/controller/park.go +++ b/controller/park.go @@ -57,7 +57,7 @@ func listParksBelongsToCurrentUser(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkLog.Info("列出当前用户下的全部园区", zap.String("user id", session.Uid)) - parks, err := repository.ParkRepository.RetrieveParkbelongs(session.Uid) + parks, err := repository.ParkRepository.ListAllParks(session.Uid) if err != nil { parkLog.Error("无法获取园区列表。", zap.String("user id", session.Uid)) return result.Error(http.StatusInternalServerError, err.Error()) @@ -70,7 +70,7 @@ func listParksBelongsTo(c *fiber.Ctx) error { result := response.NewResult(c) userId := c.Params("userId") parkLog.Info("列出指定用户下的全部园区", zap.String("user id", userId)) - parks, err := repository.ParkRepository.RetrieveParkbelongs(userId) + parks, err := repository.ParkRepository.RetrieveParkBelongs(userId) if err != nil { parkLog.Error("无法获取园区列表。", zap.String("user id", userId)) return result.Error(http.StatusInternalServerError, err.Error()) diff --git a/repository/park.go b/repository/park.go index 03f7f4a..0e83697 100644 --- a/repository/park.go +++ b/repository/park.go @@ -40,9 +40,15 @@ func (pr _ParkRepository) ListAllParks(uid string) ([]*model.Park, error) { ctx, cancel := global.TimeoutContext() defer cancel() - var parks []*model.Park + var parks = make([]*model.Park, 0) parkQuerySql, parkParams, _ := pr.ds. From("park"). + Select( + "id", "user_id", "name", "area", "tenement_quantity", "capacity", "category", + "meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate", + "basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at", + "deleted_at", + ). Where( goqu.I("user_id").Eq(uid), goqu.I("deleted_at").IsNull(), @@ -160,7 +166,7 @@ func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) { } // 获取园区对应的用户ID -func (pr _ParkRepository) RetrieveParkbelongs(pid string) (string, error) { +func (pr _ParkRepository) RetrieveParkBelongs(pid string) (string, error) { pr.log.Info("获取园区对应的用户ID", zap.String("pid", pid)) if uid, err := cache.RetrieveEntity[string]("park_belongs", pid); err == nil && uid != nil { pr.log.Info("已经从缓存获取到了园区对应的用户ID。") From 005f9020a190a59172feb0b9228552f0774a7f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 9 Jun 2023 06:06:04 +0800 Subject: [PATCH 041/141] =?UTF-8?q?fix(park):=E4=BF=AE=E6=AD=A3=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=8C=87=E5=AE=9A=E7=94=A8=E6=88=B7=E5=9B=AD=E5=8C=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/park.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/park.go b/controller/park.go index 68f27c2..2733f41 100644 --- a/controller/park.go +++ b/controller/park.go @@ -68,9 +68,9 @@ func listParksBelongsToCurrentUser(c *fiber.Ctx) error { // 列出隶属于指定用户的全部园区 func listParksBelongsTo(c *fiber.Ctx) error { result := response.NewResult(c) - userId := c.Params("userId") + userId := c.Params("uid") parkLog.Info("列出指定用户下的全部园区", zap.String("user id", userId)) - parks, err := repository.ParkRepository.RetrieveParkBelongs(userId) + parks, err := repository.ParkRepository.ListAllParks(userId) if err != nil { parkLog.Error("无法获取园区列表。", zap.String("user id", userId)) return result.Error(http.StatusInternalServerError, err.Error()) From ab2bcb3cf63c590bdc2d88e9bfb5490c86dc6724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 10 Jun 2023 06:27:31 +0800 Subject: [PATCH 042/141] =?UTF-8?q?enhance(types):=E5=96=82=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E6=97=B6=E9=97=B4=E7=B1=BB=E5=9E=8B=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=9C=89=E5=AD=97=E7=AC=A6=E4=B8=B2=E8=A7=A3=E6=9E=90=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/datetime.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/types/datetime.go b/types/datetime.go index 74bbd5a..ea5a41e 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -22,6 +22,12 @@ func Now() DateTime { } } +func NewEmptyDateTime() DateTime { + return DateTime{ + Time: time.Time{}.In(loc), + } +} + func Timestamp() int64 { startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() return Now().Unix() - startline @@ -33,6 +39,19 @@ func FromTime(t time.Time) DateTime { } } +func ParseDateTime(t string) (DateTime, error) { + if len(t) == 0 { + return NewEmptyDateTime(), fmt.Errorf("不能解析空白的日期时间。") + } + d, err := time.ParseInLocation("2006-01-02 13:04:05", t, loc) + if err != nil { + return NewEmptyDateTime(), fmt.Errorf("无法解析给定的日期, %w", err) + } + return DateTime{ + Time: d, + }, nil +} + var _ driver.Valuer = (*DateTime)(nil) func (dt DateTime) Value() (driver.Value, error) { From 48753eb3f05cf344f5491fbc670836db32c31c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 10 Jun 2023 06:28:04 +0800 Subject: [PATCH 043/141] =?UTF-8?q?enhance(excel):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=97=A5=E6=9C=9F=E4=B8=8E=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=86=85=E5=AE=B9=E7=9A=84=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/abstract.go | 65 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/excel/abstract.go b/excel/abstract.go index 2d98237..932a760 100644 --- a/excel/abstract.go +++ b/excel/abstract.go @@ -2,6 +2,7 @@ package excel import ( "electricity_bill_calc/tools" + "electricity_bill_calc/types" "encoding/json" "errors" "fmt" @@ -21,7 +22,7 @@ type ExcelTemplateGenerator interface { } type ColumnRecognizer struct { - Pattern []string + Pattern [][]string Tag string MatchIndex int MustFill bool @@ -43,7 +44,7 @@ type ExcelAnalysisError struct { Err AnalysisError `json:"error"` } -func NewColumnRecognizer(tag string, patterns ...string) ColumnRecognizer { +func NewColumnRecognizer(tag string, patterns ...[]string) ColumnRecognizer { return ColumnRecognizer{ Pattern: patterns, Tag: tag, @@ -65,9 +66,17 @@ func (e ExcelAnalysisError) Error() string { func (r *ColumnRecognizer) Recognize(cellValue string) bool { matches := make([]bool, 0) - for _, p := range r.Pattern { - matches = append(matches, strings.Contains(cellValue, p)) + for _, pG := range r.Pattern { + groupMatch := make([]bool, 0) + for _, p := range pG { + groupMatch = append(groupMatch, strings.Contains(cellValue, p)) + } + // 这句表示在每一个匹配组中,只要有一个匹配项,就算匹配成功 + matches = append(matches, lo.Reduce(groupMatch, func(acc, elem bool, index int) bool { + return acc || elem + }, false)) } + // 这句表示在尊有的匹配组中,必须全部的匹配组都完成匹配,才算匹配成功 return lo.Reduce(matches, func(acc, elem bool, index int) bool { return acc && elem }, true) @@ -187,6 +196,54 @@ func (a *ExcelAnalyzer[T]) Analysis(bean T) ([]T, []ExcelAnalysisError) { } else { actualField.SetBool(false) } + case "types.Date": + if len(matchValue) == 0 { + actualField.Set(reflect.ValueOf(types.NewEmptyDate())) + } else { + v, err := types.ParseDate(matchValue) + if err != nil { + errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}}) + actualField.Set(reflect.ValueOf(types.NewEmptyDate())) + } else { + actualField.Set(reflect.ValueOf(v)) + } + } + case "*types.Date": + if len(matchValue) == 0 { + actualField.Set(reflect.ValueOf(nil)) + } else { + v, err := types.ParseDate(matchValue) + if err != nil { + errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期格式。%w", err)}}) + actualField.Set(reflect.ValueOf(nil)) + } else { + actualField.Set(reflect.ValueOf(&v)) + } + } + case "types.DateTime": + if len(matchValue) == 0 { + actualField.Set(reflect.ValueOf(types.NewEmptyDateTime())) + } else { + v, err := types.ParseDateTime(matchValue) + if err != nil { + errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}}) + actualField.Set(reflect.ValueOf(types.NewEmptyDateTime())) + } else { + actualField.Set(reflect.ValueOf(v)) + } + } + case "*types.DateTime": + if len(matchValue) == 0 { + actualField.Set(reflect.ValueOf(nil)) + } else { + v, err := types.ParseDateTime(matchValue) + if err != nil { + errs = append(errs, ExcelAnalysisError{Row: rowIndex + 1, Col: recognizer.MatchIndex + 1, Err: AnalysisError{Err: fmt.Errorf("单元格内容应为日期时间格式。%w", err)}}) + actualField.Set(reflect.ValueOf(nil)) + } else { + actualField.Set(reflect.ValueOf(&v)) + } + } } } } From 0020776218d693d45fd52471804d142171aea3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 10 Jun 2023 06:37:34 +0800 Subject: [PATCH 044/141] =?UTF-8?q?enhance(excel):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E8=A1=A8=E8=AE=A1=E6=A1=A3=E6=A1=88=E5=92=8C?= =?UTF-8?q?=E6=8A=84=E8=A1=A8=E8=AE=B0=E5=BD=95=E4=B8=A4=E4=B8=AA=20Excel?= =?UTF-8?q?=20=E6=96=87=E4=BB=B6=E7=9A=84=E8=A7=A3=E6=9E=90=E5=99=A8?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/meter_archive.go | 27 +++++++++++++++++++++++++++ excel/meter_reading.go | 19 +++++++++++++++++++ model/meter.go | 31 ++++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 excel/meter_archive.go create mode 100644 excel/meter_reading.go diff --git a/excel/meter_archive.go b/excel/meter_archive.go new file mode 100644 index 0000000..8b80eaa --- /dev/null +++ b/excel/meter_archive.go @@ -0,0 +1,27 @@ +package excel + +import ( + "electricity_bill_calc/model" + "io" +) + +var meterArchiveRecognizers = []*ColumnRecognizer{ + {Pattern: [][]string{{"表号"}}, Tag: "code", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"表址", "地址", "户址"}}, Tag: "address", MatchIndex: -1}, + {Pattern: [][]string{{"类型"}}, Tag: "meterType", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"建筑"}}, Tag: "building", MatchIndex: -1}, + {Pattern: [][]string{{"楼层"}}, Tag: "onFloor", MatchIndex: -1}, + {Pattern: [][]string{{"面积"}}, Tag: "area", MatchIndex: -1}, + {Pattern: [][]string{{"倍率"}}, Tag: "ratio", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"序号"}}, Tag: "seq", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"抄表"}, {"时间", "日期"}}, Tag: "readAt", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"总"}}, Tag: "overall", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"尖"}}, Tag: "critical", MatchIndex: -1}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"峰"}}, Tag: "peak", MatchIndex: -1}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"平"}}, Tag: "flat", MatchIndex: -1}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1}, +} + +func NewMeterArchiveExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.MeterImportRow], error) { + return NewExcelAnalyzer[model.MeterImportRow](file, meterArchiveRecognizers) +} diff --git a/excel/meter_reading.go b/excel/meter_reading.go new file mode 100644 index 0000000..678e6df --- /dev/null +++ b/excel/meter_reading.go @@ -0,0 +1,19 @@ +package excel + +import ( + "electricity_bill_calc/model" + "io" +) + +var meterReadingsRecognizers = []*ColumnRecognizer{ + {Pattern: [][]string{{"表", "表计"}, {"编号"}}, Tag: "code", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"抄表", "结束"}, {"时间", "日期"}}, Tag: "readAt", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"用电", "有功", "表底", "底数"}, {"总", "量"}}, Tag: "overall", MatchIndex: -1, MustFill: true}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"尖", "尖锋"}}, Tag: "critical", MatchIndex: -1}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"峰"}}, Tag: "peak", MatchIndex: -1}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1}, +} + +func NewMeterReadingsExcelAnalyzer(file io.Reader) (*ExcelAnalyzer[model.ReadingImportRow], error) { + return NewExcelAnalyzer[model.ReadingImportRow](file, meterReadingsRecognizers) +} diff --git a/model/meter.go b/model/meter.go index 3a3cdad..f38961f 100644 --- a/model/meter.go +++ b/model/meter.go @@ -1,6 +1,7 @@ package model import ( + "electricity_bill_calc/types" "time" "github.com/shopspring/decimal" @@ -13,7 +14,7 @@ type MeterDetail struct { MeterType int16 `json:"meterType" db:"meter_type"` Building *string `json:"building" db:"building"` BuildingName *string `json:"buildingName" db:"building_name"` - OnFloor *string `json:"onFloor" db:"on_floor"` + OnFloor *string `json:"onFloor" db:"on_floor" ` Area decimal.NullDecimal `json:"area" db:"area"` Ratio decimal.Decimal `json:"ratio" db:"ratio"` Seq int64 `json:"seq" db:"seq"` @@ -76,3 +77,31 @@ type PooledMeterDetailCompound struct { MeterDetail BindMeters []MeterDetail `json:"bindMeters"` } + +// 以下结构体用于导入表计档案数据 +type MeterImportRow struct { + Code string `json:"code" excel:"code"` + Address *string `json:"address" excel:"address"` + MeterType int16 `json:"meterType" excel:"meterType"` + Building *string `json:"building" excel:"building"` + OnFloor *string `json:"onFloor" excel:"onFloor"` + Area decimal.NullDecimal `json:"area" excel:"area"` + Ratio decimal.Decimal `json:"ratio" excel:"ratio"` + Seq int64 `json:"seq" excel:"seq"` + ReadAt types.DateTime `json:"readAt" excel:"readAt"` + Overall decimal.Decimal `json:"overall" excel:"overall"` + Critical decimal.NullDecimal `json:"critical" excel:"critical"` + Peak decimal.NullDecimal `json:"peak" excel:"peak"` + Flat decimal.NullDecimal `json:"flat" excel:"flat"` + Valley decimal.NullDecimal `json:"valley" excel:"valley"` +} + +// 以下结构体用于导入表计抄表数据 +type ReadingImportRow struct { + Code string `json:"code" excel:"code"` + ReadAt types.DateTime `json:"readAt" excel:"readAt"` + Overall decimal.Decimal `json:"overall" excel:"overall"` + Critical decimal.NullDecimal `json:"critical" excel:"critical"` + Peak decimal.NullDecimal `json:"peak" excel:"peak"` + Valley decimal.NullDecimal `json:"valley" excel:"valley"` +} From 50418f2e3099ecae7ba6eb5b8c23bb5c02c28e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 10 Jun 2023 07:45:22 +0800 Subject: [PATCH 045/141] =?UTF-8?q?enhance(excel):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E6=8A=84=E8=A1=A8=E8=AE=B0=E5=BD=95=20Excel=20=E7=9A=84?= =?UTF-8?q?=E5=88=97=E8=AF=86=E5=88=AB=E5=85=B3=E9=94=AE=E5=AD=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/meter_reading.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/excel/meter_reading.go b/excel/meter_reading.go index 678e6df..8815b49 100644 --- a/excel/meter_reading.go +++ b/excel/meter_reading.go @@ -9,7 +9,7 @@ var meterReadingsRecognizers = []*ColumnRecognizer{ {Pattern: [][]string{{"表", "表计"}, {"编号"}}, Tag: "code", MatchIndex: -1, MustFill: true}, {Pattern: [][]string{{"抄表", "结束"}, {"时间", "日期"}}, Tag: "readAt", MatchIndex: -1, MustFill: true}, {Pattern: [][]string{{"用电", "有功", "表底", "底数"}, {"总", "量"}}, Tag: "overall", MatchIndex: -1, MustFill: true}, - {Pattern: [][]string{{"有功", "表底", "底数"}, {"尖", "尖锋"}}, Tag: "critical", MatchIndex: -1}, + {Pattern: [][]string{{"有功", "表底", "底数"}, {"尖"}}, Tag: "critical", MatchIndex: -1}, {Pattern: [][]string{{"有功", "表底", "底数"}, {"峰"}}, Tag: "peak", MatchIndex: -1}, {Pattern: [][]string{{"有功", "表底", "底数"}, {"谷"}}, Tag: "valley", MatchIndex: -1}, } From 686890b5d8a8612defdc048651b38deb3cc9187e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 10 Jun 2023 11:26:10 +0800 Subject: [PATCH 046/141] =?UTF-8?q?enhance(types):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E6=97=A5=E6=9C=9F=E6=97=B6=E9=97=B4=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E8=A7=A3=E6=9E=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/date.go | 18 ++++++++++++------ types/datetime.go | 21 ++++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/types/date.go b/types/date.go index 6713219..7e08394 100644 --- a/types/date.go +++ b/types/date.go @@ -10,6 +10,10 @@ import ( "go.uber.org/zap" ) +var dateLayouts = []string{ + "2006-01-02", "2006-1-2", "2006/01/02", "6-1-2", "6-01-02", "01/02/06", "1/2/6", "2006年01月02日", "06年1月2日", +} + type Date struct { time.Time } @@ -42,13 +46,15 @@ func ParseDate(t string) (Date, error) { if len(t) == 0 { return NewEmptyDate(), fmt.Errorf("不能解析空白的日期时间。") } - d, err := time.ParseInLocation("2006-01-02", t, loc) - if err != nil { - return NewEmptyDate(), fmt.Errorf("无法解析给定的日期, %w", err) + for _, layout := range dateLayouts { + d, err := time.ParseInLocation(layout, t, loc) + if err == nil { + return Date{ + Time: d, + }, nil + } } - return Date{ - Time: d, - }, nil + return NewEmptyDate(), fmt.Errorf("无法解析给定的日期,格式不正确。") } func ParseDateWithDefault(t string, defaultDate Date) Date { diff --git a/types/datetime.go b/types/datetime.go index ea5a41e..6d70a42 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -10,7 +10,12 @@ import ( "go.uber.org/zap" ) -var loc *time.Location = time.FixedZone("+0800", 8*60*60) +var ( + loc *time.Location = time.FixedZone("+0800", 8*60*60) + datetimeLayouts = []string{ + "2006-01-02 15:04:05", "2006-1-2 15:04:05", "2006/01/02 15:04:05", "6-1-2 15:04:05", "6-01-02 15:04:05", "01/02/06 15:04:05", "1/2/6 15:04:05", "2006年01月02日 15:04:05", "06年1月2日 15:04:05", + } +) type DateTime struct { time.Time @@ -43,13 +48,15 @@ func ParseDateTime(t string) (DateTime, error) { if len(t) == 0 { return NewEmptyDateTime(), fmt.Errorf("不能解析空白的日期时间。") } - d, err := time.ParseInLocation("2006-01-02 13:04:05", t, loc) - if err != nil { - return NewEmptyDateTime(), fmt.Errorf("无法解析给定的日期, %w", err) + for _, layout := range datetimeLayouts { + d, err := time.ParseInLocation(layout, t, loc) + if err == nil { + return DateTime{ + Time: d, + }, nil + } } - return DateTime{ - Time: d, - }, nil + return NewEmptyDateTime(), fmt.Errorf("无法解析给定的日期时间,格式不正确。") } var _ driver.Valuer = (*DateTime)(nil) From 5d938da53e71869b7a62e2a68983480a2de0b378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 10 Jun 2023 11:37:52 +0800 Subject: [PATCH 047/141] =?UTF-8?q?enhance(types):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E6=97=B6=E9=97=B4=E8=A7=A3=E6=9E=90=E5=B0=9D?= =?UTF-8?q?=E8=AF=95=E6=A8=A1=E6=9D=BF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/date.go | 2 +- types/datetime.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/types/date.go b/types/date.go index 7e08394..d1703aa 100644 --- a/types/date.go +++ b/types/date.go @@ -11,7 +11,7 @@ import ( ) var dateLayouts = []string{ - "2006-01-02", "2006-1-2", "2006/01/02", "6-1-2", "6-01-02", "01/02/06", "1/2/6", "2006年01月02日", "06年1月2日", + "2006-01-02", "2006-1-2", "2006/01/02", "06-1-2", "6-01-02", "01/02/06", "1/2/06", "2006年01月02日", "06年1月2日", } type Date struct { diff --git a/types/datetime.go b/types/datetime.go index 6d70a42..78a4066 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -13,7 +13,8 @@ import ( var ( loc *time.Location = time.FixedZone("+0800", 8*60*60) datetimeLayouts = []string{ - "2006-01-02 15:04:05", "2006-1-2 15:04:05", "2006/01/02 15:04:05", "6-1-2 15:04:05", "6-01-02 15:04:05", "01/02/06 15:04:05", "1/2/6 15:04:05", "2006年01月02日 15:04:05", "06年1月2日 15:04:05", + "2006-01-02 15:04:05", "2006-1-2 15:04:05", "2006/01/02 15:04:05", "06-1-2 15:04:05", "06-01-02 15:04:05", "01/02/06 15:04:05", "1/2/06 15:04:05", "2006年01月02日 15:04:05", "06年1月2日 15:04:05", + "2006-01-02 15:04", "2006-1-2 15:04", "2006/01/02 15:04", "06-1-2 15:04", "06-01-02 15:04", "01/02/06 15:04", "1/2/06 15:04", "2006年01月02日 15:04", "06年1月2日 15:04", } ) @@ -49,6 +50,7 @@ func ParseDateTime(t string) (DateTime, error) { return NewEmptyDateTime(), fmt.Errorf("不能解析空白的日期时间。") } for _, layout := range datetimeLayouts { + fmt.Printf("Parse: %s, Try layout: %s\n", t, layout) d, err := time.ParseInLocation(layout, t, loc) if err == nil { return DateTime{ From c2a56c82531aa4e3e4498e3c5e16cd2ab29c1275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sun, 11 Jun 2023 17:35:50 +0800 Subject: [PATCH 048/141] =?UTF-8?q?enahnce(security):=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E4=BB=A4=E7=89=8C=E7=9A=84=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- security/security.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/security/security.go b/security/security.go index dda0f61..38d5c7c 100644 --- a/security/security.go +++ b/security/security.go @@ -14,7 +14,11 @@ import ( // ! 仅通过该中间件是不能保证上下文中一定保存有用户会话信息的。 func SessionRecovery(c *fiber.Ctx) error { if auth := c.Get("Authorization", ""); len(auth) > 0 { - token := strings.Fields(auth)[1] + authFields := strings.Fields(auth) + if len(authFields) != 2 || strings.ToLower(authFields[0]) != "bearer" || len(authFields[1]) == 0 { + return c.Next() + } + token := authFields[1] session, err := cache.RetrieveSession(token) if err == nil && session != nil { From bfa9da4a039f6d6d0724c19f904b327f52fee3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sun, 11 Jun 2023 17:36:38 +0800 Subject: [PATCH 049/141] =?UTF-8?q?enhance(log):=E7=B2=BE=E7=AE=80?= =?UTF-8?q?=E4=BA=86=E5=9C=A8=E6=97=A5=E5=BF=97=E4=B8=AD=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E8=AF=B7=E6=B1=82=E7=9A=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logger/middleware.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logger/middleware.go b/logger/middleware.go index 69cff6f..52a170f 100644 --- a/logger/middleware.go +++ b/logger/middleware.go @@ -63,8 +63,8 @@ func NewLogMiddleware(config LogMiddlewareConfig) fiber.Handler { zap.Namespace("context"), zap.String("pid", strconv.Itoa(os.Getpid())), zap.String("time", stop.Sub(start).String()), - zap.Object("response", Resp(c.Response())), - zap.Object("request", Req(c)), + // zap.Object("response", Resp(c.Response())), + // zap.Object("request", Req(c)), } if u := c.Locals("userId"); u != nil { From 7476278c52ddd795855af7383b4296bf16706bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sun, 11 Jun 2023 17:43:34 +0800 Subject: [PATCH 050/141] =?UTF-8?q?enhance(tools):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E7=94=A8=E4=BA=8E=E5=B0=86=E7=A9=BA=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E8=BD=AC=E6=8D=A2=E4=B8=BA=E7=A9=BA=E7=99=BD?= =?UTF-8?q?=E6=8C=87=E9=92=88=E7=9A=84=E5=8A=9F=E8=83=BD=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/utils.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/utils.go b/tools/utils.go index 4779a6a..561f5e3 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -119,3 +119,11 @@ func NewDecimalFromString(val *string, defaultValue decimal.Decimal) decimal.Dec } return nd } + +// 将空白字符串转换为空指针,同时字符串本身也将转换为指针类型。 +func EmptyToNil(val string) *string { + if len(val) == 0 { + return nil + } + return &val +} From cadb2db8c7d538ba01e0e5b636324705b0a7fd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sun, 11 Jun 2023 17:50:40 +0800 Subject: [PATCH 051/141] =?UTF-8?q?enhance(log):=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E7=A9=BA=E7=99=BD=E6=8C=87=E9=92=88=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E6=97=A5=E5=BF=97=E8=BE=93=E5=87=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logger/logger.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/logger/logger.go b/logger/logger.go index ddf414d..5ab4c6e 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -139,7 +139,7 @@ func DecimalField(key string, val decimal.Decimal) zap.Field { func DecimalFieldp(key string, val *decimal.Decimal) zap.Field { if val == nil { - return zap.String(key, "null") + return zap.Stringp(key, nil) } return DecimalField(key, *val) } @@ -148,17 +148,17 @@ func NullDecimalField(key string, val decimal.NullDecimal) zap.Field { if val.Valid { return DecimalField(key, val.Decimal) } - return zap.String(key, "null") + return zap.Stringp(key, nil) } func NullDecimalFieldp(key string, val *decimal.NullDecimal) zap.Field { if val == nil { - return zap.String(key, "null") + return zap.Stringp(key, nil) } if val.Valid { return DecimalField(key, val.Decimal) } - return zap.String(key, "null") + return zap.Stringp(key, nil) } func DateField(key string, val types.Date) zap.Field { @@ -167,7 +167,7 @@ func DateField(key string, val types.Date) zap.Field { func DateFieldp(key string, val *types.Date) zap.Field { if val == nil { - return zap.String(key, "null") + return zap.Stringp(key, nil) } return DateField(key, *val) } @@ -178,7 +178,7 @@ func DateTimeField(key string, val types.DateTime) zap.Field { func DateTimeFieldp(key string, val *types.DateTime) zap.Field { if val == nil { - return zap.String(key, "null") + return zap.Stringp(key, nil) } return DateTimeField(key, *val) } From e3668886087334853cef866d686c231f54153c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sun, 11 Jun 2023 18:48:48 +0800 Subject: [PATCH 052/141] =?UTF-8?q?enhance(cache):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=BF=AB=E9=80=9F=E5=A4=84=E7=90=86=E5=8F=AF=E7=A9=BA=E5=80=BC?= =?UTF-8?q?=E4=BD=9C=E4=B8=BARedis=E7=BC=93=E5=AD=98=E9=94=AE=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/abstract.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cache/abstract.go b/cache/abstract.go index 8d24c69..02c13d6 100644 --- a/cache/abstract.go +++ b/cache/abstract.go @@ -137,3 +137,27 @@ func CacheKey(category string, ids ...string) string { } return b.String() } + +type ToString[T any] interface { + ToString() string + *T +} + +// 用于生成一个内容可以为空的Redis缓存键,这个键的类型必须实现了`ToString`接口。 +func NullableConditionKey[P any, T ToString[P]](value T, defaultStr ...string) string { + defaultStr = append(defaultStr, "UNDEF") + if value == nil { + return defaultStr[0] + } else { + return value.ToString() + } +} + +// 用于生成一个内容可以为空的字符串指针类型的Redis缓存键。 +func NullableStringKey(value *string, defaultStr ...string) string { + defaultStr = append(defaultStr, "UNDEF") + if value == nil { + return defaultStr[0] + } + return *value +} From 2339e4c7257ff7643bd7da7267851256f487f693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sun, 11 Jun 2023 22:31:32 +0800 Subject: [PATCH 053/141] =?UTF-8?q?enhance(meter):=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=A4=A7=E9=83=A8=E5=88=86=E8=A1=A8=E8=AE=A1=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 381 +++++++++++++++++++++++++++++++++++++++++++- model/meter.go | 39 +++-- model/reading.go | 4 +- repository/meter.go | 57 +++++-- repository/park.go | 32 ++++ service/meter.go | 144 ++++++++++++++++- tools/utils.go | 8 + types/datetime.go | 6 + vo/meter.go | 6 + vo/reading.go | 59 +++++++ 10 files changed, 700 insertions(+), 36 deletions(-) diff --git a/controller/meter.go b/controller/meter.go index 886e9c5..f627863 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -2,15 +2,20 @@ package controller import ( "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" "net/http" "github.com/gofiber/fiber/v2" + "github.com/jinzhu/copier" + "github.com/samber/lo" "go.uber.org/zap" ) @@ -20,8 +25,17 @@ func InitializeMeterHandlers(router *fiber.App) { 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/: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("/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文件中导入表计档案 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 } + +// 列出指定公摊表计下的所有关联表计 +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 +} diff --git a/model/meter.go b/model/meter.go index f38961f..e68b603 100644 --- a/model/meter.go +++ b/model/meter.go @@ -2,7 +2,6 @@ package model import ( "electricity_bill_calc/types" - "time" "github.com/shopspring/decimal" ) @@ -19,31 +18,31 @@ type MeterDetail struct { Ratio decimal.Decimal `json:"ratio" db:"ratio"` Seq int64 `json:"seq" db:"seq"` Enabled bool `json:"enabled" db:"enabled"` - AttachedAt *time.Time `json:"attachedAt" db:"attached_at"` - DetachedAt *time.Time `json:"detachedAt" db:"detached_at"` - CreatedAt time.Time `json:"createdAt" db:"created_at"` - LastModifiedAt time.Time `json:"lastModifiedAt" db:"last_modified_at"` + AttachedAt *types.DateTime `json:"attachedAt" db:"attached_at"` + DetachedAt *types.DateTime `json:"detachedAt" db:"detached_at"` + CreatedAt types.DateTime `json:"createdAt" db:"created_at"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` } type MeterRelation struct { - Id string `json:"id"` - Park string `json:"parkId" db:"park_id"` - MasterMeter string `json:"masterMeterId" db:"master_meter_id"` - SlaveMeter string `json:"slaveMeterId" db:"slave_meter_id"` - EstablishedAt time.Time `json:"establishedAt"` - SuspendedAt *time.Time `json:"suspendedAt"` - RevokeAt *time.Time `json:"revokeAt"` + Id string `json:"id"` + Park string `json:"parkId" db:"park_id"` + MasterMeter string `json:"masterMeterId" db:"master_meter_id"` + SlaveMeter string `json:"slaveMeterId" db:"slave_meter_id"` + EstablishedAt types.DateTime `json:"establishedAt"` + SuspendedAt *types.DateTime `json:"suspendedAt"` + RevokeAt *types.DateTime `json:"revokeAt"` } type MeterSynchronization struct { - Park string `json:"parkId" db:"park_id"` - Meter string `json:"meterId" db:"meter_id"` - ForeignMeter string `json:"foreignMeter"` - SystemType string `json:"systemType"` - SystemIdentity string `json:"systemIdentity"` - Enabled bool `json:"enabled"` - LastSynchronizedAt time.Time `json:"lastSynchronizedAt" db:"last_synchronized_at"` - RevokeAt *time.Time `json:"revokeAt" db:"revoke_at"` + Park string `json:"parkId" db:"park_id"` + Meter string `json:"meterId" db:"meter_id"` + ForeignMeter string `json:"foreignMeter"` + SystemType string `json:"systemType"` + SystemIdentity string `json:"systemIdentity"` + Enabled bool `json:"enabled"` + LastSynchronizedAt types.DateTime `json:"lastSynchronizedAt" db:"last_synchronized_at"` + RevokeAt *types.DateTime `json:"revokeAt" db:"revoke_at"` } type SimpleMeterDocument struct { diff --git a/model/reading.go b/model/reading.go index 03db697..8f28391 100644 --- a/model/reading.go +++ b/model/reading.go @@ -1,7 +1,7 @@ package model import ( - "time" + "electricity_bill_calc/types" "github.com/shopspring/decimal" ) @@ -38,7 +38,7 @@ func NewUnitaryReading(ratio, overall decimal.Decimal) *Reading { } type MeterReading struct { - ReadAt time.Time `json:"readAt"` + ReadAt types.DateTime `json:"readAt"` Park string `json:"parkId" db:"park_id"` Meter string `json:"meterId" db:"meter_id"` MeterType int16 `json:"meterType"` diff --git a/repository/meter.go b/repository/meter.go index d590d2a..f59700d 100644 --- a/repository/meter.go +++ b/repository/meter.go @@ -215,7 +215,7 @@ func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.M pid, 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))) return *meters, nil } @@ -231,7 +231,7 @@ func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.M ). Where( 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()). Prepared(true).ToSQL() @@ -311,6 +311,43 @@ func (mr _MeterRepository) CreateMeter(tx pgx.Tx, ctx context.Context, pid strin 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) { 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) { - 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{ pid, - tools.DefaultOrEmptyStr(keyword, "UNDEF"), + cache.NullableStringKey(keyword), fmt.Sprintf("%d", page), - tools.CondFn(func(val *types.Date) bool { - return val != nil - }, start, start.ToString(), "UNDEF"), - tools.CondFn(func(val *types.Date) bool { - return val != nil - }, end, end.ToString(), "UNDEF"), + cache.NullableConditionKey(start), + cache.NullableConditionKey(end), + cache.NullableStringKey(buidling), } - 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)) return *readings, total, nil } @@ -870,6 +904,7 @@ func (mr _MeterRepository) ListMeterReadings(pid string, keyword *string, page u readingQuery := mr.ds. 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")))). + Select("r.*"). Where( goqu.I("r.park_id").Eq(pid), ) diff --git a/repository/park.go b/repository/park.go index 0e83697..c3a7a36 100644 --- a/repository/park.go +++ b/repository/park.go @@ -1,6 +1,7 @@ package repository import ( + "context" "electricity_bill_calc/cache" "electricity_bill_calc/global" "electricity_bill_calc/logger" @@ -14,6 +15,7 @@ import ( "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" "go.uber.org/zap" ) @@ -388,6 +390,36 @@ func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (b 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) { pr.log.Info("修改指定园区中指定建筑的信息", zap.String("id", id), zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor)) diff --git a/service/meter.go b/service/meter.go index ebb428e..a6e6e06 100644 --- a/service/meter.go +++ b/service/meter.go @@ -2,6 +2,7 @@ package service import ( "electricity_bill_calc/cache" + "electricity_bill_calc/excel" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" @@ -11,6 +12,7 @@ import ( "electricity_bill_calc/vo" "fmt" "mime/multipart" + "strings" "github.com/samber/lo" "github.com/shopspring/decimal" @@ -108,8 +110,146 @@ func (ms _MeterService) UpdateMeterRecord(pid string, code string, form *vo.Mete } // 处理上传的Excel格式表计档案文件,根据表号自动更新数据库 -func (ms _MeterService) BatchImportMeters(pid string, file multipart.FileHeader) error { - return nil +func (ms _MeterService) BatchImportMeters(pid string, file *multipart.FileHeader) ([]excel.ExcelAnalysisError, error) { + 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 } // 更换系统中的表计 diff --git a/tools/utils.go b/tools/utils.go index 561f5e3..ad82f23 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -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) } +// 使用给定的函数对指定的值进行判断,根据表达式的值返回指定的值。本函数为惰性求值。 +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 { return CondFn(exprFn, value, value, elseValue) diff --git a/types/datetime.go b/types/datetime.go index 78a4066..d6d8140 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -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) { if len(t) == 0 { return NewEmptyDateTime(), fmt.Errorf("不能解析空白的日期时间。") diff --git a/vo/meter.go b/vo/meter.go index 9671097..0c84e79 100644 --- a/vo/meter.go +++ b/vo/meter.go @@ -36,3 +36,9 @@ type MeterReplacingForm struct { OldReading MeterReadingForm `json:"oldReading"` NewMeter NewMeterForReplacingForm `json:"newMeter"` } + +type SimplifiedMeterQueryResponse struct { + Code string `json:"code"` + Address *string `json:"address"` + Park string `json:"parkId"` +} diff --git a/vo/reading.go b/vo/reading.go index 0ce357b..d3c3f1e 100644 --- a/vo/reading.go +++ b/vo/reading.go @@ -1,7 +1,9 @@ package vo import ( + "electricity_bill_calc/model" "electricity_bill_calc/types" + "fmt" "github.com/shopspring/decimal" ) @@ -14,3 +16,60 @@ type MeterReadingForm struct { Valley decimal.Decimal `json:"valley"` 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, + } +} From 46ae943653d4f4e87aed8ba069d83779c51fbca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 12 Jun 2023 17:09:13 +0800 Subject: [PATCH 054/141] =?UTF-8?q?enhance(log):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=B3=BB=E7=BB=9F=E9=85=8D=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logger/middleware.go | 4 ++++ logger/rolling.go | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/logger/middleware.go b/logger/middleware.go index 52a170f..d826810 100644 --- a/logger/middleware.go +++ b/logger/middleware.go @@ -62,6 +62,10 @@ func NewLogMiddleware(config LogMiddlewareConfig) fiber.Handler { fields := []zap.Field{ zap.Namespace("context"), zap.String("pid", strconv.Itoa(os.Getpid())), + zap.String("method", c.Method()), + zap.String("remote", c.IP()), + zap.Strings("forwarded", c.IPs()), + zap.String("url", c.OriginalURL()), zap.String("time", stop.Sub(start).String()), // zap.Object("response", Resp(c.Response())), // zap.Object("request", Req(c)), diff --git a/logger/rolling.go b/logger/rolling.go index be8ff61..960dd4c 100644 --- a/logger/rolling.go +++ b/logger/rolling.go @@ -1,9 +1,12 @@ package logger import ( + "fmt" "io" "log" + "math" "os" + "time" "gopkg.in/natefinch/lumberjack.v2" ) @@ -14,10 +17,11 @@ func newRollingWriter() io.Writer { return nil } + now := time.Now() return &lumberjack.Logger{ - Filename: "log/service.log", - MaxBackups: 366 * 10, // files - MaxSize: 200, // megabytes - MaxAge: 366 * 10, // days + Filename: fmt.Sprintf("log/service_%s.log", now.Format("2006-01-02_15")), + MaxBackups: math.MaxInt, // files + MaxSize: 200, // megabytes + MaxAge: math.MaxInt, // days } } From 7bac667c2a12cf0816247e57ed14c3b06448d517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 10:23:47 +0800 Subject: [PATCH 055/141] =?UTF-8?q?refactor(meter):=E8=A1=A8=E8=AE=A1?= =?UTF-8?q?=E6=A1=A3=E6=A1=88=E6=A8=A1=E6=9D=BF=E7=9A=84=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E4=BD=BF=E7=94=A8Excel=20=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95=E5=AE=9E=E7=8E=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/meter_04kv_template.xlsx | Bin 10453 -> 0 bytes controller/meter.go | 23 ++++++-- excel/meter_archive.go | 92 ++++++++++++++++++++++++++++++++ excel/meter_reading.go | 12 +++++ service/meter.go | 6 +++ 5 files changed, 129 insertions(+), 4 deletions(-) delete mode 100644 assets/meter_04kv_template.xlsx diff --git a/assets/meter_04kv_template.xlsx b/assets/meter_04kv_template.xlsx deleted file mode 100644 index 687fafc903bee1abf9a6ebbca146e2d7b063a7bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10453 zcma)iWk6iZvM%l21`-MZ?0LMGUln}5{(Hbby)m&hR&=nnb7WL}f}ua%fcpt+ zUWtj(FN>Kzj6tC7Ml4leo!8!e}B8MevE)yo036!~%X;_%oY8EbW} zW{vd7NGf{zGEmCW)md3`4A{liU>KGspz4vrVKXkh0exbo! zKV8{bdkCsp;SqqGUC@=>4_K!Erln}c2Y&<9bFIp$A?{ekVEM zuZTle)ikH^p1}AJO$AX=wKqLQ>P$Kx?K}Yg1;(f^c`Z{9lG0j)21UkW(<|N00jO6< z>I!2FDwLp#ghLQKRhDeV{xm-ez|U&q&V#0vMkcF<3G2Q-R_dnhmw}wlD{13|2Oa^e zsmsO9c67v;YdzsZXM1_7v9@xR2CuUcwCBp( z{q49qa;}YY7x+ffay*_v8QXP|qO#1ctYg2Of_be5({7&kiX;50XPT|vVk`d=u$&0z zf!^Ih+b9eM^-9A@+o=3|F4o5?`2JV101}*c>n4~l&6Aad36twyZl)y8Bi6_q2{503 zVUl`dA_>b(ka`j9A-K57e}{X`@lQXDM~BOiKluUa$q%po;fJ@5PPW$1p2&?EknUtc ze|PDBs934sxWg_o->wJK8RWmVCzJdQJvoPMXPdaC5yuG~%f`v=>Lt;Vk9@6nOtuSp z6;5?O_%ZWQ;A#Wj1V608JhH6c8>`|5=LzZcWdM=}*7WPyuO*bs^Fr9eB=LRi?Q`Zq z(JQbd+G~v(^*Pg3R;74bd}Ce$c1o5llAq|ZuzP|TyDi}J8S$@{OCvAJd*x57(q^p{ z;>PJK6|VYra|s&oo2Hc1rcon_tu>(GDacL0Wr*dO^TRk#VH_?x^h%TOIR{wK|fqY`s)#_((`OknCzW^?_4wjB) zAdu6afvBpM18foYNeM9+8210d{uE*U>8E4`+qG_-?nCGs0ysi<3qZ_`@oWvg$OIJ?fSBZQ@>^muYkz zh5;YWHb1t0;h!2xxu~FO2^w@cTgu z>C&0lyhW-!Mc6Tr(WPSFZqidW>gJ(E2dnP<<}n)MjE+9ImYt;WIpr}@SqiSv23 z>zISL%ix{bx4nh>ET>ns8x4i?H!XOjyZbX)*Dh{cEhd-sx*ib_aWz9J9PjTbCtMXc zE*nbv54?ODAr(i{ZtozwJHS)I9AG}^3F!gk7rZ&eM z8k3N-Zw(cYDQig_GIGzX^Hjm?!Ss%7xH~V-qM6dI_|%LhVfwPEY?q_^Dm5p>d=XD} zz;oe)ZCFVG&5zVE;LX&F*kthTeAJ^Y!|F!wY0p{FlvREKRbBPhK2#hFYcwt4u=~X~ zS8huDyv~7@*z|JaZ*`SmZwRers#4a>Qd+BWYPqGv&65GAI}Wu>OGAs^`lGcJyA2tn z9I~r(v*J)o99g@g>{^iwrg5K!tn4jB6DV=zbjccCL||q+J;Y41zLuN*YdgdZD{mGNRArT;Q98b|M2YbwCkL6V7Mb?mxm$o@ExK}tjE{au#s{3Ai zVAQjo^ZaH;ygHf>Rrk>{o6;s2sHQJJ{g})9X=HErRV~ZM&jS9ZOtrT)J`{=hn?r9^ zh{!_et59yg2U<9Y@tMouowJga8{r1jddmEmPw6|qqUZCO`gT0%0{9+t9U0GKgUw0 zx3Y*nT3&Os`ID`j^esxSkDG?ctMJk_KPl{bdzdIIs%xZ5ALNJxw4rQY6A3cWh8K$M|))oj+SCC2~oHunUjT9yqqc- z<(v){(pT-bj*lM_!42>`as((Vl_5yAeRTldrd=Afe=Z{H|GYy zY5>9TG|iV%#P9?ms;Wj9fM@OeQn>OHdZr;i;#&g9kI^^KiH~`4F>1NZIh8z;;(DzH zQ?~~5QvCsy(#9B+%rRT#@)mI@>OIt3_N-6j2%#}5RfNVR_98~l07TJ}A~b0*Hqf6G zN2sNCit71xQjo+x=}<1D$|nvvGb>pIP@F&jfbcP*rp8k~MFtOAyv--v&{8HJx@qK% zNEro<;!vbafTo42@hZ`x?riGzXs_HL`?!=;QUWC*X1CM3{^&i4JXNQ{c9d* z@)Lz03*@L67_IrM=l3G$k7hd7^PM|KF34 z;xvAv*Zs`5_g^arWOd#ImHY!10^e@flfHyC>Xv>L{=BGD6|R@DzCDqaQAd02UGE|p zTl$J)%QlIDTGQ_d(v)+~q;Pzwek>mDG?miKGen*g6-5mfd0uv>yYWu-Y&G70^3bfd zelUpI&_YuipFn=Kue56I{x(I{RE8To+iy_m^izLaioTaeLF1C=)ylBI{f5549~T=?eUV&O}ckw!tQb5ru!fl-@#)Fdx+jh>)DSkK`VKo>r5xt`utn( zB#xKlaz<+3$MNOG$4!9x@(TL)6EnucZDhL592lnnP5I5QSj}inWjx%E$Uu)A)@y(M zr0gQjhVtES4;|ctw-E+2dKe3aMEj>+qfIoAk}{$~>Kx>0U6-`2p_gyR+1+N7T6wH? z8(Z@0=f@v$zFfcjzY~n-J}$%| z;&GrfV{Q9r>@~-j?2@sN!q)PyQaGcDN{8YX>RNn6b*U$&=4rs_s}S_dG9Q)kTXd(I zgOT*r8`EC{ilsnYBN8~grd7_e=;lWP* z%0pttvi-PnkqC%lxJ{3yLPiBqA6z-o-4!7a8yJz z`K|%T-6@4MjI#w7=S9J|Z-B z5U1MgaJjKDP?k>Pop0*x=tCCh^qgzoo3?7+jDw*=eI82<+o`HpT0CS&C;{&m;P$-K zYTi!{;<&Rdw_mg@@IfpWs(-w}^=8@@MyGFr*|_S)ZPBx}e!GjQ7vSLOh`dyEaQ=h- z3B^plvzR741uWbr>yLp*o&P5FWmDtw=oeZX!G#jtLsri=CP zJS;yyDm{b$X5;L5ZCpZNtA*|>WW0FZO7UUq)$P1Ob8EWS<&VDCHcd>&t&eB3wyoYf zFdh6_EbiCm;{xddUe`lgJ`YEo;7e;}Rvi+6_KwC7g(e<4znY!4nZe`4)}$^f@y<#? z<}2qf2Lm$@i>YKkI`)luTrW8#gArY=FWOeQ!>?u0dImXGJ0S>}#V>JOV3yw{4+I~X zC5RvNT@Q9H2yM@>yI|$2TijM5RmXkNaW=LZZIV->Q3_O{<2w(RV|Fe%;3rYniIQwf zGI+JKK)J|I`Yk%VcZMb>cNAQTn9&hSNU{baCEH_>Kf6rr@~9#r5ZSLF;0{iOHhg+S zHmh^PArQ?1<_@KAM$Cb8%P(XX5k4uB38W{`Dg4FC#$Ka5<_wRR=%AWt)Xj*j9-#7Z zSfn#CWUs=ULtJXJ7mBH*(Pfq>H~Ac&|=v9h$5idk9kz^mBE+7tUydX3Mk# z3+R{E=eBnv7%;jjaAuJ;SRsw9j_66c3+#2p&PI|yWY^~k5nbU%)9Z%%j(h%jysl$h z)1kBDZc4Mb5YLo>5Tb9xwMHrOYlP6|wyC9C9L<7dZA{BC102;n2Z^DqY21u+v>82& zTJ+gHuKyZ9)no^wlix$!#~YCiAlS zfq1PmRjN=8Y&NZrj}cvBj>yi%bAl==)Mh`@obEQAA*Cq_DE2-z^Aj~iiLy$n}p<2J+B8C#|2eNNs_g#5usMn$fsd6-KKkm z#`XbgeNeDtvW?7Hq-j3TLBlm2*QzP{_$73+NMn5AUtihEzPWqhk7ikILE}q;dK8+U zAi)YaQm;g>1l^aizJFyq7!p0!zLO;bX%WcZZZvDW_)PiC8&KH-28h3?L z8F>n+Xi{#VEXHwW=It<0Ifhl#ah8={O-XmW)MyGR4EPysAdKBDV3M?Rgkk7KhRkKr zdM%b#or648BNS9qct-YadNv96L_(bA1p3-r9Q9<~@VRE=iZ<4Qw0tS-q9vU#x*^)L zWU1rr%R$@%Go|YENOG84(gf!BUxTg@A@KyjjV{JP>h4H{|@aSjo*$d88Ca0io!U1!Df+-UZ0RRbO zB|;?O~mfdrUhKlrQ_WbEHEgReNJfFveX7$k;?~&~q zT!Qkgi8HeNa{Z;dn`z)pyHZbm{UN6x44wOZatjd@?6gC@x8u?d@Md?>Z#=+uE`7-w z%5L{?;|8{6-0U1)sVrcp@W(mg;=aXf4_rn}`@9x~*ksP50z8aBz( z6Z5%#^uisLSQZjRDBa5DY-#L(jT;O$K(uA&Ew4Tzm;)HxP0sW-_g=)xj;%hghvd?n z?Uk1x4H=!rI!+h0Pu1=PZwBumGmc8^$!=FrWqL+rh8N5A>3tv8VYiejtAy#5MZ%q;hPgpVXnr`^5bJ_KxLmKZU*#N?57nqx@>33-bRCg%`3Z| zFK|L;TPylmzT{3T#4z|e#HerPv8WY0B!cYpOb5STZ+@^o(M)zLFm&|6v^tSeA_g#A zxx_%I4f$%nF8vsag*dE>-#Y8*(@yUxWLzLRSGy8KluqY@_LoB=WV zoEDh&K$lUjnX24CK^|FagVA-D(_9W9b9>!rwK z74wiYoT3A8B^!|A=iRd<`DJR^&x)I?)a0m-^W&Gyp{qJ=WL?Yj$`0wcqL9ZEi(n311+)U|hHRl;aOlO(&|0q#(F zO%+q0!@((Uov{;`bbNN6KGptwkaJ9)K@(0jn>`c}IsPIOaI(0BdY;u0C4Sa7Q?~YOcs%qWVQNTY9k3)Hi5SbajTz#5ZILb*GY# zVlZ`*2m)5wYOXV~g6hhBt9-?8~|6Q3}q_c z@4MDAiJ(*lEn-(q;=;nFWgj9pG)CHv^TDaY-#CRi^Nrp|DEe)W;<~hB?~2TQK*xteSnNI5;%u43w0DtW5(g#W4~0H^qvZ zo;HBr$*$Wl)Df z>G2LPsCe_+KG1&JNN9wy`5qf-$12fNs@kIi6Ix61b}I7$pZ?<>PbDfZK7#ZtuBuL& zx_u@j@)p$xn(f|Ahl4My2J-66x=^HGm@L}CpBR`1Y-+Z_LkEE3qHs)AL11A8wRT34 z><@e4^-_ebCQ`mg_zX$?a5YKvp`)3U1Lk2+8FehGY@k{}ag7fauM}PNaEOQc=f11UwAd$dL>LSBcGp+xieYU&?qLp908QYt`vdPehvQI`J278gbJ*RNr-A=!Sl3+e-+TfPLfwoI~xC(c!my4l%#`uwd z%|oz7FvWG9o|m-IY#qs1j)>oj+xrgKF>=th-*(KJz-Vx~z$Qffh-@u~=Y4&Bm2oNV z!%4{R#&*3OaBRyznYnEs2o*&_A=VINn;eGI1AU{@9VA;MTGR%J5ylF_^iPl93QkW` zLFNG3uk{QQy*SJWMDs&eYV$@95~Y>5Md>-Cz;FT73A!9n)au!RzcFIi^SrITcy#0S zya`iez!{cs*rC`ruRw5t_sek)?nOL9nq#{k5a1(vxOtteu!sl}MvV&I4oNjn*7zdm z$)zLt#$R-NjXGr3w~-4E$2nmPgz!?-y*D_Z>PzTVPcr0KJt51Hf%_Gm6_jTm1jPx0 zRgThUoQ#L_jU6%)&3A;dGBlFtgu$vGm0&$W(2*!ye6Ck_UXiH0BcOw$l{CWhK>J?c@VA6SROO*&oNS+M+}cJxdib0iU2nTk`ul}bfyhAVXy`1F2FjA3EKB-G#Zk};Yp71Rw(>R+xCk~lW%3g&gl8> zx-6#5BU8Ue^UL=(%jLkMh2)2-v{Hw+9zDQmn(Na3WHq}`%gNQL3$4*2+o!~FvMH(j z5nl`Gy1C8r{X6#46hGa%9S^T^f)b9Ef{)WSjdE`;UPBhwTU<5zwXJKlLfT$`*C@Li z+SZhl;E?rN{efRF^pG#UezSUs#PRs^sN;7X)?u2@isGq41$e4ZJ>Pvg!*Db+bO4#C zI60Wxy!n~F2E_XkJl~N2aN~TPRNvu%DyOA;W_76&| zAVOFOQrzPVa{P@&g><#`l&L_m+eSqYVazUw!WJ66qvjD`;1H`6cqQH#o#93|5RS=N zkaVU}=)`RxZ*K!(Vud^hI~U&)U(fI#jb3n!pj%iXj9*m`&A)!Pu?9%*NHqkkP6O|@hFsRdkhkcm}Hb1 zIV3Lkq0;x5ziwvdcyB2AJJsM%`IfI(6ZEcHozb*46Ws3~6(ypt$%Rg}FBK^Lk)RZ1sOeZK&CG`7-POCj#Qjd;2Q7C!l(6!+B_*O@I%&2ztfFySy zdDiYDfTgE%J;9BiU0>4SgudA2|9KwzpXl5;%EI1%qOswLPTZ&KQ!M!fdoI5I`^o<| zyB1%Wkh%{8ngd3??8hRQ#nbeEz>H~q%#2`Vv{ULbdOvAqI(^hZ$_Q*dq#T%E zs6U)|Nnv5kq-erLg~V_JoPcS0arlxkGuZm5HVhm&T=FyR;bAS&jAnw&L^UL}J-kV# zA)#Ik#k7Gfbr>Yfjw@r`%P9iu2=}pA8no|Rq=B_GrAbZ~ja zx^stVBXuPsE9UMQm1Za!c+Bku1 zob*-Q>_CpX&%vQIen1|A`6=E~w0zCcW>v8>3^z1b0TlvPAtx@{*v=Bt{f{ovsSO~6 z{K8YDxc9GHAMcux#Az)<$wem?Crk9tHNfTFa~DcXiQ+zD>(R`^&@8j8oTP;e#OG~S zrIV#ee|_e$2JjbB=e5DCElCbR2~Qx6QE74d;j3%|kD<27KC|u1VG?kh9q7F2(pGyA zkrkpP#Ei+@j35L2a^isRB`E){s8ua;=4`w$K@b;F232*WwnXrQAAp1cSs?wr31O%}v>>emg5ssPlBvG|4^L9uy%eE3jDh$@GhZ0{pP%^Oz}^ zhXp}QJ|TIhLfz-oYr!fJ31F{L}l@lJuu%o}2>)4o3X+N=)`2{q#%0{~dCk75wUM3MT#?fc|Op z&k*!1@ylq*Uot;`GXKrkbB*HHcy6V79)Ih)`lr1=`>vkBzwA-|UF=_7SbtLVXJ658 z>7b`a`Y)sZ?lSsQ{Lgln-{QzmeH_2V|7wo;Z;O8J{P;DV7i|pnGv53+fq$|zLGrf% z_2)$YjAYMtezA1>cd`E!*Z$P=XLj{lPdUMV&b9v3@aMVWZw;4dzcl=H%J~1k_D-aq zqW`XG{qqU=v#Rym;_lzYe%HAEobsQk&u;~=L_h8Rowolr>F3s+U*p-sfxkTbk4Bz< n&iv0-o@d!#Ui Date: Tue, 13 Jun 2023 10:59:49 +0800 Subject: [PATCH 056/141] =?UTF-8?q?enhance(meter):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E8=A1=A8=E8=AE=A1=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/enums.go | 18 ++++++++++++++++++ model/meter.go | 2 +- service/meter.go | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/model/enums.go b/model/enums.go index bdbb9c1..fd8b0db 100644 --- a/model/enums.go +++ b/model/enums.go @@ -1,5 +1,9 @@ package model +import ( + "strings" +) + const ( ELECTRICITY_CATE_TWO_PART int16 = iota ELECTRICITY_CATE_UNITARY_PV @@ -15,8 +19,22 @@ const ( METER_INSTALLATION_TENEMENT int16 = iota METER_INSTALLATION_PARK METER_INSTALLATION_POOLING + METER_INSTALLATION_UNDEFINED = 99 ) +func ParseMeterInstallationType(s string) int16 { + switch { + case strings.Contains(s, "商户"): + return METER_INSTALLATION_TENEMENT + case strings.Contains(s, "公共"): + return METER_INSTALLATION_PARK + case strings.Contains(s, "楼道"): + return METER_INSTALLATION_POOLING + default: + return METER_INSTALLATION_UNDEFINED + } +} + const ( PRICING_POLICY_CONSUMPTION int16 = iota PRICING_POLICY_ALL diff --git a/model/meter.go b/model/meter.go index e68b603..d592fdc 100644 --- a/model/meter.go +++ b/model/meter.go @@ -81,7 +81,7 @@ type PooledMeterDetailCompound struct { type MeterImportRow struct { Code string `json:"code" excel:"code"` Address *string `json:"address" excel:"address"` - MeterType int16 `json:"meterType" excel:"meterType"` + MeterType *string `json:"meterType" excel:"meterType"` Building *string `json:"building" excel:"building"` OnFloor *string `json:"onFloor" excel:"onFloor"` Area decimal.NullDecimal `json:"area" excel:"area"` diff --git a/service/meter.go b/service/meter.go index c3c85bc..182c987 100644 --- a/service/meter.go +++ b/service/meter.go @@ -208,7 +208,7 @@ func (ms _MeterService) BatchImportMeters(pid string, file *multipart.FileHeader return vo.MeterCreationForm{ Code: element.Code, Address: element.Address, - MeterType: element.MeterType, + MeterType: model.ParseMeterInstallationType(*element.MeterType), Ratio: element.Ratio, Seq: element.Seq, Enabled: true, From 23e9e2ec4dc467ae86b62e650c5c02b38936476b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 11:11:10 +0800 Subject: [PATCH 057/141] =?UTF-8?q?enhance(meter):=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E8=A1=A8=E8=AE=A1=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=9C=A8=E8=A1=A8=E8=AE=A1=E7=B1=BB=E5=9E=8B=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=87=BA=E7=8E=B0=E9=94=99=E8=AF=AF=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E5=80=99=E5=81=9C=E4=B8=8B=E6=9D=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/enums.go | 12 ++++----- service/meter.go | 68 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/model/enums.go b/model/enums.go index fd8b0db..1e8faf4 100644 --- a/model/enums.go +++ b/model/enums.go @@ -1,6 +1,7 @@ package model import ( + "fmt" "strings" ) @@ -19,19 +20,18 @@ const ( METER_INSTALLATION_TENEMENT int16 = iota METER_INSTALLATION_PARK METER_INSTALLATION_POOLING - METER_INSTALLATION_UNDEFINED = 99 ) -func ParseMeterInstallationType(s string) int16 { +func ParseMeterInstallationType(s string) (int16, error) { switch { case strings.Contains(s, "商户"): - return METER_INSTALLATION_TENEMENT + return METER_INSTALLATION_TENEMENT, nil case strings.Contains(s, "公共"): - return METER_INSTALLATION_PARK + return METER_INSTALLATION_PARK, nil case strings.Contains(s, "楼道"): - return METER_INSTALLATION_POOLING + return METER_INSTALLATION_POOLING, nil default: - return METER_INSTALLATION_UNDEFINED + return -1, fmt.Errorf("提供了一个无法识别的表计类型: %s", s) } } diff --git a/service/meter.go b/service/meter.go index 182c987..b08515d 100644 --- a/service/meter.go +++ b/service/meter.go @@ -204,27 +204,55 @@ func (ms _MeterService) BatchImportMeters(pid string, file *multipart.FileHeader 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: model.ParseMeterInstallationType(*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, - }, + var meterCreationForms = make([]vo.MeterCreationForm, 0) + for row, element := range records { + if element.MeterType != nil { + meterType, err := model.ParseMeterInstallationType(*element.MeterType) + if err != nil { + ms.log.Error("无法识别表计类型。", zap.Int("record_index", row), zap.Error(err)) + errs = append(errs, excel.ExcelAnalysisError{ + Row: row + 1, + Col: 3, + Err: excel.AnalysisError{ + Err: fmt.Errorf("表计类型无法识别"), + }, + }) + } + meterCreationForms = append(meterCreationForms, vo.MeterCreationForm{ + Code: element.Code, + Address: element.Address, + MeterType: 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, + }, + }) + } else { + ms.log.Error("表计类型不能为空。", zap.Int("record_index", row)) + errs = append(errs, excel.ExcelAnalysisError{ + Row: row + 1, + Col: 3, + Err: excel.AnalysisError{ + Err: fmt.Errorf("表计类型不能为空"), + }, + }) } - }) + } + if len(errs) > 0 { + ms.log.Error("表计档案分析器在解析上传的 Excel 文件时发生错误。", zap.Int("error count", len(errs))) + tx.Rollback(ctx) + return errs, fmt.Errorf("表计档案分析器在解析上传的 Excel 文件时发生错误。") + } for _, record := range meterCreationForms { _, err := repository.MeterRepository.CreateOrUpdateMeter(tx, ctx, pid, record) if err != nil { From 7c6f21193198f0072a61f21374234055342c61b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 13:25:54 +0800 Subject: [PATCH 058/141] =?UTF-8?q?enhance(meter):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E7=9A=84=E8=A1=A8=E8=AE=A1=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E4=B8=AD=E7=9A=84=E5=8D=95=E5=85=83=E6=A0=BC?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E6=A0=BC=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/meter_archive.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/excel/meter_archive.go b/excel/meter_archive.go index 8992669..a027418 100644 --- a/excel/meter_archive.go +++ b/excel/meter_archive.go @@ -96,6 +96,26 @@ func (g *MeterArchiveExcelTemplateGenerator) WriteTemplateData(buildings []*mode 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, "I2", "I1048576", 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, "J2", "N1048576", numColStyle) + meterInstallationTypeValidation := excelize.NewDataValidation(false) meterInstallationTypeValidation.SetDropList([]string{"商户表", "公共表", "楼道表"}) meterInstallationTypeValidation.Sqref = "D2:D1048576" From 542efdac86f8ef8df4eea6ad391d1082239802b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 13:28:13 +0800 Subject: [PATCH 059/141] =?UTF-8?q?enhance(meter):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=97=A5=E5=BF=97=E8=AF=AD=E7=A7=8D=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/meter_archive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/excel/meter_archive.go b/excel/meter_archive.go index a027418..872e7a5 100644 --- a/excel/meter_archive.go +++ b/excel/meter_archive.go @@ -74,7 +74,7 @@ func (g MeterArchiveExcelTemplateGenerator) WriteTo(w io.Writer) (int64, error) 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)) + g.log.Debug("选定默认输出表格", zap.String("sheet", defaultSheet)) err = g.file.SetColWidth(defaultSheet, "B", "I", 20) if err != nil { g.log.Error("未能设定长型列宽。", zap.Error(err)) From e2a61d58acd6e75b417f91cf7f7312803272dc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 14:21:00 +0800 Subject: [PATCH 060/141] =?UTF-8?q?enhance(meter):=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=8A=84=E8=A1=A8=E6=A8=A1=E6=9D=BF=E4=B8=8B=E8=BD=BD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 54 ++++++++++++++++++-- excel/meter_reading.go | 111 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 4 deletions(-) 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 +} From ef325aede5d5d8ffc08fceb0103e9decb152dc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 14:25:54 +0800 Subject: [PATCH 061/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E6=AD=A3=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E4=BF=A1=E6=81=AF=E7=9A=84=E6=A3=80=E7=B4=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/meter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repository/meter.go b/repository/meter.go index f59700d..9241ea7 100644 --- a/repository/meter.go +++ b/repository/meter.go @@ -1080,7 +1080,7 @@ func (mr _MeterRepository) ListLastMeterReading(pid string, date types.Date) ([] func (mr _MeterRepository) ListMeterDocForTemplate(pid string) ([]*model.SimpleMeterDocument, error) { mr.log.Info("列出指定园区中的表计与商户的关联详细记录", zap.String("park id", pid)) cacheConditions := []string{pid} - if docs, err := cache.RetrieveSearch[[]*model.SimpleMeterDocument]("simple_meter_doc", cacheConditions...); err == nil { + if docs, err := cache.RetrieveSearch[[]*model.SimpleMeterDocument]("simple_meter_doc", cacheConditions...); err == nil && docs != nil { mr.log.Info("从缓存中获取到了指定园区中的表计与商户的关联详细记录", zap.Int("count", len(*docs))) return *docs, nil } @@ -1110,7 +1110,7 @@ func (mr _MeterRepository) ListMeterDocForTemplate(pid string) ([]*model.SimpleM Where( goqu.I("m.park_id").Eq(pid), goqu.I("m.enabled").IsTrue(), - goqu.I("m.disassociated_at").IsNull(), + goqu.I("tm.disassociated_at").IsNull(), ). Order(goqu.I("m.seq").Asc()). Prepared(true).ToSQL() From 0cffc1b6d18b0d6daf882994e09de813cc82ecd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 14:43:08 +0800 Subject: [PATCH 062/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E8=A1=A5=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E6=8A=84=E8=A1=A8=E8=AE=B0=E5=BD=95=E7=9A=84=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=AF=BC=E5=87=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/meter_reading.go | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/excel/meter_reading.go b/excel/meter_reading.go index 69aaa40..082651f 100644 --- a/excel/meter_reading.go +++ b/excel/meter_reading.go @@ -89,7 +89,7 @@ func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.S return fmt.Errorf("未能设定标题行的高度,%w", err) } - dateTimeExp := "yyyy-mm-dd hh:mm;@" + dateTimeExp := "yyyy-mm-dd hh:mm" dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{ CustomNumFmt: &dateTimeExp, }) @@ -97,9 +97,10 @@ func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.S g.log.Error("未能创建日期时间格式。", zap.Error(err)) return fmt.Errorf("未能创建日期时间格式,%w", err) } - g.file.SetCellStyle(defaultSheet, "B2", "B1048576", dateTimeColStyle) + endCellCoord, _ := excelize.CoordinatesToCellName(2, len(meters)+1) + g.file.SetCellStyle(defaultSheet, "B2", endCellCoord, dateTimeColStyle) - numExp := "0.0000;@" + numExp := "0.0000" numColStyle, err := g.file.NewStyle(&excelize.Style{ CustomNumFmt: &numExp, }) @@ -107,21 +108,12 @@ func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.S 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) - } + endCellCoord, _ = excelize.CoordinatesToCellName(9, len(meters)+1) + g.file.SetCellStyle(defaultSheet, "F2", endCellCoord, numColStyle) 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{}{ + cellCoord, _ := excelize.CoordinatesToCellName(1, i+2) + if err := g.file.SetSheetRow(defaultSheet, cellCoord, &[]interface{}{ meter.Seq, "", meter.Code, @@ -133,10 +125,6 @@ func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.S return fmt.Errorf("向模板写入数据出现错误,%w", err) } } - if err = stream.Flush(); err != nil { - g.log.Error("未能刷新流式写入器。", zap.Error(err)) - return fmt.Errorf("未能刷新流式写入器,%w", err) - } return err } From 424ba2e839787a149d0b4c986cd13160dbd7cdad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 14:47:22 +0800 Subject: [PATCH 063/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E6=AD=A3=E5=86=99?= =?UTF-8?q?=E5=85=A5=20Excel=20=E7=9A=84=E5=86=85=E5=AE=B9=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/meter_archive.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/excel/meter_archive.go b/excel/meter_archive.go index 872e7a5..cc99647 100644 --- a/excel/meter_archive.go +++ b/excel/meter_archive.go @@ -96,7 +96,7 @@ func (g *MeterArchiveExcelTemplateGenerator) WriteTemplateData(buildings []*mode return fmt.Errorf("未能设定标题行高度,%w", err) } - dateTimeExp := "yyyy-mm-dd hh:mm;@" + dateTimeExp := "yyyy-mm-dd hh:mm" dateTimeColStyle, err := g.file.NewStyle(&excelize.Style{ CustomNumFmt: &dateTimeExp, }) @@ -106,7 +106,7 @@ func (g *MeterArchiveExcelTemplateGenerator) WriteTemplateData(buildings []*mode } g.file.SetCellStyle(defaultSheet, "I2", "I1048576", dateTimeColStyle) - numExp := "0.0000;@" + numExp := "0.0000" numColStyle, err := g.file.NewStyle(&excelize.Style{ CustomNumFmt: &numExp, }) From c9b7dc3aecd15267bb1fb61519c1db23c8de81ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 15:54:06 +0800 Subject: [PATCH 064/141] =?UTF-8?q?enhance(meter):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E8=A1=A8=E8=AE=A1=E6=8A=84=E8=A1=A8=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=B8=AD=E6=95=B0=E5=80=BC=E7=9A=84=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/meter_reading.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/excel/meter_reading.go b/excel/meter_reading.go index 082651f..6ad33cc 100644 --- a/excel/meter_reading.go +++ b/excel/meter_reading.go @@ -113,13 +113,14 @@ func (g MeterReadingsExcelTemplateGenerator) WriteTemplateData(meters []*model.S for i, meter := range meters { cellCoord, _ := excelize.CoordinatesToCellName(1, i+2) + ratio, _ := meter.Ratio.Float64() if err := g.file.SetSheetRow(defaultSheet, cellCoord, &[]interface{}{ meter.Seq, "", meter.Code, tools.DefaultTo(meter.Address, ""), tools.DefaultTo(meter.TenementName, ""), - meter.Ratio, + ratio, }); err != nil { g.log.Error("向模板写入数据出现错误。", zap.Error(err)) return fmt.Errorf("向模板写入数据出现错误,%w", err) From d4fbf868003d1b3f0820879c06e1f9389c519d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 20:43:24 +0800 Subject: [PATCH 065/141] =?UTF-8?q?build(deps):=E5=8D=87=E7=BA=A7=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BE=9D=E8=B5=96=E5=BA=93=E7=9A=84=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/abstract.go | 14 ++++----- cache/relation.go | 2 +- go.mod | 56 ++++++++++++++++++------------------ go.sum | 72 ++++++++++++++++++++++++++++++++++++++++++++++ repository/user.go | 4 +-- service/user.go | 6 ++-- 6 files changed, 113 insertions(+), 41 deletions(-) diff --git a/cache/abstract.go b/cache/abstract.go index 02c13d6..63463c4 100644 --- a/cache/abstract.go +++ b/cache/abstract.go @@ -81,23 +81,23 @@ func Delete(key string) (bool, error) { return count > 0, err } -func dissembleScan(result rueidis.RedisResult) (int64, []string, error) { +func dissembleScan(result rueidis.RedisResult) (uint64, []string, error) { var ( err error - cursor int64 + cursor uint64 keys = make([]string, 0) ) results, err := result.ToArray() if err != nil { - return -1, keys, err + return 0, keys, err } - cursor, err = results[0].AsInt64() + cursor, err = results[0].AsUint64() if err != nil { - return -1, keys, err + return 0, keys, err } keys, err = results[1].AsStrSlice() if err != nil { - return -1, keys, err + return 0, keys, err } return cursor, keys, err } @@ -106,7 +106,7 @@ func dissembleScan(result rueidis.RedisResult) (int64, []string, error) { func DeleteAll(pattern string) error { var ( err error - cursor int64 + cursor uint64 keys = make([]string, 0) sKeys []string ) diff --git a/cache/relation.go b/cache/relation.go index d707edb..03cf099 100644 --- a/cache/relation.go +++ b/cache/relation.go @@ -74,7 +74,7 @@ func AbolishRelation(relationName string) error { func ClearOrphanRelationItems() error { var ( err error - cursor int64 + cursor uint64 keys = make([]string, 0) sKeys []string ) diff --git a/go.mod b/go.mod index 3b0e108..9f93995 100644 --- a/go.mod +++ b/go.mod @@ -4,32 +4,32 @@ go 1.19 require ( github.com/deckarep/golang-set/v2 v2.1.0 - github.com/fufuok/utils v0.7.13 + github.com/fufuok/utils v0.10.2 + github.com/georgysavva/scany/v2 v2.0.0 github.com/gofiber/fiber/v2 v2.46.0 github.com/google/uuid v1.3.0 + github.com/jackc/pgx/v5 v5.3.1 github.com/jinzhu/copier v0.3.5 github.com/liamylian/jsontime/v2 v2.0.0 - github.com/mozillazg/go-pinyin v0.19.0 - github.com/rueian/rueidis v0.0.73 - github.com/samber/lo v1.27.0 + github.com/mozillazg/go-pinyin v0.20.0 + github.com/rueian/rueidis v0.0.100 + github.com/samber/lo v1.38.1 github.com/shopspring/decimal v1.3.1 - github.com/spf13/viper v1.12.0 + github.com/spf13/viper v1.16.0 github.com/valyala/fasthttp v1.47.0 - github.com/xuri/excelize/v2 v2.6.1 - go.uber.org/zap v1.23.0 - gopkg.in/natefinch/lumberjack.v2 v2.0.0 + github.com/xuri/excelize/v2 v2.7.1 + go.uber.org/zap v1.24.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( github.com/andybalholm/brotli v1.0.5 // indirect - github.com/georgysavva/scany/v2 v2.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jackc/puddle/v2 v2.2.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect - github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/compress v1.16.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect @@ -44,43 +44,43 @@ require ( github.com/valyala/tcplisten v1.0.0 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/sync v0.1.0 // indirect + golang.org/x/sync v0.2.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect mellium.im/sasl v0.3.0 // indirect ) require ( github.com/doug-martin/goqu/v9 v9.18.0 - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect - github.com/spf13/afero v1.8.2 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.3.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/uptrace/bun v1.1.8 github.com/uptrace/bun/dialect/pgdialect v1.1.8 github.com/uptrace/bun/driver/pgdriver v1.1.8 - github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect - github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.8.0 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect + github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9 // indirect + github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 08a121d..62deb92 100644 --- a/go.sum +++ b/go.sum @@ -68,10 +68,15 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fufuok/utils v0.7.13 h1:FGx8Mnfg0ZB8HdVz1X60JJ2kFu1rtcsFDYUxUTzNKkU= github.com/fufuok/utils v0.7.13/go.mod h1:ztIaorPqZGdbvmW3YlwQp80K8rKJmEy6xa1KwpJSsmk= +github.com/fufuok/utils v0.10.2 h1:jXgE7yBSUW9z+sJs/VQq3o4MH+jN30PzIILVXFw73lE= +github.com/fufuok/utils v0.10.2/go.mod h1:87MJq0gAZDYBgUOpxSGoLkdv8VCuRNOL9vK02F7JC3s= github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU= github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -171,10 +176,13 @@ github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwc github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= +github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -184,6 +192,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -206,10 +216,14 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c= github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc= +github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+GnMFQ= +github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= @@ -235,8 +249,12 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rueian/rueidis v0.0.73 h1:+r0Z6C6HMnkquPgY3zaHVpTqmCyJL56Z36GSlyBrufk= github.com/rueian/rueidis v0.0.73/go.mod h1:FwnfDILF2GETrvXcYFlhIiru/7NmSIm1f+7C5kutO0I= +github.com/rueian/rueidis v0.0.100 h1:22yp/+8YHuWc/vcrp8bkjeE7baD3vygoh2gZ2+xu1KQ= +github.com/rueian/rueidis v0.0.100/go.mod h1:ivvsRYRtAUcf9OnheuKc5Gpa8IebrkLT1P45Lr2jlXE= github.com/samber/lo v1.27.0 h1:GOyDWxsblvqYobqsmUuMddPa2/mMzkKyojlXol4+LaQ= github.com/samber/lo v1.27.0/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= @@ -246,15 +264,23 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -264,9 +290,14 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= @@ -293,10 +324,16 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9 h1:ge5g8vsTQclA5lXDi+PuiAFw5GMIlMHOB/5e1hsf96E= +github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.6.1 h1:ICBdtw803rmhLN3zfvyEGH3cwSmZv+kde7LhTDT659k= github.com/xuri/excelize/v2 v2.6.1/go.mod h1:tL+0m6DNwSXj/sILHbQTYsLi9IF4TW59H2EF3Yrx1AU= +github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI= +github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY= github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83 h1:xVwnvkzzi+OiwhIkWOXvh1skFI6bagk8OvGuazM80Rw= +github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -311,11 +348,18 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -326,6 +370,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -335,6 +380,9 @@ golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -347,10 +395,14 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE= golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= +golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -374,6 +426,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -415,6 +468,9 @@ golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -437,6 +493,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -481,15 +539,21 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U= golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -504,6 +568,9 @@ golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -557,6 +624,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -656,8 +724,12 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/repository/user.go b/repository/user.go index 7610603..4f61829 100644 --- a/repository/user.go +++ b/repository/user.go @@ -15,7 +15,7 @@ import ( "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" - "github.com/fufuok/utils" + "github.com/fufuok/utils/xhash" "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "github.com/samber/lo" @@ -362,7 +362,7 @@ func (ur _UserRepository) UpdatePassword(uid, newCredential string, needReset bo userUpdateQuery := ur.ds. Update("user"). - Set(goqu.Record{"password": utils.Sha512Hex(newCredential), "reset_needed": needReset}). + Set(goqu.Record{"password": xhash.Sha512Hex(newCredential), "reset_needed": needReset}). Where(goqu.Ex{"id": uid}) userSql, userParams, _ := userUpdateQuery. diff --git a/service/user.go b/service/user.go index 7a18799..a81dc3f 100644 --- a/service/user.go +++ b/service/user.go @@ -12,7 +12,7 @@ import ( "electricity_bill_calc/types" "time" - "github.com/fufuok/utils" + "github.com/fufuok/utils/xhash" "github.com/google/uuid" "github.com/samber/lo" "go.uber.org/zap" @@ -27,7 +27,7 @@ var UserService = _UserService{ } func (us _UserService) MatchUserPassword(controlCode, testCode string) bool { - hashedCode := utils.Sha512Hex(testCode) + hashedCode := xhash.Sha512Hex(testCode) return controlCode == hashedCode } @@ -139,7 +139,7 @@ func (us _UserService) CreateUserAccount(user *model.User, detail *model.UserDet detail.Id = user.Id } verifyCode := tools.RandStr(10) - user.Password = utils.Sha512Hex(verifyCode) + user.Password = xhash.Sha512Hex(verifyCode) user.ResetNeeded = true res, err := repository.UserRepository.CreateUser(*user, *detail, nil) if err != nil || !res { From 7da5bd1112d1fa423cec28e1147dc7bafb4765e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 13 Jun 2023 21:01:14 +0800 Subject: [PATCH 066/141] =?UTF-8?q?enhance(meter):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=A1=A8=E8=AE=A1=E6=8A=84=E8=A1=A8=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E4=B8=8A=E4=BC=A0=E8=A7=A3=E6=9E=90=EF=BC=8C=E5=BE=85?= =?UTF-8?q?=E6=B5=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 68 +++++++++++++++++-------- service/meter.go | 117 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 150 insertions(+), 35 deletions(-) diff --git a/controller/meter.go b/controller/meter.go index 7f3c368..28f0e57 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -37,9 +37,10 @@ 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) + router.Post("/reading/:pid/batch", security.EnterpriseAuthorize, uploadMeterReadings) + router.Post("/reading/:pid/:code", security.EnterpriseAuthorize, recordMeterReading) } // 查询指定园区下的表计信息 @@ -202,10 +203,10 @@ func uploadMeterArchive(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } uploadFile, err := c.FormFile("data") @@ -255,10 +256,10 @@ func listAssociatedMeters(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } meterId := c.Params("code") @@ -283,10 +284,10 @@ func bindAssociatedMeters(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } meterId := c.Params("code") @@ -320,10 +321,10 @@ func unbindAssociatedMeters(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } masterMeter := c.Params("master") @@ -356,10 +357,10 @@ func listPooledMeters(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } page := c.QueryInt("page", 1) @@ -388,10 +389,10 @@ func listUnboundMeters(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } keyword := c.Query("keyword") @@ -452,10 +453,10 @@ func queryMeterReadings(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } keyword := tools.EmptyToNil(c.Query("keyword")) @@ -506,10 +507,10 @@ func recordMeterReading(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } meterCode := c.Params("code") @@ -542,10 +543,10 @@ func updateMeterReading(c *fiber.Ctx) error { 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)) + 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: - parkLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + meterLog.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) return result.Forbidden("您无权访问该园区。") } meterCode := c.Params("code") @@ -616,5 +617,32 @@ func downloadMeterReadingsTemplate(c *fiber.Ctx) error { // 处理上传的抄表记录文件 func uploadMeterReadings(c *fiber.Ctx) error { - return nil + parkId := c.Params("pid") + meterLog.Info("从Excel文件中导入抄表档案", zap.String("park id", parkId)) + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + meterLog.Error("无法从Excel文件中导入抄表档案,无法获取当前用户会话", zap.Error(err)) + return result.Unauthorized(err.Error()) + } + 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("您无权访问该园区。") + } + 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}) } diff --git a/service/meter.go b/service/meter.go index b08515d..1eb32ec 100644 --- a/service/meter.go +++ b/service/meter.go @@ -686,28 +686,115 @@ func (ms _MeterService) RecordReading(pid, meterCode string, form *vo.MeterReadi return nil } -// 获取指定园区的全部待抄表计列表,并将其输出到Excel文件模板中,提供生成文件的二进制内容 -func (ms _MeterService) GenerateParkMeterReadingTemplate(pid string, meters []*model.SimpleMeterDocument) ([]byte, error) { - // 步骤1:复制公用模板文件到临时文件夹,文件以园区ID命名 - - // 步骤2:打开复制后的园区文件 - // 步骤3:获取园区中需要抄表的表计列表 - // 步骤4:循环表计列表,将表计信息写入到Excel文件中 - // 步骤5:将生成的临时文件的具体二进制内容返回给调用者 - return nil, nil -} - // 处理上传的Excel格式的表计抄表记录,所有满足审查条件的记录都将被保存到数据库中。 // 无论峰谷表计还是普通表计,只要抄表记录中不存在峰谷数据,都将自动使用平段配平。 -func (ms _MeterService) BatchImportReadings(pid string, uploadContent []byte) error { +func (ms _MeterService) BatchImportReadings(pid string, file *multipart.FileHeader) ([]excel.ExcelAnalysisError, error) { + ms.log.Info("处理上传的Excel格式的表计抄表记录", zap.String("park id", pid)) + ctx, cancel := global.TimeoutContext() + defer cancel() // 步骤1:将解析到的数据转换成创建表单数据 + activeFile, err := file.Open() + if err != nil { + ms.log.Error("无法打开上传的抄表数据文件。", zap.Error(err)) + return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法打开上传的抄表数据文件,%w", err) + } + analyzer, err := excel.NewMeterReadingsExcelAnalyzer(activeFile) + if err != nil { + ms.log.Error("无法根据上传的 Excel 文件创建表计抄表数据解析器。", zap.Error(err)) + return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法根据上传的 Excel 文件创建表计抄表数据解析器,%w", err) + } + records, errs := analyzer.Analysis(*new(model.ReadingImportRow)) + if len(errs) > 0 { + ms.log.Error("表计抄表数据解析器在解析上传的 Excel 文件时发生错误。", zap.Int("error count", len(errs))) + return errs, fmt.Errorf("表计抄表数据解析器在解析上传的 Excel 文件时发生错误。") + } + ms.log.Debug("已经解析到的上传数据", zap.Any("records", records)) // 步骤2:对目前已经解析到的数据进行合法性检测,检测包括表计编号在同一抄表时间是否重复 + var collectRecords = make(map[types.DateTime][]string, 0) + for _, record := range records { + if _, ok := collectRecords[record.ReadAt]; !ok { + collectRecords[record.ReadAt] = []string{} + } + collectRecords[record.ReadAt] = append(collectRecords[record.ReadAt], record.Code) + } + for readAt, codes := range collectRecords { + valCounts := lo.CountValues(codes) + for code, count := range valCounts { + if count > 1 { + errs = append(errs, excel.ExcelAnalysisError{ + Row: 0, + Col: 0, + Err: excel.AnalysisError{ + Err: fmt.Errorf("表计编号 %s 在同一抄表时间 %s 内重复出现 %d 次", code, readAt.ToString(), count), + }, + }) + } + } + } + if len(errs) > 0 { + ms.log.Error("表计抄表数据解析器在解析上传的 Excel 文件时发生错误。", zap.Int("error count", len(errs))) + return errs, fmt.Errorf("表计抄表数据解析器在解析上传的 Excel 文件时发生错误。") + } // 步骤3:从数据库中获取当前园区中已有的表计编号 + meters, err := repository.MeterRepository.AllMeters(pid) + if err != nil { + ms.log.Error("无法从数据库中获取当前园区中已有的表计编号。", zap.Error(err)) + return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法从数据库中获取当前园区中已有的表计编号,%w", err) + } // 步骤4.0:启动数据库事务 + tx, err := global.DB.Begin(ctx) + if err != nil { + ms.log.Error("无法启动数据库事务。", zap.Error(err)) + return make([]excel.ExcelAnalysisError, 0), fmt.Errorf("无法启动数据库事务,%w", err) + } // 步骤4.1:对比检查数据库中的表计编号与上传文件中的表计编号是否存在差异。非差异内容将直接保存 - // 步骤4.1.1:抄表的表计在数据库中已经存在,可以直接保存起数据。 - // 步骤4.1.2:抄表表计在数据库中不存在,需要将其记录进入错误。 + for row, record := range records { + meter, exists := lo.Find(meters, func(element *model.MeterDetail) bool { + return element.Code == record.Code + }) + if exists { + // 步骤4.1.1:抄表的表计在数据库中已经存在,可以直接保存起数据。 + _, err := repository.MeterRepository.RecordReading(tx, ctx, pid, record.Code, meter.MeterType, meter.Ratio, &vo.MeterReadingForm{ + ReadAt: lo.ToPtr(record.ReadAt), + Overall: record.Overall, + Critical: record.Critical.Decimal, + Peak: record.Peak.Decimal, + Flat: record.Overall.Sub(record.Peak.Decimal).Sub(record.Valley.Decimal).Sub(record.Critical.Decimal), + Valley: record.Valley.Decimal, + }) + if err != nil { + ms.log.Error("无法在数据插入阶段保存抄表信息。", zap.String("meter code", record.Code), zap.Error(err)) + errs = append(errs, excel.ExcelAnalysisError{ + Row: row + 1, + Col: 0, + Err: excel.AnalysisError{ + Err: fmt.Errorf("无法在数据插入阶段保存抄表信息,%w", err), + }, + }) + } + } else { + // 步骤4.1.2:抄表表计在数据库中不存在,需要将其记录进入错误。 + errs = append(errs, excel.ExcelAnalysisError{ + Row: row + 1, + Col: 0, + Err: excel.AnalysisError{ + Err: fmt.Errorf("表计编号 %s 在系统中不存在", record.Code), + }, + }) + } + } // 步骤4.3:如果批处理过程中存在错误,撤销全部导入动作。 + if len(errs) > 0 { + ms.log.Error("表计抄表数据解析器在解析上传的 Excel 文件时发生错误。", zap.Int("error count", len(errs))) + tx.Rollback(ctx) + return errs, fmt.Errorf("表计抄表数据解析器在解析上传的 Excel 文件时发生错误。") + } // 步骤5:执行事务,更新数据库,获取完成更改的行数。 - return nil + 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 } From 2558a83024613037bcad502dcfc1b7d52163a4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 14 Jun 2023 14:26:18 +0800 Subject: [PATCH 067/141] =?UTF-8?q?enhance(tenement):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=95=86=E6=88=B7=E9=83=A8=E5=88=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E6=95=B0=E6=8D=AE=E5=BA=93=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/invoice.go | 10 + model/tenement.go | 22 ++ repository/tenement.go | 517 +++++++++++++++++++++++++++++++++++++++++ vo/tenement.go | 16 ++ 4 files changed, 565 insertions(+) create mode 100644 model/invoice.go create mode 100644 model/tenement.go create mode 100644 repository/tenement.go create mode 100644 vo/tenement.go diff --git a/model/invoice.go b/model/invoice.go new file mode 100644 index 0000000..1f07041 --- /dev/null +++ b/model/invoice.go @@ -0,0 +1,10 @@ +package model + +type InvoiceTitle struct { + Name string `json:"name"` + USCI string `json:"usci"` + Address string `json:"address"` + Phone string `json:"phone"` + Bank string `json:"bank"` + Account string `json:"account"` +} diff --git a/model/tenement.go b/model/tenement.go new file mode 100644 index 0000000..16448f4 --- /dev/null +++ b/model/tenement.go @@ -0,0 +1,22 @@ +package model + +import "electricity_bill_calc/types" + +type Tenement struct { + Id string `json:"id"` + Park string `json:"parkId" db:"park_id"` + FullName string `json:"fullName" db:"full_name"` + ShortName *string `json:"shortName" db:"short_name"` + Address string `json:"address"` + ContactName string `json:"contactName" db:"contact_name"` + ContactPhone string `json:"contactPhone" db:"contact_phone"` + Building string `json:"building"` + BuildingName *string `json:"buildingName" db:"building_name"` + OnFloor *string `json:"onFloor" db:"on_floor"` + InvoiceInfo *InvoiceTitle `json:"invoiceInfo" db:"invoice_info"` + MovedInAt *types.DateTime `json:"movedInAt" db:"moved_in_at"` + MovedOutAt *types.DateTime `json:"movedOutAt" db:"moved_out_at"` + CreatedAt types.DateTime `json:"createdAt" db:"created_at"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` + DeletedAt *types.DateTime `json:"deletedAt" db:"deleted_at"` +} diff --git a/repository/tenement.go b/repository/tenement.go new file mode 100644 index 0000000..48642a6 --- /dev/null +++ b/repository/tenement.go @@ -0,0 +1,517 @@ +package repository + +import ( + "context" + "electricity_bill_calc/cache" + "electricity_bill_calc/config" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/tools" + "electricity_bill_calc/tools/serial" + "electricity_bill_calc/types" + "electricity_bill_calc/vo" + "fmt" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "go.uber.org/zap" +) + +type _TenementRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var TenementRepository = _TenementRepository{ + log: logger.Named("Repository", "Tenement"), + ds: goqu.Dialect("postgres"), +} + +// 判断指定商户是否属于指定用户的管辖 +func (tr _TenementRepository) IsTenementBelongs(tid, uid string) (bool, error) { + tr.log.Info("检查指定商户是否属于指定企业管辖", zap.String("Tenement", tid), zap.String("Enterprise", uid)) + cacheConditions := []string{ + tid, "belongs", uid, + } + if exists, err := cache.CheckExists("tenement", cacheConditions...); err != nil && exists { + tr.log.Error("检查指定商户是否属于指定企业管辖失败", zap.Error(err)) + return exists, err + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + countSql, countArgs, _ := tr.ds. + From(goqu.T("tenement").As("t")). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("t.park_id").Eq(goqu.I("p.id")))). + Select(goqu.COUNT("t.*")). + Where( + goqu.I("t.id").Eq(tid), + goqu.I("p.user_id").Eq(uid), + ). + Prepared(true).ToSQL() + var count int + if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil { + tr.log.Error("检查指定商户是否属于指定企业管辖失败", zap.Error(err)) + return false, err + } + if count > 0 { + cache.CacheExists([]string{fmt.Sprintf("tenement:%s", tid)}, "tenement", cacheConditions...) + } + + return count > 0, nil +} + +// 列出指定园区中的所有商户 +func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, building *string, startDate, endDate *types.Date, state *string) ([]*model.Tenement, int64, error) { + tr.log.Info( + "检索查询指定园区中符合条件的商户", + zap.String("Park", pid), + zap.Uint("Page", page), + zap.Stringp("Keyword", keyword), + zap.Stringp("Building", building), + logger.DateFieldp("StartDate", startDate), + logger.DateFieldp("EndDate", endDate), + zap.Stringp("State", state), + ) + cacheConditions := []string{ + pid, + fmt.Sprintf("%d", page), + cache.NullableStringKey(keyword), + cache.NullableStringKey(building), + cache.NullableConditionKey(startDate), + cache.NullableConditionKey(endDate), + cache.NullableStringKey(state), + } + + if tenements, total, err := cache.RetrievePagedSearch[[]*model.Tenement]("tenements", cacheConditions...); err != nil && tenements != nil { + tr.log.Info("从缓存中获取到了符合条件的商户记录", zap.Int64("Total", total), zap.Int("Count", len(*tenements))) + return *tenements, total, nil + } + + ctx, cancel := global.TimeoutContext() + defer cancel() + + tenementQuery := tr.ds. + From(goqu.T("tenement").As("t")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). + Select("t.*", goqu.I("b.name").As("building_name")) + countQuery := tr.ds. + From(goqu.T("tenement").As("t")). + Select(goqu.COUNT("t.*")) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + tenementQuery = tenementQuery.Where( + goqu.Or( + goqu.I("t.full_name").ILike(pattern), + goqu.I("t.short_name").ILike(pattern), + goqu.I("t.abbr").ILike(pattern), + goqu.I("t.contact_name").ILike(pattern), + goqu.I("t.contact_phone").ILike(pattern), + goqu.I("t.address").ILike(pattern), + ), + ) + countQuery = countQuery.Where( + goqu.Or( + goqu.I("t.full_name").ILike(pattern), + goqu.I("t.short_name").ILike(pattern), + goqu.I("t.abbr").ILike(pattern), + goqu.I("t.contact_name").ILike(pattern), + goqu.I("t.contact_phone").ILike(pattern), + goqu.I("t.address").ILike(pattern), + ), + ) + } + + if building != nil && len(*building) > 0 { + tenementQuery = tenementQuery.Where(goqu.I("t.building").Eq(*building)) + countQuery = countQuery.Where(goqu.I("t.building").Eq(*building)) + } + + if startDate != nil { + tenementQuery = tenementQuery.Where( + goqu.Or( + goqu.I("t.moved_in_at").Gte(startDate.ToBeginningOfDate()), + goqu.I("t.moved_out_at").Gte(startDate.ToBeginningOfDate()), + ), + ) + countQuery = countQuery.Where( + goqu.Or( + goqu.I("t.moved_in_at").Gte(startDate.ToBeginningOfDate()), + goqu.I("t.moved_out_at").Gte(startDate.ToBeginningOfDate()), + ), + ) + } + + if endDate != nil { + tenementQuery = tenementQuery.Where( + goqu.Or( + goqu.I("t.moved_in_at").Lte(endDate.ToEndingOfDate()), + goqu.I("t.moved_out_at").Lte(endDate.ToEndingOfDate()), + ), + ) + countQuery = countQuery.Where( + goqu.Or( + goqu.I("t.moved_in_at").Lte(endDate.ToEndingOfDate()), + goqu.I("t.moved_out_at").Lte(endDate.ToEndingOfDate()), + ), + ) + } + + if state != nil && *state == "1" { + tenementQuery = tenementQuery.Where( + goqu.I("t.moved_out_at").IsNull(), + ) + countQuery = countQuery.Where( + goqu.I("t.moved_out_at").IsNull(), + ) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + tenementQuery = tenementQuery.Order(goqu.I("t.created_at").Desc()).Limit(config.ServiceSettings.ItemsPageSize).Offset(startRow) + + tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + + var ( + tenements []*model.Tenement = make([]*model.Tenement, 0) + total int64 + ) + if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil { + tr.log.Error("检索查询指定园区中符合条件的商户失败", zap.Error(err)) + return tenements, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { + tr.log.Error("检索查询指定园区中符合条件的商户总数量失败", zap.Error(err)) + return tenements, 0, err + } + + cache.CachePagedSearch(tenements, total, []string{fmt.Sprintf("tenements:%s", pid)}, "tenements", cacheConditions...) + + return tenements, total, nil +} + +// 查询指定园区中某一商户下的所有表计编号,不包含公摊表计 +func (tr _TenementRepository) ListMeterCodesBelongsTo(pid, tid string) ([]string, error) { + tr.log.Info("查询指定商户下所有的表计编号", zap.String("Park", pid), zap.String("Tenement", tid)) + cacheConditions := []string{ + pid, tid, + } + if meterCodes, err := cache.RetrieveSearch[[]string]("tenement_submeter", cacheConditions...); err != nil && meterCodes != nil { + tr.log.Info("从缓存中获取到了指定商户下所有的表计编号", zap.Int("Count", len(*meterCodes))) + return *meterCodes, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + sql, args, _ := tr.ds. + From("tenement_meter"). + Select("meter_id"). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("tenement_id").Eq(tid), + goqu.I("disassociated_at").IsNull(), + ). + Prepared(true).ToSQL() + + var meterCodes []string = make([]string, 0) + if err := pgxscan.Select(ctx, global.DB, &meterCodes, sql, args...); err != nil { + tr.log.Error("查询指定商户下所有的表计编号失败", zap.Error(err)) + return meterCodes, err + } + + cache.CacheSearch(&meterCodes, []string{"tenement", "tenement_submeter", "meter", fmt.Sprintf("tenement:%s", pid)}, "tenement_submeter", cacheConditions...) + + return meterCodes, nil +} + +// 在指定园区中创建一个新的商户 +func (tr _TenementRepository) AddTenement(tx pgx.Tx, ctx context.Context, pid string, tenement *vo.TenementCreationForm) error { + tr.log.Info("在指定园区中创建一个新的商户", zap.String("Park", pid)) + + serial.StringSerialRequestChan <- 1 + tenementId := serial.Prefix("T", <-serial.StringSerialResponseChan) + currentTime := types.Now() + createSql, createArgs, _ := tr.ds. + Insert("tenement"). + Cols( + "id", "park_id", "full_name", "short_name", "abbr", "address", "contact_name", "contact_phone", + "building", "on_floor", "invoice_info", + "moved_in_at", "created_at", "last_modified_at", + ). + Vals( + goqu.Vals{ + tenementId, + pid, + tenement.Name, + tenement.ShortName, + tools.PinyinAbbr(tenement.Name), + tenement.Address, + tenement.Contact, + tenement.Phone, + tenement.Building, + tenement.OnFloor, + &model.InvoiceTitle{ + Name: tenement.Name, + USCI: tenement.USCI, + Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""), + Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""), + Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""), + Account: tools.DefaultOrEmptyStr(tenement.Account, ""), + }, + currentTime, + currentTime, + currentTime, + }, + ). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil { + tr.log.Error("在指定园区中创建一个新的商户失败", zap.Error(err)) + return err + } + return nil +} + +// 向园区中指定商户下绑定一个新的表计 +func (tr _TenementRepository) BindMeter(tx pgx.Tx, ctx context.Context, pid, tid, meter string) error { + tr.log.Info("向园区中指定商户下绑定一个新的表计", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meter)) + + createSql, createArgs, _ := tr.ds. + Insert("tenement_meter"). + Cols( + "park_id", "tenement_id", "meter_id", "associated_at", + ). + Vals( + goqu.Vals{ + pid, + tid, + meter, + types.Now(), + }, + ). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil { + tr.log.Error("向园区中指定商户下绑定一个新的表计失败", zap.Error(err)) + return err + } + return nil +} + +// 将指定商户与指定表计解绑 +func (tr _TenementRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, tid, meter string) error { + tr.log.Info("将指定商户与指定表计解绑", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meter)) + + updateSql, updateArgs, _ := tr.ds. + Update("tenement_meter"). + Set( + goqu.Record{ + "disassociated_at": types.Now(), + }, + ). + Where( + goqu.I("park_id").Eq(pid), + goqu.I("tenement_id").Eq(tid), + goqu.I("meter_id").Eq(meter), + ). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil { + tr.log.Error("将指定商户与指定表计解绑失败", zap.Error(err)) + return err + } + return nil +} + +// 修改指定商户的信息 +func (tr _TenementRepository) UpdateTenement(pid, tid string, tenement *vo.TenementCreationForm) error { + tr.log.Info("修改指定商户的信息", zap.String("Park", pid), zap.String("Tenement", tid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + updateSql, updateArgs, _ := tr.ds. + Update("tenement"). + Set( + goqu.Record{ + "full_name": tenement.Name, + "short_name": tenement.ShortName, + "abbr": tools.PinyinAbbr(tenement.Name), + "address": tenement.Address, + "contact_name": tenement.Contact, + "contact_phone": tenement.Phone, + "building": tenement.Building, + "on_floor": tenement.OnFloor, + "invoice_info": &model.InvoiceTitle{ + Name: tenement.Name, + USCI: tenement.USCI, + Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""), + Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""), + Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""), + Account: tools.DefaultOrEmptyStr(tenement.Account, ""), + }, + "last_modified_at": types.Now(), + }, + ). + Where( + goqu.I("id").Eq(tid), + goqu.I("park_id").Eq(pid), + ). + Prepared(true).ToSQL() + if _, err := global.DB.Exec(ctx, updateSql, updateArgs...); err != nil { + tr.log.Error("修改指定商户的信息失败", zap.Error(err)) + return err + } + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + return nil +} + +// 迁出指定商户 +func (tr _TenementRepository) MoveOut(tx pgx.Tx, ctx context.Context, pid, tid string) error { + tr.log.Info("迁出指定商户", zap.String("Park", pid), zap.String("Tenement", tid)) + + updateSql, updateArgs, _ := tr.ds. + Update("tenement"). + Set( + goqu.Record{ + "moved_out_at": types.Now(), + }, + ). + Where( + goqu.I("id").Eq(tid), + goqu.I("park_id").Eq(pid), + ). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil { + tr.log.Error("迁出指定商户失败", zap.Error(err)) + return err + } + return nil +} + +// 列出用于下拉列表的符合指定条件的商户信息 +func (tr _TenementRepository) ListForSelect(uid string, pid, keyword *string, limit *uint) ([]*model.Tenement, error) { + tr.log.Info("列出用于下拉列表的符合指定条件的商户信息", zap.String("Ent", uid), zap.String("Park", tools.DefaultOrEmptyStr(pid, "All")), zap.Stringp("Keyword", keyword), zap.Uintp("Limit", limit)) + cacheConditions := []string{ + uid, + cache.NullableStringKey(pid), + cache.NullableStringKey(keyword), + fmt.Sprintf("%d", tools.DefaultTo(limit, 0)), + } + if tenements, err := cache.RetrieveSearch[[]*model.Tenement]("tenement_choice", cacheConditions...); err != nil && tenements != nil { + tr.log.Info("从缓存中获取到了用于下拉列表的符合指定条件的商户信息", zap.Int("Count", len(*tenements))) + return *tenements, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + tenementQuery := tr.ds. + From(goqu.T("tenement").As("t")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("b.park_id")))). + Select( + "t.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("p.user_id").Eq(uid), + goqu.I("t.moved_out_at").IsNull(), + ) + + if pid != nil && len(*pid) > 0 { + tenementQuery = tenementQuery.Where(goqu.I("p.id").Eq(*pid)) + } + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + tenementQuery = tenementQuery.Where( + goqu.Or( + goqu.I("t.full_name").ILike(pattern), + goqu.I("t.short_name").ILike(pattern), + goqu.I("t.abbr").ILike(pattern), + ), + ) + } + + tenementQuery = tenementQuery.Order(goqu.I("t.created_at").Desc()) + + if limit != nil && *limit > 0 { + tenementQuery = tenementQuery.Limit(*limit) + } + + tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL() + + var tenements = make([]*model.Tenement, 0) + if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil { + tr.log.Error("列出用于下拉列表的符合指定条件的商户信息失败", zap.Error(err)) + return tenements, err + } + + cache.CacheSearch(&tenements, []string{"tenement"}, "tenement_choice", cacheConditions...) + + return tenements, nil +} + +// 列出指定园区中在指定时间区间内存在过入住的商户 +func (tr _TenementRepository) ListTenementsInTimeRange(pid string, start, end types.Date) ([]*model.Tenement, error) { + tr.log.Info("列出指定园区中在指定时间区间内存在过入住的商户", zap.String("Park", pid), logger.DateField("Start", start), logger.DateField("End", end)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tenementQuery := tr.ds. + From(goqu.T("tenement").As("t")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). + Select( + "t.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("t.park_id").Eq(pid), + goqu.I("t.moved_in_at").Lte(end.ToEndingOfDate()), + goqu.Or( + goqu.I("t.moved_out_at").IsNull(), + goqu.I("t.moved_out_at").Gte(start.ToBeginningOfDate()), + ), + ). + Order(goqu.I("t.created_at").Desc()) + + tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL() + + var tenements = make([]*model.Tenement, 0) + if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil { + tr.log.Error("列出指定园区中在指定时间区间内存在过入住的商户失败", zap.Error(err)) + return tenements, err + } + + return tenements, nil +} + +// 获取指定园区中指定商户的详细信息 +func (tr _TenementRepository) RetrieveTenementDetail(pid, tid string) (*model.Tenement, error) { + tr.log.Info("获取指定园区中指定商户的详细信息", zap.String("Park", pid), zap.String("Tenement", tid)) + if tenement, err := cache.RetrieveEntity[model.Tenement](fmt.Sprintf("tenement:%s", pid), tid); err != nil && tenement != nil { + tr.log.Info("从缓存中获取到了指定园区中指定商户的详细信息") + return tenement, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + tenementSql, tenementArgs, _ := tr.ds. + From(goqu.T("tenement").As("t")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). + Select( + "t.*", goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("t.id").Eq(tid), + goqu.I("t.park_id").Eq(pid), + ). + Prepared(true).ToSQL() + var tenement model.Tenement + if err := pgxscan.Get(ctx, global.DB, &tenement, tenementSql, tenementArgs...); err != nil { + tr.log.Error("获取指定园区中指定商户的详细信息失败", zap.Error(err)) + return nil, err + } + + cache.CacheEntity(&tenement, []string{"tenement", fmt.Sprintf("tenement:%s", pid)}, fmt.Sprintf("tenement:%s", pid), tid) + + return &tenement, nil +} diff --git a/vo/tenement.go b/vo/tenement.go new file mode 100644 index 0000000..5916612 --- /dev/null +++ b/vo/tenement.go @@ -0,0 +1,16 @@ +package vo + +type TenementCreationForm struct { + Name string `json:"name"` + ShortName *string `json:"shortName"` + Address string `json:"address"` + Contact string `json:"contact"` + Phone string `json:"phone"` + Building *string `json:"building"` + OnFloor *string `json:"onFloor"` + USCI string `json:"usci"` + InvoiceAddress *string `json:"invoiceAddress"` + InvoicePhone *string `json:"invoicePhone"` + Bank *string `json:"bank"` + Account *string `json:"bankAccount"` +} From d97db0cf501e4a1560a69f3f963c534b39bb46f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 14 Jun 2023 17:26:09 +0800 Subject: [PATCH 068/141] =?UTF-8?q?enhance(tenement):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=95=86=E6=88=B7=E4=B8=8E=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E8=81=94=E5=90=88=E6=93=8D=E4=BD=9C=E7=9A=84?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/tenement.go | 245 ++++++++++++++++++++++++++++++++++++++++++++ vo/reading.go | 5 + 2 files changed, 250 insertions(+) create mode 100644 service/tenement.go diff --git a/service/tenement.go b/service/tenement.go new file mode 100644 index 0000000..c0999bb --- /dev/null +++ b/service/tenement.go @@ -0,0 +1,245 @@ +package service + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "electricity_bill_calc/vo" + "fmt" + + "github.com/samber/lo" + "go.uber.org/zap" +) + +type _TenementService struct { + log *zap.Logger +} + +var TenementService = _TenementService{ + log: logger.Named("Service", "Tenement"), +} + +// 列出指定商户下的全部计量表计,不包含公摊表计 +func (ts _TenementService) ListMeter(pid, tid string) ([]*model.MeterDetail, error) { + ts.log.Info("列出指定商户下的全部表计", zap.String("Park", pid), zap.String("Tenement", tid)) + meterCodes, err := repository.TenementRepository.ListMeterCodesBelongsTo(pid, tid) + if err != nil { + ts.log.Error("列出指定商户下的全部表计失败,未能获取属于商户的表计编号", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + meters, err := repository.MeterRepository.ListMetersByIDs(pid, meterCodes) + if err != nil { + ts.log.Error("列出指定商户下的全部表计失败,未能获取表计编号对应的表计详细信息", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + return meters, nil +} + +// 增加一个新的商户 +func (ts _TenementService) CreateTenementRecord(pid string, creationForm *vo.TenementCreationForm) error { + ts.log.Info("增加一个新的商户", zap.String("Park", pid), zap.Any("Form", creationForm)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("增加一个新商户失败的,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + err = repository.TenementRepository.AddTenement(tx, ctx, pid, creationForm) + if err != nil { + ts.log.Error("增加一个新商户失败的,未能增加商户记录", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能增加商户记录,%w", err) + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("增加一个新商户失败的,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + return nil +} + +// 向商户绑定一个新表计 +func (ts _TenementService) BindMeter(pid, tid, meterCode string, reading *vo.MeterReadingForm) error { + ts.log.Info("向商户绑定一个新表计", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meterCode)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + meterDetail, err := repository.MeterRepository.FetchMeterDetail(pid, meterCode) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能获取表计详细信息", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取表计详细信息,%w", err) + } + err = repository.TenementRepository.BindMeter(tx, ctx, pid, tid, meterCode) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能绑定表计", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能绑定表计,%w", err) + } + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meterDetail.MeterType, meterDetail.Ratio, reading) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,记录表计读数出现错误", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数出现错误,%w", err) + } + if !ok { + ts.log.Error("向商户绑定一个新表计失败,记录表计读数失败") + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数失败") + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} + +// 解除商户与指定表计的绑定 +func (ts _TenementService) UnbindMeter(pid, tid, meterCode string, reading *vo.MeterReadingForm) error { + ts.log.Info("解除商户与指定表计的绑定", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meterCode)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + meterDetail, err := repository.MeterRepository.FetchMeterDetail(pid, meterCode) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能获取表计详细信息", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取表计详细信息,%w", err) + } + err = repository.TenementRepository.UnbindMeter(tx, ctx, pid, tid, meterCode) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能解除绑定", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能解除绑定,%w", err) + } + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meterDetail.MeterType, meterDetail.Ratio, reading) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,记录表计读数出现错误", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数出现错误,%w", err) + } + if !ok { + ts.log.Error("解除商户与指定表计的绑定失败,记录表计读数失败") + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数失败") + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} + +// 迁出指定商户 +func (ts _TenementService) MoveOutTenement(pid, tid string, reading []*vo.MeterReadingFormWithCode) error { + ts.log.Info("迁出指定商户", zap.String("Park", pid), zap.String("Tenement", tid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("迁出指定商户失败,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + meterCodes, err := repository.TenementRepository.ListMeterCodesBelongsTo(pid, tid) + if err != nil { + ts.log.Error("迁出指定商户失败,未能获取属于商户的表计编号", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取属于商户的表计编号,%w", err) + } + meters, err := repository.MeterRepository.ListMetersByIDs(pid, meterCodes) + if err != nil { + ts.log.Error("迁出指定商户失败,未能获取表涉及计编号对应的表计详细信息", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取涉及表计编号对应的表计详细信息,%w", err) + } + + for _, meterCode := range meterCodes { + meterDetail, exists := lo.Find(meters, func(m *model.MeterDetail) bool { + return m.Code == meterCode + }) + if !exists { + ts.log.Error("迁出指定商户失败,找不到指定表计的详细信息", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("找不到指定表计[%s]的详细信息,%w", meterCode, err) + } + if meterDetail.MeterType != model.METER_INSTALLATION_TENEMENT { + ts.log.Error("迁出指定商户失败,需要解绑的表计不是商户表计", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("需要解绑的表计[%s]不是商户表计,%w", meterCode, err) + } + reading, exists := lo.Find(reading, func(r *vo.MeterReadingFormWithCode) bool { + return r.Code == meterCode + }) + if !exists { + ts.log.Error("迁出指定商户失败,找不到指定表计的抄表信息", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("找不到指定表计[%s]的抄表信息,%w", meterCode, err) + } + err = repository.TenementRepository.UnbindMeter(tx, ctx, pid, tid, meterCode) + if err != nil { + ts.log.Error("迁出指定商户失败,未能解除表计绑定", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能解除表计[%s]绑定,%w", meterCode, err) + } + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meterDetail.MeterType, meterDetail.Ratio, &reading.MeterReadingForm) + if err != nil { + ts.log.Error("迁出指定商户失败,记录表计抄表信息出现错误", zap.String("Meter", meterCode), zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计[%s]抄表信息出现错误,%w", meterCode, err) + } + if !ok { + ts.log.Error("迁出指定商户失败,记录表计抄表数据失败", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计[%s]抄表数据失败", meterCode) + } + } + err = repository.TenementRepository.MoveOut(tx, ctx, pid, tid) + if err != nil { + ts.log.Error("迁出指定商户失败,未能迁出指定商户", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能迁出指定商户,%w", err) + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("迁出指定商户失败,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} diff --git a/vo/reading.go b/vo/reading.go index d3c3f1e..0826d01 100644 --- a/vo/reading.go +++ b/vo/reading.go @@ -22,6 +22,11 @@ func (r MeterReadingForm) Validate() bool { return flat.LessThan(decimal.Zero) } +type MeterReadingFormWithCode struct { + Code string `json:"code"` + MeterReadingForm +} + type MeterReadingDetailResponse struct { Code string `json:"code"` Park string `json:"parkId"` From 77587b8157312320eb9a7092707d6bf0fe434148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 14 Jun 2023 21:22:10 +0800 Subject: [PATCH 069/141] =?UTF-8?q?enhance(types):=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=B1=BB=E5=9E=8B=E5=A2=9E=E5=8A=A0=E4=BB=8E?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E8=A7=A3=E6=9E=90=E6=88=90=E4=B8=BA?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E6=97=B6=E9=97=B4=E6=8C=87=E9=92=88=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E5=87=BD=E6=95=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/date.go | 15 +++++++++++++++ types/datetime.go | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/types/date.go b/types/date.go index d1703aa..4897776 100644 --- a/types/date.go +++ b/types/date.go @@ -57,6 +57,21 @@ func ParseDate(t string) (Date, error) { return NewEmptyDate(), fmt.Errorf("无法解析给定的日期,格式不正确。") } +func ParseDatep(t string) (*Date, error) { + if len(t) == 0 { + return nil, fmt.Errorf("不能解析空白的日期时间。") + } + for _, layout := range dateLayouts { + d, err := time.ParseInLocation(layout, t, loc) + if err == nil { + return &Date{ + Time: d, + }, nil + } + } + return nil, fmt.Errorf("无法解析给定的日期,格式不正确。") +} + func ParseDateWithDefault(t string, defaultDate Date) Date { if len(t) == 0 { return defaultDate diff --git a/types/datetime.go b/types/datetime.go index d6d8140..56e4025 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -67,6 +67,22 @@ func ParseDateTime(t string) (DateTime, error) { return NewEmptyDateTime(), fmt.Errorf("无法解析给定的日期时间,格式不正确。") } +func ParseDateTimep(t string) (*DateTime, error) { + if len(t) == 0 { + return nil, fmt.Errorf("不能解析空白的日期时间。") + } + for _, layout := range datetimeLayouts { + fmt.Printf("Parse: %s, Try layout: %s\n", t, layout) + d, err := time.ParseInLocation(layout, t, loc) + if err == nil { + return &DateTime{ + Time: d, + }, nil + } + } + return nil, fmt.Errorf("无法解析给定的日期时间,格式不正确。") +} + var _ driver.Valuer = (*DateTime)(nil) func (dt DateTime) Value() (driver.Value, error) { From be270597f50957cccfa45f337be735e0873b7d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 15 Jun 2023 05:48:38 +0800 Subject: [PATCH 070/141] =?UTF-8?q?refactor(park):=E5=9B=AD=E5=8C=BA?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=9A=84=E5=BD=92=E5=B1=9E=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E9=87=87=E7=94=A8=E9=80=9A=E7=94=A8=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=AE=8C=E6=88=90=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/abstract.go | 22 +++++++++ controller/park.go | 103 +++++++++-------------------------------- 2 files changed, 43 insertions(+), 82 deletions(-) diff --git a/controller/abstract.go b/controller/abstract.go index 8d34e1c..1dd952b 100644 --- a/controller/abstract.go +++ b/controller/abstract.go @@ -3,8 +3,12 @@ package controller import ( "electricity_bill_calc/exceptions" "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "electricity_bill_calc/response" + "net/http" "github.com/gofiber/fiber/v2" + "go.uber.org/zap" ) func _retreiveSession(c *fiber.Ctx) (*model.Session, error) { @@ -18,3 +22,21 @@ func _retreiveSession(c *fiber.Ctx) (*model.Session, error) { } return userSession, nil } + +// 检查当前用户是否拥有指定园区,在判断完成之后直接产生响应 +func checkParkBelongs(logger *zap.Logger, parkId string, session *model.Session, result *response.Result) (bool, error) { + if session == nil { + logger.Error("用户会话无效。") + return false, result.Unauthorized("用户会话无效。") + } + ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid) + switch { + case err != nil: + logger.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + return false, result.Error(http.StatusInternalServerError, err.Error()) + case err == nil && !ok: + logger.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + return false, result.Forbidden("您无权访问该园区。") + } + return true, nil +} diff --git a/controller/park.go b/controller/park.go index 2733f41..3bb0efe 100644 --- a/controller/park.go +++ b/controller/park.go @@ -2,7 +2,6 @@ package controller import ( "electricity_bill_calc/logger" - "electricity_bill_calc/model" "electricity_bill_calc/repository" "electricity_bill_calc/response" "electricity_bill_calc/security" @@ -30,24 +29,6 @@ func InitializeParkHandlers(router *fiber.App) { router.Put("/park/:pid/building/:bid/enabled", security.EnterpriseAuthorize, modifyParkBuildingEnabling) } -// 检查当前用户是否拥有指定园区,在判断完成之后直接产生响应 -func checkParkBelongs(logger *zap.Logger, parkId string, session *model.Session, result *response.Result) (bool, error) { - if session == nil { - logger.Error("用户会话无效。") - return false, result.Unauthorized("用户会话无效。") - } - ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid) - switch { - case err != nil: - logger.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) - return false, result.Error(http.StatusInternalServerError, err.Error()) - case err == nil && !ok: - logger.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) - return false, result.Forbidden("您无权访问该园区。") - } - return true, nil -} - // 列出隶属于当前用户的全部园区 func listParksBelongsToCurrentUser(c *fiber.Ctx) error { result := response.NewResult(c) @@ -131,14 +112,8 @@ func modifySpecificPark(c *fiber.Ctx) error { parkLog.Error("修改指定园区的信息,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + return err } parkForm := new(vo.ParkInformationForm) if err := c.BodyParser(parkForm); err != nil { @@ -150,7 +125,7 @@ func modifySpecificPark(c *fiber.Ctx) error { parkLog.Error("无法将园区表单数据转换为园区对象。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) return result.NotAccept(err.Error()) } - ok, err = repository.ParkRepository.UpdatePark(parkId, park) + ok, err := repository.ParkRepository.UpdatePark(parkId, park) switch { case err == nil && !ok: parkLog.Error("无法更新园区信息。", zap.String("park id", parkId), zap.String("user id", session.Uid)) @@ -171,21 +146,15 @@ func modifyParkEnabling(c *fiber.Ctx) error { parkLog.Error("修改指定园区的可用性,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + return err } stateForm := new(vo.StateForm) if err := c.BodyParser(stateForm); err != nil { parkLog.Error("无法解析园区表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) return result.NotAccept(err.Error()) } - ok, err = repository.ParkRepository.EnablingPark(parkId, stateForm.Enabled) + ok, err := repository.ParkRepository.EnablingPark(parkId, stateForm.Enabled) switch { case err == nil && !ok: parkLog.Error("无法更新园区可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid)) @@ -206,16 +175,10 @@ func deleteSpecificPark(c *fiber.Ctx) error { parkLog.Error("删除指定的园区,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + return err } - ok, err = repository.ParkRepository.DeletePark(parkId) + ok, err := repository.ParkRepository.DeletePark(parkId) switch { case err == nil && !ok: parkLog.Error("无法删除园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) @@ -262,21 +225,15 @@ func createBuildingInPark(c *fiber.Ctx) error { parkLog.Error("在指定园区中创建一个新的建筑,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + return err } buildingForm := new(vo.ParkBuildingInformationForm) if err := c.BodyParser(buildingForm); err != nil { parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) return result.NotAccept(err.Error()) } - ok, err = repository.ParkRepository.CreateParkBuilding(parkId, buildingForm.Name, &buildingForm.Floors) + ok, err := repository.ParkRepository.CreateParkBuilding(parkId, buildingForm.Name, &buildingForm.Floors) switch { case err == nil && !ok: parkLog.Error("无法创建新的建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid)) @@ -298,21 +255,15 @@ func modifySpecificBuildingInPark(c *fiber.Ctx) error { parkLog.Error("修改指定园区中的指定建筑的信息,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + return err } buildingForm := new(vo.ParkBuildingInformationForm) if err := c.BodyParser(buildingForm); err != nil { parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) return result.NotAccept(err.Error()) } - ok, err = repository.ParkRepository.ModifyParkBuilding(buildingId, parkId, buildingForm.Name, &buildingForm.Floors) + ok, err := repository.ParkRepository.ModifyParkBuilding(buildingId, parkId, buildingForm.Name, &buildingForm.Floors) switch { case err == nil && !ok: parkLog.Error("无法更新建筑信息。", zap.String("park id", parkId), zap.String("user id", session.Uid)) @@ -334,21 +285,15 @@ func modifyParkBuildingEnabling(c *fiber.Ctx) error { parkLog.Error("修改指定园区中指定建筑的可用性,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + return err } stateForm := new(vo.StateForm) if err := c.BodyParser(stateForm); err != nil { parkLog.Error("无法解析建筑表单数据。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) return result.NotAccept(err.Error()) } - ok, err = repository.ParkRepository.EnablingParkBuilding(buildingId, parkId, stateForm.Enabled) + ok, err := repository.ParkRepository.EnablingParkBuilding(buildingId, parkId, stateForm.Enabled) switch { case err == nil && !ok: parkLog.Error("无法更新建筑可用性。", zap.String("park id", parkId), zap.String("user id", session.Uid)) @@ -370,16 +315,10 @@ func deletedParkBuilding(c *fiber.Ctx) error { parkLog.Error("删除指定园区中的指定建筑,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + return err } - ok, err = repository.ParkRepository.DeleteParkBuilding(buildingId, parkId) + ok, err := repository.ParkRepository.DeleteParkBuilding(buildingId, parkId) switch { case err == nil && !ok: parkLog.Error("无法删除建筑。", zap.String("park id", parkId), zap.String("user id", session.Uid)) From 7531a6a5a141c20fa76343a4dbb1efb2e20fc4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 15 Jun 2023 05:58:00 +0800 Subject: [PATCH 071/141] =?UTF-8?q?refactor(meter):=E5=90=8C=E6=84=8F?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E5=9B=AD=E5=8C=BA=E5=BD=92=E5=B1=9E=E7=9A=84?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E5=A4=84=E7=90=86=E5=8F=A5=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 130 +++++++++++--------------------------------- 1 file changed, 32 insertions(+), 98 deletions(-) diff --git a/controller/meter.go b/controller/meter.go index 28f0e57..e49d39e 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -53,7 +53,7 @@ func searchMetersWithinPark(c *fiber.Ctx) error { meterLog.Error("无法查询指定园区下的表计信息,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { return err } keyword := c.Query("keyword") @@ -81,7 +81,7 @@ func retrieveSpecificMeterDetail(c *fiber.Ctx) error { meterLog.Error("无法查询指定园区中指定表计的详细信息,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { return err } meter, err := repository.MeterRepository.FetchMeterDetail(parkId, meterId) @@ -106,7 +106,7 @@ func createNewMeterManually(c *fiber.Ctx) error { meterLog.Error("无法手动添加一条0.4kV表计记录,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { return err } var creationForm vo.MeterCreationForm @@ -132,7 +132,7 @@ func updateMeterManually(c *fiber.Ctx) error { meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { return err } var updateForm vo.MeterModificationForm @@ -157,7 +157,7 @@ func downloadMeterArchiveTemplate(c *fiber.Ctx) error { meterLog.Error("无法下载指定的园区表计登记模板,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { return err } parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId) @@ -200,14 +200,8 @@ func uploadMeterArchive(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } uploadFile, err := c.FormFile("data") if err != nil { @@ -233,7 +227,7 @@ func replaceMeter(c *fiber.Ctx) error { meterLog.Error("无法更换系统中的表计,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { return err } var replacementForm vo.MeterReplacingForm @@ -253,14 +247,8 @@ func listAssociatedMeters(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } meterId := c.Params("code") meterLog.Info("列出指定公摊表计下的所有关联表计", zap.String("park id", parkId), zap.String("meter id", meterId)) @@ -281,14 +269,8 @@ func bindAssociatedMeters(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } meterId := c.Params("code") meterLog.Info("向指定表计绑定关联表计", zap.String("park id", parkId), zap.String("meter id", meterId)) @@ -297,7 +279,7 @@ func bindAssociatedMeters(c *fiber.Ctx) error { meterLog.Error("无法向指定表计绑定关联表计,无法解析关联表计列表", zap.Error(err)) return result.NotAccept(err.Error()) } - ok, err = service.MeterService.BindMeter(parkId, meterId, meters) + ok, err := service.MeterService.BindMeter(parkId, meterId, meters) if err != nil { meterLog.Error("无法向指定表计绑定关联表计,无法绑定关联表计", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) @@ -318,14 +300,8 @@ func unbindAssociatedMeters(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } masterMeter := c.Params("master") slaveMeter := c.Params("slave") @@ -333,7 +309,7 @@ func unbindAssociatedMeters(c *fiber.Ctx) error { meterLog.Warn("无法解除指定园区下两个表计之间的关联关系,表计编号为空。") return result.NotAccept("存在未给定要操作的表计编号。") } - ok, err = service.MeterService.UnbindMeter(parkId, masterMeter, []string{slaveMeter}) + ok, err := service.MeterService.UnbindMeter(parkId, masterMeter, []string{slaveMeter}) if err != nil { meterLog.Error("无法解除指定园区下两个表计之间的关联关系,无法解除关联关系", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) @@ -354,14 +330,8 @@ func listPooledMeters(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } page := c.QueryInt("page", 1) keyword := c.Query("keyword") @@ -386,14 +356,8 @@ func listUnboundMeters(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } keyword := c.Query("keyword") limit := uint(c.QueryInt("limit", 6)) @@ -420,14 +384,8 @@ func listUnboundTenementMeters(c *fiber.Ctx) error { 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } keyword := c.Query("keyword") limit := uint(c.QueryInt("limit", 6)) @@ -450,14 +408,8 @@ func queryMeterReadings(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } keyword := tools.EmptyToNil(c.Query("keyword")) page := c.QueryInt("page", 1) @@ -504,14 +456,8 @@ func recordMeterReading(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } meterCode := c.Params("code") var readingForm vo.MeterReadingForm @@ -540,14 +486,8 @@ func updateMeterReading(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } meterCode := c.Params("code") readingAtMicro, err := c.ParamsInt("reading") @@ -561,7 +501,7 @@ func updateMeterReading(c *fiber.Ctx) error { meterLog.Error("更新一条新的表计抄表记录,无法解析表计抄表表单", zap.Error(err)) return result.NotAccept(fmt.Sprintf("无法解析表计抄表表单,%s", err.Error())) } - ok, err = repository.MeterRepository.UpdateMeterReading(parkId, meterCode, readingAt, &readingForm) + ok, err := repository.MeterRepository.UpdateMeterReading(parkId, meterCode, readingAt, &readingForm) if err != nil { meterLog.Error("更新一条新的表计抄表记录,无法更新表计抄表记录", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) @@ -583,7 +523,7 @@ func downloadMeterReadingsTemplate(c *fiber.Ctx) error { meterLog.Error("无法下载指定的园区表计抄表模板,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok { + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { return err } parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId) @@ -625,14 +565,8 @@ func uploadMeterReadings(c *fiber.Ctx) error { meterLog.Error("无法从Excel文件中导入抄表档案,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - 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("您无权访问该园区。") + if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + return err } uploadFile, err := c.FormFile("data") if err != nil { From 577ac9d1dc4418db2e32dd5c691c128adb64ce0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 15 Jun 2023 09:31:00 +0800 Subject: [PATCH 072/141] =?UTF-8?q?refactor(park):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E5=9B=AD=E5=8C=BA=E5=BD=92=E5=B1=9E=E7=9A=84?= =?UTF-8?q?=E9=80=BB=E5=92=8C=E4=BD=BF=E7=94=A8=E6=96=B9=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/abstract.go | 17 +++--- controller/meter.go | 118 +++++++---------------------------------- controller/park.go | 14 ++--- 3 files changed, 37 insertions(+), 112 deletions(-) diff --git a/controller/abstract.go b/controller/abstract.go index 1dd952b..3cb2fd0 100644 --- a/controller/abstract.go +++ b/controller/abstract.go @@ -24,18 +24,23 @@ func _retreiveSession(c *fiber.Ctx) (*model.Session, error) { } // 检查当前用户是否拥有指定园区,在判断完成之后直接产生响应 -func checkParkBelongs(logger *zap.Logger, parkId string, session *model.Session, result *response.Result) (bool, error) { +func checkParkBelongs(parkId string, logger *zap.Logger, c *fiber.Ctx, result *response.Result) (bool, error) { + session := c.Locals("session") if session == nil { - logger.Error("用户会话无效。") - return false, result.Unauthorized("用户会话无效。") + logger.Error("用户会话不存在。") + return false, result.Unauthorized("用户会话不存在。") } - ok, err := repository.ParkRepository.IsParkBelongs(parkId, session.Uid) + userSession, ok := session.(*model.Session) + if !ok { + return false, result.Unauthorized("用户会话格式不正确,需要重新登录") + } + ok, err := repository.ParkRepository.IsParkBelongs(parkId, userSession.Uid) switch { case err != nil: - logger.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", session.Uid), zap.Error(err)) + logger.Error("无法判断园区是否隶属于当前用户。", zap.String("park id", parkId), zap.String("user id", userSession.Uid), zap.Error(err)) return false, result.Error(http.StatusInternalServerError, err.Error()) case err == nil && !ok: - logger.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", session.Uid)) + logger.Error("用户试图访问不属于自己的园区。", zap.String("park id", parkId), zap.String("user id", userSession.Uid)) return false, result.Forbidden("您无权访问该园区。") } return true, nil diff --git a/controller/meter.go b/controller/meter.go index e49d39e..a25cc18 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -48,12 +48,7 @@ func searchMetersWithinPark(c *fiber.Ctx) error { parkId := c.Params("parkId") 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 pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } keyword := c.Query("keyword") @@ -76,12 +71,7 @@ func retrieveSpecificMeterDetail(c *fiber.Ctx) error { meterId := c.Params("code") meterLog.Info("查询指定园区中指定表计的详细信息", zap.String("park id", parkId), zap.String("meter id", meterId)) result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - meterLog.Error("无法查询指定园区中指定表计的详细信息,无法获取当前用户会话", zap.Error(err)) - return result.Unauthorized(err.Error()) - } - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meter, err := repository.MeterRepository.FetchMeterDetail(parkId, meterId) @@ -101,12 +91,7 @@ func createNewMeterManually(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("手动添加一条0.4kV表计记录", zap.String("park id", parkId)) result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - meterLog.Error("无法手动添加一条0.4kV表计记录,无法获取当前用户会话", zap.Error(err)) - return result.Unauthorized(err.Error()) - } - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } var creationForm vo.MeterCreationForm @@ -127,12 +112,7 @@ func updateMeterManually(c *fiber.Ctx) error { meterId := c.Params("code") meterLog.Info("手动更新一条新的0.4kV表计记录", zap.String("park id", parkId), zap.String("meter id", meterId)) result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法获取当前用户会话", zap.Error(err)) - return result.Unauthorized(err.Error()) - } - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } var updateForm vo.MeterModificationForm @@ -152,12 +132,7 @@ func downloadMeterArchiveTemplate(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 pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId) @@ -194,13 +169,8 @@ func downloadMeterArchiveTemplate(c *fiber.Ctx) error { // 从Excel文件中导入表计档案 func uploadMeterArchive(c *fiber.Ctx) error { 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } uploadFile, err := c.FormFile("data") @@ -222,12 +192,7 @@ func replaceMeter(c *fiber.Ctx) error { meterId := c.Params("code") meterLog.Info("更换系统中的表计", zap.String("park id", parkId), zap.String("meter id", meterId)) result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - meterLog.Error("无法更换系统中的表计,无法获取当前用户会话", zap.Error(err)) - return result.Unauthorized(err.Error()) - } - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } var replacementForm vo.MeterReplacingForm @@ -241,13 +206,8 @@ func replaceMeter(c *fiber.Ctx) error { // 列出指定公摊表计下的所有关联表计 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterId := c.Params("code") @@ -263,13 +223,8 @@ func listAssociatedMeters(c *fiber.Ctx) error { // 向指定表计绑定关联表计 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterId := c.Params("code") @@ -294,13 +249,8 @@ func bindAssociatedMeters(c *fiber.Ctx) error { // 解除指定园区下两个表计之间的关联关系 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } masterMeter := c.Params("master") @@ -324,13 +274,8 @@ func unbindAssociatedMeters(c *fiber.Ctx) error { // 分页列出园区中的公摊表计 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } page := c.QueryInt("page", 1) @@ -356,7 +301,7 @@ func listUnboundMeters(c *fiber.Ctx) error { return result.Unauthorized(err.Error()) } parkId := c.Params("pid") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } keyword := c.Query("keyword") @@ -384,7 +329,7 @@ func listUnboundTenementMeters(c *fiber.Ctx) error { meterLog.Error("无法列出指定园区中尚未绑定商户的表计,未指定要访问的园区ID") return result.NotAccept("未指定要访问的园区。") } - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } keyword := c.Query("keyword") @@ -402,13 +347,8 @@ func listUnboundTenementMeters(c *fiber.Ctx) error { // 查询指定园区中的表计读数 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } keyword := tools.EmptyToNil(c.Query("keyword")) @@ -450,13 +390,8 @@ func queryMeterReadings(c *fiber.Ctx) error { // 记录一条新的表计抄表记录 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterCode := c.Params("code") @@ -469,7 +404,7 @@ func recordMeterReading(c *fiber.Ctx) error { meterLog.Warn("记录一条新的表计抄表记录,表计读数不能正常配平,尖、峰、谷电量和超过总电量。") return result.NotAccept("表计读数不能正常配平,尖、峰、谷电量和超过总电量。") } - err = service.MeterService.RecordReading(parkId, meterCode, &readingForm) + err := service.MeterService.RecordReading(parkId, meterCode, &readingForm) if err != nil { meterLog.Error("记录一条新的表计抄表记录,无法记录表计抄表记录", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) @@ -480,13 +415,8 @@ func recordMeterReading(c *fiber.Ctx) error { // 更新指定园区中指定表计的抄表记录 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") - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } meterCode := c.Params("code") @@ -518,12 +448,7 @@ 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 pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId) @@ -560,12 +485,7 @@ func uploadMeterReadings(c *fiber.Ctx) error { parkId := c.Params("pid") meterLog.Info("从Excel文件中导入抄表档案", zap.String("park id", parkId)) result := response.NewResult(c) - session, err := _retreiveSession(c) - if err != nil { - meterLog.Error("无法从Excel文件中导入抄表档案,无法获取当前用户会话", zap.Error(err)) - return result.Unauthorized(err.Error()) - } - if pass, err := checkParkBelongs(meterLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } uploadFile, err := c.FormFile("data") diff --git a/controller/park.go b/controller/park.go index 3bb0efe..861ba19 100644 --- a/controller/park.go +++ b/controller/park.go @@ -112,7 +112,7 @@ func modifySpecificPark(c *fiber.Ctx) error { parkLog.Error("修改指定园区的信息,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { return err } parkForm := new(vo.ParkInformationForm) @@ -146,7 +146,7 @@ func modifyParkEnabling(c *fiber.Ctx) error { parkLog.Error("修改指定园区的可用性,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { return err } stateForm := new(vo.StateForm) @@ -175,7 +175,7 @@ func deleteSpecificPark(c *fiber.Ctx) error { parkLog.Error("删除指定的园区,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { return err } ok, err := repository.ParkRepository.DeletePark(parkId) @@ -225,7 +225,7 @@ func createBuildingInPark(c *fiber.Ctx) error { parkLog.Error("在指定园区中创建一个新的建筑,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { return err } buildingForm := new(vo.ParkBuildingInformationForm) @@ -255,7 +255,7 @@ func modifySpecificBuildingInPark(c *fiber.Ctx) error { parkLog.Error("修改指定园区中的指定建筑的信息,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { return err } buildingForm := new(vo.ParkBuildingInformationForm) @@ -285,7 +285,7 @@ func modifyParkBuildingEnabling(c *fiber.Ctx) error { parkLog.Error("修改指定园区中指定建筑的可用性,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { return err } stateForm := new(vo.StateForm) @@ -315,7 +315,7 @@ func deletedParkBuilding(c *fiber.Ctx) error { parkLog.Error("删除指定园区中的指定建筑,无法获取当前用户的会话。") return result.Unauthorized(err.Error()) } - if pass, err := checkParkBelongs(parkLog, parkId, session, &result); !pass { + if pass, err := checkParkBelongs(parkId, parkLog, c, &result); !pass { return err } ok, err := repository.ParkRepository.DeleteParkBuilding(buildingId, parkId) From 2d6bff58281c16c2ad84aa16e02e35a5d44fc57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 15 Jun 2023 13:50:50 +0800 Subject: [PATCH 073/141] =?UTF-8?q?enhance(tenement):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=95=86=E6=88=B7=E7=9B=B8=E5=85=B3=E5=80=9F?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E5=BE=85=E6=B5=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/tenement.go | 286 +++++++++++++++++++++++++++++++++++++++++ service/tenement.go | 5 + vo/tenement.go | 45 +++++++ 3 files changed, 336 insertions(+) create mode 100644 controller/tenement.go 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"` +} From 2c303cfba72f397116d8c3c05fc01f3b08d80eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 16 Jun 2023 08:25:01 +0800 Subject: [PATCH 074/141] =?UTF-8?q?feat(types):=E5=A2=9E=E5=8A=A0=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E6=97=B6=E9=97=B4=E7=9A=84=E8=8C=83=E5=9B=B4=E5=8C=BA?= =?UTF-8?q?=E9=97=B4=E7=B1=BB=E5=9E=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/date.go | 9 ++++ types/daterange.go | 80 ++++++++++++++++++++++++++++ types/datetime.go | 21 ++++++++ types/datetimerange.go | 80 ++++++++++++++++++++++++++++ types/range.go | 115 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+) create mode 100644 types/daterange.go create mode 100644 types/datetimerange.go create mode 100644 types/range.go diff --git a/types/date.go b/types/date.go index 4897776..188b089 100644 --- a/types/date.go +++ b/types/date.go @@ -153,6 +153,15 @@ func (d Date) IsEmpty() bool { return d.Time.IsZero() } +func (d *Date) Parse(s string) error { + t, err := ParseDate(s) + if err != nil { + return err + } + d.Time = t.Time + return nil +} + func (d Date) ToString() string { return d.Time.Format("2006-01-02") } diff --git a/types/daterange.go b/types/daterange.go new file mode 100644 index 0000000..17308b1 --- /dev/null +++ b/types/daterange.go @@ -0,0 +1,80 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "electricity_bill_calc/tools" + "encoding/json" + "errors" + + "github.com/jackc/pgx/v5/pgtype" +) + +type DateRange struct { + pgtype.Range[Date] +} + +func NewDateRange(lower *Date, upper *Date) DateRange { + return DateRange{ + Range: pgtype.Range[Date]{ + LowerType: tools.Cond(lower != nil, pgtype.Inclusive, pgtype.Unbounded), + Lower: tools.DefaultTo(lower, MinDate()), + UpperType: tools.Cond(upper != nil, pgtype.Inclusive, pgtype.Unbounded), + Upper: tools.DefaultTo(upper, MaxDate()), + Valid: true, + }, + } +} + +var _ driver.Valuer = (*DateRange)(nil) + +func (dr DateRange) Value() (driver.Value, error) { + return assembleRange(dr.Range), nil +} + +var _ sql.Scanner = (*DateRange)(nil) + +func (dr *DateRange) Scan(src interface{}) (err error) { + switch src := src.(type) { + case pgtype.Range[Date]: + dr.Range = src + case string: + r, err := destructureToRange[Date](src) + if err != nil { + return err + } + dr.Range = r + case []byte: + r, err := destructureToRange[Date](string(src)) + if err != nil { + return err + } + dr.Range = r + case nil: + dr = nil + default: + return errors.New("该数据类型不支持解析到日期范围。") + } + return +} + +var _ json.Marshaler = (*DateRange)(nil) + +func (dr DateRange) MarshalJSON() ([]byte, error) { + return json.Marshal(assembleRange(dr.Range)) +} + +var _ json.Unmarshaler = (*DateRange)(nil) + +func (dr *DateRange) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return errors.New("不能解析指定的日期范围值。") + } + r, err := destructureToRange[Date](str) + if err != nil { + return err + } + dr.Range = r + return nil +} diff --git a/types/datetime.go b/types/datetime.go index 56e4025..a1c36cc 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -34,6 +34,18 @@ func NewEmptyDateTime() DateTime { } } +func MinDateTime() DateTime { + return DateTime{ + Time: time.Date(1, 1, 1, 0, 0, 0, 0, loc), + } +} + +func MaxDateTime() DateTime { + return DateTime{ + Time: time.Date(9999, 12, 31, 23, 59, 59, 999999, loc), + } +} + func Timestamp() int64 { startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() return Now().Unix() - startline @@ -159,6 +171,15 @@ func (dt DateTime) IsEmpty() bool { return dt.Time.IsZero() } +func (dt *DateTime) Parse(s string) error { + t, err := ParseDateTime(s) + if err != nil { + return err + } + dt.Time = t.Time + return nil +} + func (dt DateTime) ToString() string { return dt.Time.Format("2006-01-02 15:04:05") } diff --git a/types/datetimerange.go b/types/datetimerange.go new file mode 100644 index 0000000..46e9562 --- /dev/null +++ b/types/datetimerange.go @@ -0,0 +1,80 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "electricity_bill_calc/tools" + "encoding/json" + "errors" + + "github.com/jackc/pgx/v5/pgtype" +) + +type DateTimeRange struct { + pgtype.Range[DateTime] +} + +func NewDateTimeRange(lower *DateTime, upper *DateTime) DateTimeRange { + return DateTimeRange{ + Range: pgtype.Range[DateTime]{ + LowerType: tools.Cond(lower != nil, pgtype.Inclusive, pgtype.Unbounded), + Lower: tools.DefaultTo(lower, MinDateTime()), + UpperType: tools.Cond(upper != nil, pgtype.Inclusive, pgtype.Unbounded), + Upper: tools.DefaultTo(upper, MaxDateTime()), + Valid: true, + }, + } +} + +var _ driver.Value = (*DateTimeRange)(nil) + +func (dr DateTimeRange) Value() (driver.Value, error) { + return assembleRange(dr.Range), nil +} + +var _ sql.Scanner = (*DateTimeRange)(nil) + +func (dr *DateTimeRange) Scan(src interface{}) (err error) { + switch src := src.(type) { + case pgtype.Range[DateTime]: + dr.Range = src + case string: + r, err := destructureToRange[DateTime](src) + if err != nil { + return err + } + dr.Range = r + case []byte: + r, err := destructureToRange[DateTime](string(src)) + if err != nil { + return err + } + dr.Range = r + case nil: + dr = nil + default: + return errors.New("该数据类型不支持解析到日期范围。") + } + return +} + +var _ json.Marshaler = (*DateTimeRange)(nil) + +func (dr DateTimeRange) MarshalJSON() ([]byte, error) { + return json.Marshal(assembleRange(dr.Range)) +} + +var _ json.Unmarshaler = (*DateTimeRange)(nil) + +func (dr *DateTimeRange) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return errors.New("不能解析指定的日期范围值。") + } + r, err := destructureToRange[DateTime](str) + if err != nil { + return err + } + dr.Range = r + return nil +} diff --git a/types/range.go b/types/range.go new file mode 100644 index 0000000..178b59f --- /dev/null +++ b/types/range.go @@ -0,0 +1,115 @@ +package types + +import ( + "errors" + "strings" + + "github.com/jackc/pgx/v5/pgtype" +) + +type Parse interface { + Parse(string) error +} + +type ToString interface { + ToString() string +} + +// 将一个字符串拆解解析为一个 Postgresql 范围类型的值。 +func destructureToRange[T any, PT interface { + Parse + *T +}](s string) (pgtype.Range[T], error) { + var r pgtype.Range[T] + r.Valid = false + if len(s) == 0 { + r.LowerType = pgtype.Empty + r.UpperType = pgtype.Empty + return r, nil + } + rangeUnit := strings.Split(s, ",") + if len(rangeUnit) != 2 { + return r, errors.New("无法解析给定的范围值,格式不正确。") + } + if unit, found := strings.CutPrefix(rangeUnit[0], "["); found { + var t PT + if len(unit) > 0 { + r.LowerType = pgtype.Inclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最低范围值。") + } + } else { + r.LowerType = pgtype.Unbounded + } + r.Lower = *t + } + if unit, found := strings.CutPrefix(rangeUnit[0], "("); found { + var t PT + if len(unit) > 0 { + r.LowerType = pgtype.Exclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最低范围值。") + } + } else { + r.LowerType = pgtype.Unbounded + } + r.Lower = *t + } + + if unit, found := strings.CutSuffix(rangeUnit[1], "]"); found { + var t PT + if len(unit) > 0 { + r.UpperType = pgtype.Inclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最高范围值。") + } + } else { + r.UpperType = pgtype.Unbounded + } + r.Upper = *t + } + if unit, found := strings.CutSuffix(rangeUnit[1], ")"); found { + var t PT + if len(unit) > 0 { + r.UpperType = pgtype.Exclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最高范围值。") + } + } else { + r.UpperType = pgtype.Unbounded + } + r.Upper = *t + } + r.Valid = true + return r, nil +} + +// 将一个范围类型的值转换为一个字符串、 +func assembleRange[T ToString](r pgtype.Range[T]) string { + var sb strings.Builder + if r.LowerType == pgtype.Empty || r.UpperType == pgtype.Empty { + return "empty" + } + if r.LowerType == pgtype.Inclusive { + sb.WriteString("[") + } else { + sb.WriteString("(") + } + if r.LowerType != pgtype.Unbounded { + sb.WriteString(r.Lower.ToString()) + } + sb.WriteString(",") + if r.UpperType != pgtype.Unbounded { + sb.WriteString(r.Upper.ToString()) + } + if r.UpperType == pgtype.Inclusive { + sb.WriteString("]") + } else { + sb.WriteString(")") + } + return sb.String() +} From b244fd58237e37c0a38c024a8604fb445a11e8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 16 Jun 2023 08:36:11 +0800 Subject: [PATCH 075/141] =?UTF-8?q?enhance(types):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=8B=A5=E5=B9=B2=E5=B8=B8=E7=94=A8=E6=96=B9=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/daterange.go | 32 ++++++++++++++++++++++++++++++++ types/datetimerange.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/types/daterange.go b/types/daterange.go index 17308b1..5ac1089 100644 --- a/types/daterange.go +++ b/types/daterange.go @@ -14,6 +14,18 @@ type DateRange struct { pgtype.Range[Date] } +func NewEmptyDateRange() DateRange { + return DateRange{ + Range: pgtype.Range[Date]{ + Lower: MinDate(), + LowerType: pgtype.Unbounded, + Upper: MaxDate(), + UpperType: pgtype.Unbounded, + Valid: true, + }, + } +} + func NewDateRange(lower *Date, upper *Date) DateRange { return DateRange{ Range: pgtype.Range[Date]{ @@ -78,3 +90,23 @@ func (dr *DateRange) UnmarshalJSON(data []byte) error { dr.Range = r return nil } + +func (dr *DateRange) SetLower(lower Date, bound pgtype.BoundType) { + dr.Range.Lower = lower + dr.Range.LowerType = bound +} + +func (dr *DateRange) SetLowerUnbounded() { + dr.Range.Lower = MinDate() + dr.Range.LowerType = pgtype.Unbounded +} + +func (dr *DateRange) SetUpper(upper Date, bound pgtype.BoundType) { + dr.Range.Upper = upper + dr.Range.UpperType = bound +} + +func (dr *DateRange) SetUpperUnbounded() { + dr.Range.Upper = MaxDate() + dr.Range.UpperType = pgtype.Unbounded +} diff --git a/types/datetimerange.go b/types/datetimerange.go index 46e9562..419a035 100644 --- a/types/datetimerange.go +++ b/types/datetimerange.go @@ -14,6 +14,18 @@ type DateTimeRange struct { pgtype.Range[DateTime] } +func NewEmptyDateTimeRange() DateTimeRange { + return DateTimeRange{ + Range: pgtype.Range[DateTime]{ + Lower: MinDateTime(), + LowerType: pgtype.Unbounded, + Upper: MaxDateTime(), + UpperType: pgtype.Unbounded, + Valid: true, + }, + } +} + func NewDateTimeRange(lower *DateTime, upper *DateTime) DateTimeRange { return DateTimeRange{ Range: pgtype.Range[DateTime]{ @@ -78,3 +90,23 @@ func (dr *DateTimeRange) UnmarshalJSON(data []byte) error { dr.Range = r return nil } + +func (dr *DateTimeRange) SetLower(lower DateTime, bound pgtype.BoundType) { + dr.Range.Lower = lower + dr.Range.LowerType = bound +} + +func (dr *DateTimeRange) SetLowerUnbounded() { + dr.Range.Lower = MinDateTime() + dr.Range.LowerType = pgtype.Unbounded +} + +func (dr *DateTimeRange) SetUpper(upper DateTime, bound pgtype.BoundType) { + dr.Range.Upper = upper + dr.Range.UpperType = bound +} + +func (dr *DateTimeRange) SetUpperUnbounded() { + dr.Range.Upper = MaxDateTime() + dr.Range.UpperType = pgtype.Unbounded +} From fb388c53c7b6b2923c2f1189562757aa4c9ad36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 16 Jun 2023 08:40:15 +0800 Subject: [PATCH 076/141] =?UTF-8?q?enhance(types):=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E5=8C=BA=E9=97=B4=E8=8C=83=E5=9B=B4=E5=A2=9E=E5=8A=A0=E5=B8=B8?= =?UTF-8?q?=E7=94=A8=E6=96=B9=E6=B3=95=E7=9A=84=E6=98=93=E7=94=A8=E6=80=A7?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/daterange.go | 10 ++++++---- types/datetimerange.go | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/types/daterange.go b/types/daterange.go index 5ac1089..ef46d3f 100644 --- a/types/daterange.go +++ b/types/daterange.go @@ -91,9 +91,10 @@ func (dr *DateRange) UnmarshalJSON(data []byte) error { return nil } -func (dr *DateRange) SetLower(lower Date, bound pgtype.BoundType) { +func (dr *DateRange) SetLower(lower Date, bound ...pgtype.BoundType) { + bound = append(bound, pgtype.Inclusive) dr.Range.Lower = lower - dr.Range.LowerType = bound + dr.Range.LowerType = bound[0] } func (dr *DateRange) SetLowerUnbounded() { @@ -101,9 +102,10 @@ func (dr *DateRange) SetLowerUnbounded() { dr.Range.LowerType = pgtype.Unbounded } -func (dr *DateRange) SetUpper(upper Date, bound pgtype.BoundType) { +func (dr *DateRange) SetUpper(upper Date, bound ...pgtype.BoundType) { + bound = append(bound, pgtype.Inclusive) dr.Range.Upper = upper - dr.Range.UpperType = bound + dr.Range.UpperType = bound[0] } func (dr *DateRange) SetUpperUnbounded() { diff --git a/types/datetimerange.go b/types/datetimerange.go index 419a035..5927d8d 100644 --- a/types/datetimerange.go +++ b/types/datetimerange.go @@ -91,9 +91,10 @@ func (dr *DateTimeRange) UnmarshalJSON(data []byte) error { return nil } -func (dr *DateTimeRange) SetLower(lower DateTime, bound pgtype.BoundType) { +func (dr *DateTimeRange) SetLower(lower DateTime, bound ...pgtype.BoundType) { + bound = append(bound, pgtype.Inclusive) dr.Range.Lower = lower - dr.Range.LowerType = bound + dr.Range.LowerType = bound[0] } func (dr *DateTimeRange) SetLowerUnbounded() { @@ -101,9 +102,10 @@ func (dr *DateTimeRange) SetLowerUnbounded() { dr.Range.LowerType = pgtype.Unbounded } -func (dr *DateTimeRange) SetUpper(upper DateTime, bound pgtype.BoundType) { +func (dr *DateTimeRange) SetUpper(upper DateTime, bound ...pgtype.BoundType) { + bound = append(bound, pgtype.Inclusive) dr.Range.Upper = upper - dr.Range.UpperType = bound + dr.Range.UpperType = bound[0] } func (dr *DateTimeRange) SetUpperUnbounded() { From 24f2c26d86db2b98fa2726314be8bea3eb564896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 16 Jun 2023 10:43:06 +0800 Subject: [PATCH 077/141] =?UTF-8?q?enhance(cache):=E6=94=BE=E5=BC=80?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=AE=A1=E7=90=86=E4=B8=AD=E5=AF=B9=E4=BA=8E?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E7=BC=93=E5=AD=98=E9=94=AE=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/count.go | 6 +++--- cache/entity.go | 8 ++++---- cache/exists.go | 6 +++--- cache/relation.go | 10 +++++----- cache/search.go | 14 +++++++------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cache/count.go b/cache/count.go index 5b8b65b..78844b9 100644 --- a/cache/count.go +++ b/cache/count.go @@ -10,7 +10,7 @@ type _CountRecord struct { Count int64 } -func assembleCountKey(entityName string, additional ...string) string { +func AssembleCountKey(entityName string, additional ...string) string { var keys = make([]string, 0) keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, additional...) @@ -24,7 +24,7 @@ func assembleCountKey(entityName string, additional ...string) string { // 向缓存中缓存模型名称明确的包含指定条件的实体记录数量 func CacheCount(relationNames []string, entityName string, count int64, conditions ...string) error { - countKey := assembleCountKey(entityName, conditions...) + countKey := AssembleCountKey(entityName, conditions...) cacheInstance := &_CountRecord{Count: count} err := Cache(countKey, cacheInstance, 5*time.Minute) for _, relationName := range relationNames { @@ -35,7 +35,7 @@ func CacheCount(relationNames []string, entityName string, count int64, conditio // 从缓存中获取模型名称明确的,包含指定条件的实体记录数量 func RetrieveCount(entityName string, condtions ...string) (int64, error) { - countKey := assembleCountKey(entityName, condtions...) + countKey := AssembleCountKey(entityName, condtions...) exist, err := Exists(countKey) if err != nil { return -1, err diff --git a/cache/entity.go b/cache/entity.go index db1ff52..203133b 100644 --- a/cache/entity.go +++ b/cache/entity.go @@ -6,7 +6,7 @@ import ( "time" ) -func assembleEntityKey(entityName, id string) string { +func AssembleEntityKey(entityName, id string) string { var keys = make([]string, 0) keys = append(keys, strings.ToUpper(entityName), id) var b strings.Builder @@ -19,7 +19,7 @@ func assembleEntityKey(entityName, id string) string { // 缓存模型名称明确的,使用ID进行检索的实体内容。 func CacheEntity[T any](instance T, relationNames []string, entityName, id string) error { - entityKey := assembleEntityKey(entityName, id) + entityKey := AssembleEntityKey(entityName, id) err := Cache(entityKey, &instance, 5*time.Minute) for _, relationName := range relationNames { CacheRelation(relationName, STORE_TYPE_KEY, entityKey) @@ -29,14 +29,14 @@ func CacheEntity[T any](instance T, relationNames []string, entityName, id strin // 从缓存中取出模型名称明确的,使用ID进行检索的实体内容。 func RetrieveEntity[T any](entityName, id string) (*T, error) { - entityKey := assembleEntityKey(entityName, id) + entityKey := AssembleEntityKey(entityName, id) instance, err := Retrieve[T](entityKey) return instance, err } // 精确的从缓存中删除指定模型名称、指定ID的实体内容。 func AbolishSpecificEntity(entityName, id string) (bool, error) { - entityKey := assembleEntityKey(entityName, id) + entityKey := AssembleEntityKey(entityName, id) return Delete(entityKey) } diff --git a/cache/exists.go b/cache/exists.go index cae7ad7..150c0c7 100644 --- a/cache/exists.go +++ b/cache/exists.go @@ -8,7 +8,7 @@ import ( "github.com/samber/lo" ) -func assembleExistsKey(entityName string, additional ...string) string { +func AssembleExistsKey(entityName string, additional ...string) string { var keys = make([]string, 0) keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, additional...) @@ -22,7 +22,7 @@ func assembleExistsKey(entityName string, additional ...string) string { // 缓存模型名称明确的、包含指定ID以及一些附加条件的记录 func CacheExists(relationNames []string, entityName string, conditions ...string) error { - existskey := assembleExistsKey(entityName, conditions...) + existskey := AssembleExistsKey(entityName, conditions...) err := Cache(existskey, lo.ToPtr(true), 5*time.Minute) for _, relationName := range relationNames { CacheRelation(relationName, STORE_TYPE_KEY, existskey) @@ -32,7 +32,7 @@ func CacheExists(relationNames []string, entityName string, conditions ...string // 从缓存中获取模型名称明确、包含指定ID以及一些附加条件的实体是否存在的标记,函数在返回false时不保证数据库中相关记录也不存在 func CheckExists(entityName string, condtions ...string) (bool, error) { - existsKey := assembleExistsKey(entityName, condtions...) + existsKey := AssembleExistsKey(entityName, condtions...) return Exists(existsKey) } diff --git a/cache/relation.go b/cache/relation.go index 03cf099..5b89af3 100644 --- a/cache/relation.go +++ b/cache/relation.go @@ -15,13 +15,13 @@ const ( STORE_TYPE_HASH = "HASH" ) -func assembleRelationKey(relationName string) string { +func AssembleRelationKey(relationName string) string { var keys = make([]string, 0) keys = append(keys, strings.ToUpper(relationName)) return CacheKey(TAG_RELATION, keys...) } -func assembleRelationIdentity(storeType, key string, field ...string) string { +func AssembleRelationIdentity(storeType, key string, field ...string) string { var identity = make([]string, 0) identity = append(identity, storeType, key) identity = append(identity, field...) @@ -30,8 +30,8 @@ func assembleRelationIdentity(storeType, key string, field ...string) string { // 向缓存中保存与指定关联名称相关联的键的名称以及键的类型和子字段的组成。 func CacheRelation(relationName, storeType, key string, field ...string) error { - relationKey := assembleRelationKey(relationName) - relationIdentity := assembleRelationIdentity(storeType, key, field...) + relationKey := AssembleRelationKey(relationName) + relationIdentity := AssembleRelationIdentity(storeType, key, field...) cmd := global.Rd.B().Sadd().Key(relationKey).Member(relationIdentity).Build() result := global.Rd.Do(global.Ctx, cmd) return result.Error() @@ -39,7 +39,7 @@ func CacheRelation(relationName, storeType, key string, field ...string) error { // 从缓存中清理指定的关联键 func AbolishRelation(relationName string) error { - relationKey := assembleRelationKey(relationName) + relationKey := AssembleRelationKey(relationName) cmd := global.Rd.B().Smembers().Key(relationKey).Build() relationItems, err := global.Rd.Do(global.Ctx, cmd).AsStrSlice() if err != nil { diff --git a/cache/search.go b/cache/search.go index fb4b8df..42ffcc9 100644 --- a/cache/search.go +++ b/cache/search.go @@ -11,7 +11,7 @@ import ( var log = logger.Named("Cache") -func assembleSearchKey(entityName string, additional ...string) string { +func AssembleSearchKey(entityName string, additional ...string) string { var keys = make([]string, 0) keys = append(keys, strings.ToUpper(entityName)) keys = append(keys, additional...) @@ -25,7 +25,7 @@ func assembleSearchKey(entityName string, additional ...string) string { // 缓存模型名称明确的,使用或者包含非ID检索条件的实体内容。 func CacheSearch[T any](instance T, relationNames []string, entityName string, conditions ...string) error { - searchKey := assembleSearchKey(entityName, conditions...) + searchKey := AssembleSearchKey(entityName, conditions...) err := Cache(searchKey, &instance, 5*time.Minute) for _, relationName := range relationNames { CacheRelation(relationName, STORE_TYPE_KEY, searchKey) @@ -35,7 +35,7 @@ func CacheSearch[T any](instance T, relationNames []string, entityName string, c // 从缓存中取得模型名称明确的,使用或者包含非ID检索条件的实体内容。 func RetrieveSearch[T any](entityName string, conditions ...string) (*T, error) { - searchKey := assembleSearchKey(entityName, conditions...) + searchKey := AssembleSearchKey(entityName, conditions...) instance, err := Retrieve[T](searchKey) return instance, err } @@ -48,8 +48,8 @@ func AbolishSearch(entityName string) error { // 向缓存中保存指定模型名称的分页检索结果,会同时采用`CacheCount`中的方法保存检索结果的总数量。 func CachePagedSearch[T any](instance T, total int64, relationNames []string, entityName string, conditions ...string) error { - searchKey := assembleSearchKey(entityName, conditions...) - countKey := assembleCountKey(entityName, conditions...) + searchKey := AssembleSearchKey(entityName, conditions...) + countKey := AssembleCountKey(entityName, conditions...) err := Cache(searchKey, &instance, 5*time.Minute) if err != nil { return err @@ -65,8 +65,8 @@ func CachePagedSearch[T any](instance T, total int64, relationNames []string, en // 从缓存中获取指定模型名称的分页检索结果,会同时采用`RetrieveCount`中的方法获取检索结果的总数量。 func RetrievePagedSearch[T any](entityName string, conditions ...string) (*T, int64, error) { - searchKey := assembleSearchKey(entityName, conditions...) - countKey := assembleCountKey(entityName, conditions...) + searchKey := AssembleSearchKey(entityName, conditions...) + countKey := AssembleCountKey(entityName, conditions...) instance, err := Retrieve[T](searchKey) if err != nil { return nil, -1, err From ce2bb160e11295bc1f216e449fdddbc0e16299a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 16 Jun 2023 13:48:34 +0800 Subject: [PATCH 078/141] =?UTF-8?q?enhance(enum):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=85=AC=E5=85=B1=E6=9E=9A=E4=B8=BE=E5=86=85=E5=AE=B9=E7=9A=84?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/enums.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/model/enums.go b/model/enums.go index 1e8faf4..18eb8fe 100644 --- a/model/enums.go +++ b/model/enums.go @@ -67,3 +67,17 @@ const ( RETRY_INTERVAL_ALGORITHM_TRIPLE_LINEAR_BACKOFF RETRY_INTERVAL_ALGORITHM_FIXED ) + +const ( + TAX_METHOD_INCLUSIVE int16 = iota + TAX_METHOD_EXCLUSIVE +) + +const ( + REPORT_CALCULATE_TASK_STATUS_PENDING int16 = iota + REPORT_CALCULATE_TASK_STATUS_SUCCESS + REPORT_CALCULATE_TASK_STATUS_INSUFICIENT_DATA + REPORT_CALCULATE_TASK_STATUS_SUSPENDED + REPORT_CALCULATE_TASK_STATUS_UNKNOWN_ERROR + REPORT_CALCULATE_TASK_STATUS_UNEXISTS = 99 +) From db52a140b1fc366ffd60466ec85093017bed12fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 16 Jun 2023 13:49:00 +0800 Subject: [PATCH 079/141] =?UTF-8?q?feat(invoice):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=8F=91=E7=A5=A8=E7=9B=B8=E5=85=B3=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=93=8D=E4=BD=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/invoice.go | 35 ++++ model/report.go | 129 ++++++++++++++ repository/invoice.go | 400 ++++++++++++++++++++++++++++++++++++++++++ vo/invoice.go | 27 +++ 4 files changed, 591 insertions(+) create mode 100644 model/report.go create mode 100644 repository/invoice.go create mode 100644 vo/invoice.go diff --git a/model/invoice.go b/model/invoice.go index 1f07041..827bfac 100644 --- a/model/invoice.go +++ b/model/invoice.go @@ -1,5 +1,12 @@ package model +import ( + "electricity_bill_calc/tools" + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + type InvoiceTitle struct { Name string `json:"name"` USCI string `json:"usci"` @@ -8,3 +15,31 @@ type InvoiceTitle struct { Bank string `json:"bank"` Account string `json:"account"` } + +type InvoiceCargo struct { + Name string `json:"name"` + Price decimal.Decimal `json:"price"` + Unit string `json:"unit"` + Quantity decimal.Decimal `json:"quantity"` + TaxRate decimal.Decimal `json:"taxRate"` + Tax decimal.Decimal `json:"tax"` + Total decimal.Decimal `json:"total"` +} + +type Invoice struct { + InvoiceNo string `json:"invoiceNo"` + Park string `json:"parkId" db:"park_id"` + Tenement string `json:"tenementId" db:"tenement_id"` + InvoiceType *string `json:"type" db:"type"` + Info InvoiceTitle `json:"invoiceInfo" db:"invoice_info"` + Cargos []InvoiceCargo `json:"cargos"` + TaxRate decimal.Decimal `json:"taxRate" db:"tax_rate"` + TaxMethod int16 `json:"taxMethod" db:"tax_method"` + Total decimal.Decimal `json:"total" db:"total"` + IssuedAt types.DateTime `json:"issuedAt" db:"issued_at"` + Covers []string `json:"covers"` +} + +func (i Invoice) Type() string { + return tools.DefaultOrEmptyStr(i.InvoiceType, "") +} diff --git a/model/report.go b/model/report.go new file mode 100644 index 0000000..5b58f60 --- /dev/null +++ b/model/report.go @@ -0,0 +1,129 @@ +package model + +import ( + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type ReportIndex struct { + Id string `json:"id"` + Park string `json:"parkId" db:"park_id"` + Period types.DateRange `json:"period"` + Category int16 `json:"category"` + MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"` + PricePolicy int16 `json:"pricePolicy"` + BasisPooled int16 `json:"basisPooled"` + AdjustPooled int16 `json:"adjustPooled"` + LossPooled int16 `json:"lossPooled"` + PublicPooled int16 `json:"publicPooled"` + Published bool `json:"published"` + PublishedAt *types.DateTime `json:"publishedAt" db:"published_at"` + Withdraw int16 `json:"withdraw"` + LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt" db:"last_withdraw_applied_at"` + LastWithdrawAuditAt *types.DateTime `json:"lastWithdrawAuditAt" db:"last_withdraw_audit_at"` + Status *int16 `json:"status"` + Message *string `json:"message"` + CreatedAt types.DateTime `json:"createdAt" db:"created_at"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` +} + +type ReportSummary struct { + ReportId string `json:"reportId" db:"report_id"` + OverallArea decimal.Decimal `json:"overallArea" db:"overall_area"` + Overall ConsumptionUnit `json:"overall"` + ConsumptionFee decimal.NullDecimal `json:"consumptionFee" db:"consumption_fee"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + Loss decimal.NullDecimal `json:"loss"` + LossFee decimal.NullDecimal `json:"lossFee" db:"loss_fee"` + LossProportion decimal.NullDecimal `json:"lossProportion" db:"loss_proportion"` + AuthorizeLoss *ConsumptionUnit `json:"authorizeLoss" db:"authorize_loss"` + BasicFee decimal.Decimal `json:"basicFee" db:"basic_fee"` + BasicPooledPriceConsumption decimal.NullDecimal `json:"basicPooledPriceConsumption" db:"basic_pooled_price_consumption"` + BasicPooledPriceArea decimal.NullDecimal `json:"basicPooledPriceArea" db:"basic_pooled_price_area"` + AdjustFee decimal.Decimal `json:"adjustFee" db:"adjust_fee"` + AdjustPooledPriceConsumption decimal.NullDecimal `json:"adjustPooledPriceConsumption" db:"adjust_pooled_price_consumption"` + AdjustPooledPriceArea decimal.NullDecimal `json:"adjustPooledPriceArea" db:"adjust_pooled_price_area"` + LossDilutedPrice decimal.NullDecimal `json:"lossDilutedPrice" db:"loss_diluted_price"` + TotalConsumption decimal.Decimal `json:"totalConsumption" db:"total_consumption"` + FinalDilutedOverall decimal.NullDecimal `json:"finalDilutedOverall" db:"final_diluted_overall"` +} + +type ReportPublicConsumption struct { + ReportId string `json:"reportId" db:"report_id"` + MeterId string `json:"parkMeterId" db:"park_meter_id"` + Overall ConsumptionUnit `json:"overall"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + LossAdjust ConsumptionUnit `json:"lossAdjust"` + ConsumptionTotal decimal.Decimal `json:"consumptionTotal" db:"consumption_total"` + LossAdjustTotal decimal.Decimal `json:"lossAdjustTotal" db:"loss_adjust_total"` + FinalTotal decimal.Decimal `json:"finalTotal" db:"final_total"` +} + +type ReportDetailedPublicConsumption struct { + MeterDetail + ReportPublicConsumption +} + +type ReportPooledConsumption struct { + ReportId string `json:"reportId" db:"report_id"` + MeterId string `json:"parkMeterId" db:"park_meter_id"` + Overall ConsumptionUnit `json:"overall"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + PooledArea decimal.Decimal `json:"pooledArea" db:"pooled_area"` + Diluted []NestedMeter `json:"diluted"` +} + +type ReportDetailedPooledConsumption struct { + MeterDetail + ReportPooledConsumption + PublicPooled int16 `json:"publicPooled"` +} + +type ReportDetailNestedMeterConsumption struct { + Meter MeterDetail `json:"meter"` + Consumption NestedMeter `json:"consumption"` +} + +type ReportTenement struct { + ReportId string `json:"reportId" db:"report_id"` + Tenement string `json:"tenementId" db:"tenement_id"` + Detail Tenement `json:"tenementDetail" db:"tenement_detail"` + Period types.DateRange `json:"calcPeriod" db:"calc_period"` + Overall ConsumptionUnit `json:"overall"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + BasicFeePooled decimal.Decimal `json:"basicFeePooled" db:"basic_fee_pooled"` + AdjustFeePooled decimal.Decimal `json:"adjustFeePooled" db:"adjust_fee_pooled"` + LossFeePooled decimal.Decimal `json:"lossFeePooled" db:"loss_fee_pooled"` + FinalPooled decimal.Decimal `json:"finalPooled" db:"final_pooled"` + FinalCharge decimal.Decimal `json:"finalCharge" db:"final_charge"` + Invoice []string `json:"invoice" db:"invoice"` + Meters []NestedMeter `json:"meters" db:"meters"` + Pooled []NestedMeter `json:"pooled" db:"pooled"` +} + +type ReportTask struct { + Id string `json:"id"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` + Status int16 `json:"status"` + Message *string `json:"message"` +} + +type SimplifiedTenementCharge struct { + ReportId string `json:"reportId" db:"report_id"` + Period types.DateRange `json:"period"` + TotalConsumption decimal.Decimal `json:"totalConsumption" db:"total_consumption"` + FinalCharge decimal.Decimal `json:"finalCharge" db:"final_charge"` +} diff --git a/repository/invoice.go b/repository/invoice.go new file mode 100644 index 0000000..413e726 --- /dev/null +++ b/repository/invoice.go @@ -0,0 +1,400 @@ +package repository + +import ( + "context" + "electricity_bill_calc/cache" + "electricity_bill_calc/config" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/types" + "errors" + "fmt" + "strings" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "github.com/shopspring/decimal" + "go.uber.org/zap" +) + +type _InvoiceRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var InvoiceRepository = _InvoiceRepository{ + log: logger.Named("Repository", "Invoice"), + ds: goqu.Dialect("postgres"), +} + +// 查询指定园区中符合条件的发票 +func (ir _InvoiceRepository) ListInvoice(pid *string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.Invoice, int64, error) { + ir.log.Info("查询指定园区的发票。", zap.Stringp("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("Keyword", keyword), zap.Uint("Page", page)) + cacheCondition := []string{ + cache.NullableStringKey(pid), + fmt.Sprintf("%d", page), + cache.NullableStringKey(keyword), + cache.NullableConditionKey(startDate), + cache.NullableConditionKey(endDate), + } + if invoices, total, err := cache.RetrievePagedSearch[[]*model.Invoice]("invoice", cacheCondition...); err != nil && invoices != nil && len(*invoices) > 0 { + ir.log.Info("从缓存中获取到了符合条件的发票记录。", zap.Int("Count", len(*invoices)), zap.Int64("Total", total)) + return *invoices, total, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + invoiceQuery := ir.ds. + From(goqu.T("invoice").As("i")). + Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("i.tenement_id").Eq(goqu.I("t.id")))). + Select("i.*") + countQuery := ir.ds. + From(goqu.T("invoice").As("i")). + Select(goqu.COUNT("*")) + + if pid != nil && len(*pid) > 0 { + invoiceQuery = invoiceQuery.Where(goqu.I("t.park_id").Eq(*pid)) + countQuery = countQuery.Where(goqu.I("t.park_id").Eq(*pid)) + } + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + invoiceQuery = invoiceQuery.Where(goqu.Or( + goqu.I("i.invoice_no").ILike(pattern), + goqu.I("t.full_name").ILike(pattern), + goqu.I("t.short_name").ILike(pattern), + goqu.I("t.abbr").ILike(pattern), + goqu.I("t.contact_name").ILike(pattern), + goqu.I("t.contact_phone").ILike(pattern), + goqu.L("t.invoice_info->>'usci'").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("i.invoice_no").ILike(pattern), + goqu.I("t.full_name").ILike(pattern), + goqu.I("t.short_name").ILike(pattern), + goqu.I("t.abbr").ILike(pattern), + goqu.I("t.contact_name").ILike(pattern), + goqu.I("t.contact_phone").ILike(pattern), + goqu.L("t.invoice_info->>'usci'").ILike(pattern), + )) + } + + var queryRange = types.NewEmptyDateTimeRange() + if startDate != nil { + queryRange.SetLower(startDate.ToBeginningOfDate()) + } + if endDate != nil { + queryRange.SetUpper(endDate.ToEndingOfDate()) + } + invoiceQuery = invoiceQuery.Where(goqu.L("i.issued_at <@ ?", queryRange)) + countQuery = countQuery.Where(goqu.L("i.issued_at <@ ?", queryRange)) + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + invoiceQuery = invoiceQuery. + Order(goqu.I("i.issued_at").Desc()). + Offset(startRow). + Limit(config.ServiceSettings.ItemsPageSize) + + var ( + invoices []*model.Invoice = make([]*model.Invoice, 0) + total int64 + ) + querySql, queryArgs, _ := invoiceQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &invoices, querySql, queryArgs...); err != nil { + ir.log.Error("查询发票记录失败。", zap.Error(err)) + return invoices, 0, err + } + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { + ir.log.Error("查询发票记录数失败。", zap.Error(err)) + return invoices, 0, err + } + var relationName string + if pid != nil && len(*pid) > 0 { + relationName = fmt.Sprintf("invoice:%s", *pid) + } else { + relationName = "invoice" + } + cache.CachePagedSearch(invoices, total, []string{relationName}, "invoice", cacheCondition...) + return invoices, total, nil +} + +// 查询指定商户未开票的核算记录,改记录将只包括商户整体核算,不包括商户各个表计的详细 +func (ir _InvoiceRepository) ListUninvoicedTenementCharges(tid string) ([]*model.SimplifiedTenementCharge, error) { + ir.log.Info("查询指定商户的未开票核算记录", zap.String("Tenement", tid)) + cacheConditions := []string{ + tid, + } + if records, err := cache.RetrieveSearch[[]*model.SimplifiedTenementCharge]("uninvoiced_tenement_charge", cacheConditions...); err == nil && records != nil && len(*records) > 0 { + ir.log.Info("从缓存中获取到了符合条件的未开票核算记录。", zap.Int("Count", len(*records))) + return *records, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + chargeSql, chargeArgs, _ := ir.ds. + From(goqu.T("report_tenement").As("t")). + Join(goqu.T("report").As("r"), goqu.On(goqu.I("t.report_id").Eq(goqu.I("r.id")))). + Select( + goqu.I("t.report_id"), + goqu.I("r.period"), + goqu.L("(t.overall->>'amount')::numeric").As("amount"), + goqu.I("t.final_charge"), + ). + Where( + goqu.I("t.tenement_id").Eq(tid), + goqu.I("t.invoice").IsNull(), + ). + Prepared(true).ToSQL() + var charges []*model.SimplifiedTenementCharge + if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil { + ir.log.Error("查询未开票核算记录失败。", zap.Error(err)) + return charges, err + } + cache.CacheSearch(charges, []string{fmt.Sprintf("uninvoiced_tenement_charge:%s", tid)}, "uninvoiced_tenement_charge", cacheConditions...) + searchKey := cache.AssembleSearchKey("uninvoiced_tenement_charge", cacheConditions...) + for _, charge := range charges { + cache.CacheRelation(fmt.Sprintf("report:%s", charge.ReportId), cache.STORE_TYPE_KEY, searchKey) + } + return charges, nil +} + +// 更新指定核算中指定商户的开票状态以及对应发票号。 +// 如果给定了发票号,那么指定记录状态为已开票,如果给定的发票号为`nil`,啊么指定记录为未开票。 +func (ir _InvoiceRepository) UpdateTenementInvoicedState(tx pgx.Tx, ctx context.Context, rid, tid string, invoiceNo *string) error { + ir.log.Info("更新指定核算中指定商户的开票状态和记录", zap.String("Report", rid), zap.String("Tenement", tid), zap.Stringp("InvoiceNo", invoiceNo)) + updateSql, updateArgs, _ := ir.ds. + Update(goqu.T("report_tenement")). + Set(goqu.Record{ + "invoice": invoiceNo, + }). + Where( + goqu.I("report_id").Eq(rid), + goqu.I("tenement_id").Eq(tid), + ). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil { + ir.log.Error("更新核算记录的开票状态失败。", zap.Error(err)) + return err + } + return nil +} + +// 查询指定发票的详细记录信息 +func (ir _InvoiceRepository) GetInvoiceDetail(invoiceNo string) (*model.Invoice, error) { + ir.log.Info("查询指定发票的详细信息", zap.String("InvoiceNo", invoiceNo)) + if invoice, err := cache.RetrieveEntity[model.Invoice]("invoice", invoiceNo); err == nil && invoice != nil { + ir.log.Info("从缓存中获取到了符合条件的发票记录。") + return invoice, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + invoiceSql, invoiceArgs, _ := ir.ds. + From(goqu.T("invoice")). + Select("*"). + Where(goqu.I("invoice_no").Eq(invoiceNo)). + Prepared(true).ToSQL() + var invoice model.Invoice + if err := pgxscan.Get(ctx, global.DB, &invoice, invoiceSql, invoiceArgs...); err != nil { + ir.log.Error("查询发票记录失败。", zap.Error(err)) + return nil, err + } + cache.CacheEntity(invoice, []string{fmt.Sprintf("invoice:%s", invoiceNo)}, "invoice", invoiceNo) + return &invoice, nil +} + +// 获取指定商户的简化核算记录 +func (ir _InvoiceRepository) GetSimplifiedTenementCharges(tid string, rids []string) ([]*model.SimplifiedTenementCharge, error) { + ir.log.Info("查询庄园商户的简化核算记录", zap.String("Tenement", tid), zap.Strings("Reports", rids)) + cacheConditions := []string{ + tid, + strings.Join(rids, ":"), + } + if records, err := cache.RetrieveSearch[[]*model.SimplifiedTenementCharge]("simplified_tenement_charge", cacheConditions...); err == nil && records != nil && len(*records) > 0 { + ir.log.Info("从缓存中获取到了符合条件的简化核算记录。", zap.Int("Count", len(*records))) + return *records, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + chargeSql, chargeArgs, _ := ir.ds. + From(goqu.T("report_tenement").As("t")). + Join(goqu.T("report").As("r"), goqu.On(goqu.I("t.report_id").Eq(goqu.I("r.id")))). + Select( + goqu.I("t.report_id"), + goqu.I("r.period"), + goqu.L("(t.overall->>'amount')::numeric").As("amount"), + goqu.I("t.final_charge"), + ). + Where( + goqu.I("t.tenement_id").Eq(tid), + goqu.I("t.report_id").In(rids), + ). + Prepared(true).ToSQL() + var charges []*model.SimplifiedTenementCharge + if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil { + ir.log.Error("查询简化核算记录失败。", zap.Error(err)) + return charges, err + } + cache.CacheSearch(charges, []string{fmt.Sprintf("tenement:%s", tid)}, "simplified_tenement_charge", cacheConditions...) + searchKey := cache.AssembleSearchKey("simplified_tenement_charge", cacheConditions...) + for _, charge := range charges { + cache.CacheRelation(fmt.Sprintf("report:%s", charge.ReportId), cache.STORE_TYPE_KEY, searchKey) + } + return charges, nil +} + +// 查询发票号码对应的商户 ID +// ! 这个方法不能被加入缓存,这个方法存在的目的就是为了清除缓存。 +func (ir _InvoiceRepository) GetInvoiceBelongs(invoiceNo string) ([]string, error) { + ir.log.Info("查询发票号码对应的商户 ID", zap.String("InvoiceNo", invoiceNo)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tenementSql, tenementArgs, _ := ir.ds. + From(goqu.T("invoice")). + Select("tenement_id"). + Where(goqu.I("i.invoice_no").Eq(invoiceNo)). + Prepared(true).ToSQL() + var tenementIds []string + if err := pgxscan.Select(ctx, global.DB, &tenementIds, tenementSql, tenementArgs...); err != nil { + ir.log.Error("查询发票号码对应的商户 ID 失败。", zap.Error(err)) + return tenementIds, err + } + return tenementIds, nil +} + +// 删除指定的发票记录 +func (ir _InvoiceRepository) Delete(tx pgx.Tx, ctx context.Context, invoiceNo string) error { + ir.log.Info("删除指定的发票记录", zap.String("InvoiceNo", invoiceNo)) + deleteSql, deleteArgs, _ := ir.ds. + Delete(goqu.T("invoice")). + Where(goqu.I("invoice_no").Eq(invoiceNo)). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, deleteSql, deleteArgs...); err != nil { + ir.log.Error("删除发票记录失败。", zap.Error(err)) + return err + } + return nil +} + +// 删除指定发票记录与指定核算记录之间的关联 +func (ir _InvoiceRepository) DeleteInvoiceTenementRelation(tx pgx.Tx, ctx context.Context, invoiceNo string) error { + ir.log.Info("删除指定发票记录与指定核算记录之间的关联", zap.String("InvoiceNo", invoiceNo)) + updateSql, updateArgs, _ := ir.ds. + Update(goqu.T("report_tenement")). + Set(goqu.Record{ + "invoice": nil, + }). + Where(goqu.I("invoice").Eq(invoiceNo)). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil { + ir.log.Error("删除发票记录与核算记录之间的关联失败。", zap.Error(err)) + return err + } + return nil +} + +// 确认发票的归属 +func (ir _InvoiceRepository) IsBelongsTo(invoiceNo, uid string) (bool, error) { + ir.log.Info("确认发票的归属", zap.String("InvoiceNo", invoiceNo), zap.String("User", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + querySql, queryArgs, _ := ir.ds. + From(goqu.T("invoice").As("i")). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("i.park_id")))). + Select(goqu.COUNT("i.*")). + Where( + goqu.I("i.invoice_no").Eq(invoiceNo), + goqu.I("p.user_id").Eq(uid), + ). + Prepared(true).ToSQL() + var count int64 + if err := pgxscan.Get(ctx, global.DB, &count, querySql, queryArgs...); err != nil { + ir.log.Error("查询发票归属失败", zap.Error(err)) + return false, err + } + return count > 0, nil +} + +// 创建一条新的发票记录 +func (ir _InvoiceRepository) Create(pid, tid, invoiceNo string, invoiceType *string, amount decimal.Decimal, issuedAt types.DateTime, taxMethod int16, taxRate decimal.Decimal, cargos *[]*model.InvoiceCargo, covers *[]string) error { + ir.log.Info("记录一个新的发票", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Invoice", invoiceNo)) + ctx, cancel := global.TimeoutContext() + defer cancel() + tx, err := global.DB.Begin(ctx) + if err != nil { + ir.log.Error("开启事务失败。", zap.Error(err)) + return err + } + + tenemenetSql, tenementArgs, _ := ir.ds. + From(goqu.T("tenement").As("t")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("t.building").Eq(goqu.I("b.id")))). + Select( + "t.*", goqu.I("b.name").As("building_name"), + ). + Where(goqu.I("t.id").Eq(tid)). + Prepared(true).ToSQL() + var tenement model.Tenement + if err := pgxscan.Get(ctx, global.DB, &tenement, tenemenetSql, tenementArgs...); err != nil { + ir.log.Error("查询商户信息失败。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + + if tenement.InvoiceInfo == nil { + ir.log.Error("尚未设定商户的发票抬头信息") + tx.Rollback(ctx) + return errors.New("尚未设定商户的发票抬头信息") + } + + createSql, createArgs, _ := ir.ds. + Insert(goqu.T("invoice")). + Cols( + "invoice_no", "park_id", "tenement_id", "invoice_type", "amount", "issued_at", "tax_method", "tax_rate", "cargos", "covers", + ). + Vals(goqu.Vals{ + invoiceNo, pid, tid, invoiceType, amount, issuedAt, taxMethod, taxRate, cargos, covers, + }). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil { + ir.log.Error("创建发票记录失败。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + + updateSql, updateArgs, _ := ir.ds. + Update(goqu.T("report_tenement")). + Set(goqu.Record{ + "invoice": invoiceNo, + }). + Where( + goqu.I("tenement_id").Eq(tid), + goqu.I("report_id").In(*covers), + ). + Prepared(true).ToSQL() + if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil { + ir.log.Error("更新核算记录的开票状态失败。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + + err = tx.Commit(ctx) + if err != nil { + ir.log.Error("提交事务失败。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + for _, rid := range *covers { + cache.AbolishRelation(fmt.Sprintf("report:%s", rid)) + } + cache.AbolishRelation(fmt.Sprintf("invoice:%s", pid)) + cache.AbolishRelation("invoice") + return nil +} diff --git a/vo/invoice.go b/vo/invoice.go new file mode 100644 index 0000000..e60a3fa --- /dev/null +++ b/vo/invoice.go @@ -0,0 +1,27 @@ +package vo + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type InvoiceResponse struct { + No string `json:"no" copier:"InvoiceNo"` + Tenement string `json:"tenement"` + Title model.InvoiceTitle `json:"title" copier:"Info"` + IssuedAt types.DateTime `json:"issuedAt"` + Amount decimal.Decimal `json:"amount" copier:"Total"` + TaxMethod int16 `json:"taxMethod"` + TaxRate decimal.Decimal `json:"taxRate"` + InvoiceType string `json:"invoiceType" copier:"Type"` +} + +type InvoiceCreationForm struct { + Park string `json:"park"` + Tenement string `json:"tenement"` + TaxMethod int16 `json:"taxMethod"` + TaxRate decimal.NullDecimal `json:"taxRate"` + Covers []string `json:"covers"` +} From e5dadf06be0060018e26052559ff2a62c0cf4f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 17 Jun 2023 09:30:48 +0800 Subject: [PATCH 080/141] =?UTF-8?q?feat(error):=E5=A2=9E=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=B8=8D=E8=B6=B3=E7=9A=84=E9=94=99=E8=AF=AF=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exceptions/insufficient_data.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 exceptions/insufficient_data.go diff --git a/exceptions/insufficient_data.go b/exceptions/insufficient_data.go new file mode 100644 index 0000000..45a2047 --- /dev/null +++ b/exceptions/insufficient_data.go @@ -0,0 +1,16 @@ +package exceptions + +import "fmt" + +type InsufficientDataError struct { + Field string + Message string +} + +func NewInsufficientDataError(field, msg string) *InsufficientDataError { + return &InsufficientDataError{Field: field, Message: msg} +} + +func (e InsufficientDataError) Error() string { + return fmt.Sprintf("字段 [%s] 数据不足,%s", e.Field, e.Message) +} From 986562c2c20b4b87010dfec893912bb4b99302f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 17 Jun 2023 09:37:09 +0800 Subject: [PATCH 081/141] =?UTF-8?q?enahnce(error):=E5=B0=86=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=B1=BB=E5=9E=8B=E4=B8=AD=E7=9A=84=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=9B=B4=E6=94=B9=E4=B8=BA=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exceptions/auth.go | 2 +- exceptions/illegal_arguments.go | 2 +- exceptions/improper_operate.go | 2 +- exceptions/not_found.go | 2 +- exceptions/unsuccessful.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exceptions/auth.go b/exceptions/auth.go index e452ceb..af0ed46 100644 --- a/exceptions/auth.go +++ b/exceptions/auth.go @@ -31,5 +31,5 @@ func NewUnauthorizedError(msg string) *UnauthorizedError { } func (e UnauthorizedError) Error() string { - return fmt.Sprintf("Unauthorized: %s", e.Message) + return fmt.Sprintf("用户未获得授权: %s", e.Message) } diff --git a/exceptions/illegal_arguments.go b/exceptions/illegal_arguments.go index 8760a4d..e6871ae 100644 --- a/exceptions/illegal_arguments.go +++ b/exceptions/illegal_arguments.go @@ -15,5 +15,5 @@ func NewIllegalArgumentsError(msg string, arguments ...string) IllegalArgumentsE } func (e IllegalArgumentsError) Error() string { - return fmt.Sprintf("Illegal Arguments, %s", e.Message) + return fmt.Sprintf("使用了非法参数, %s", e.Message) } diff --git a/exceptions/improper_operate.go b/exceptions/improper_operate.go index 4ac7626..f2fba06 100644 --- a/exceptions/improper_operate.go +++ b/exceptions/improper_operate.go @@ -15,5 +15,5 @@ func NewImproperOperateError(msg string, arguments ...string) ImproperOperateErr } func (e ImproperOperateError) Error() string { - return fmt.Sprintf("Improper Operate, %s", e.Message) + return fmt.Sprintf("操作不恰当, %s", e.Message) } diff --git a/exceptions/not_found.go b/exceptions/not_found.go index 14065ba..4bc8ae1 100644 --- a/exceptions/not_found.go +++ b/exceptions/not_found.go @@ -11,7 +11,7 @@ func NewNotFoundError(msg string) *NotFoundError { } func NewNotFoundErrorFromError(msg string, err error) *NotFoundError { - return &NotFoundError{Message: fmt.Sprintf("%s,%v", msg, err)} + return &NotFoundError{Message: fmt.Sprintf("所需数据未找到,%s,%v", msg, err)} } func (e NotFoundError) Error() string { diff --git a/exceptions/unsuccessful.go b/exceptions/unsuccessful.go index 22a9da9..0309658 100644 --- a/exceptions/unsuccessful.go +++ b/exceptions/unsuccessful.go @@ -7,5 +7,5 @@ func NewUnsuccessfulOperationError() *UnsuccessfulOperationError { } func (UnsuccessfulOperationError) Error() string { - return "Unsuccessful Operation" + return "操作不成功。" } From 11bd661e7919f3834fca7720e8b8aecd062d4cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 17 Jun 2023 10:43:51 +0800 Subject: [PATCH 082/141] =?UTF-8?q?enahnce(error):=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E6=9C=AA=E6=88=90=E5=8A=9F=E5=AE=8C=E6=88=90=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E7=9A=84=E9=94=99=E8=AF=AF=E6=8F=8F=E8=BF=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exceptions/unsuccessful.go | 129 +++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/exceptions/unsuccessful.go b/exceptions/unsuccessful.go index 0309658..f2623a0 100644 --- a/exceptions/unsuccessful.go +++ b/exceptions/unsuccessful.go @@ -1,11 +1,130 @@ package exceptions -type UnsuccessfulOperationError struct{} +import ( + "strings" +) -func NewUnsuccessfulOperationError() *UnsuccessfulOperationError { - return &UnsuccessfulOperationError{} +type OperationType int16 + +const ( + OPERATE_CREATE OperationType = iota + OPERATE_UPDATE + OPERATE_DELETE + OEPRATE_QUERY + OPERATE_CALCULATE + OPERATE_DB + OPERATE_DB_TRANSACTION + OPERATE_CUSTOM OperationType = 98 + OPERATE_OTHER OperationType = 99 +) + +type UnsuccessfulOperationError struct { + Operate OperationType + Description string + Message string } -func (UnsuccessfulOperationError) Error() string { - return "操作不成功。" +func NewUnsuccessfulOperationError(oeprate OperationType, describe, message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: oeprate, + Description: describe, + Message: message, + } +} + +func NewUnsuccessCreateError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_CREATE, + Message: message, + } +} + +func NewUnsuccessUpdateError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_UPDATE, + Message: message, + } +} + +func NewUnsuccessDeleteError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_DELETE, + Message: message, + } +} + +func NewUnsuccessQueryError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OEPRATE_QUERY, + Message: message, + } +} + +func NewUnsuccessCalculateError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_CALCULATE, + Message: message, + } +} + +func NewUnsuccessDBError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_DB, + Message: message, + } +} + +func NewUnsuccessDBTransactionError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_DB_TRANSACTION, + Message: message, + } +} + +func NewUnsuccessCustomError(describe, message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_CUSTOM, + Description: describe, + Message: message, + } +} + +func NewUnsuccessOtherError(message string) *UnsuccessfulOperationError { + return &UnsuccessfulOperationError{ + Operate: OPERATE_OTHER, + Message: message, + } +} + +func (e UnsuccessfulOperationError) Error() string { + var builder strings.Builder + switch e.Operate { + case OPERATE_CREATE: + builder.WriteString("创建") + case OPERATE_UPDATE: + builder.WriteString("更新") + case OPERATE_DELETE: + builder.WriteString("删除") + case OEPRATE_QUERY: + builder.WriteString("查询") + case OPERATE_CALCULATE: + builder.WriteString("计算") + case OPERATE_DB: + builder.WriteString("数据库") + case OPERATE_DB_TRANSACTION: + builder.WriteString("数据库事务") + case OPERATE_CUSTOM: + builder.WriteString(e.Description) + case OPERATE_OTHER: + builder.WriteString("其他") + default: + builder.WriteString("未知") + } + builder.WriteString("操作不成功,") + if len(e.Message) > 0 { + builder.WriteString(e.Message) + } else { + builder.WriteString("未知原因") + } + return builder.String() } From e88477a710bc1e19fca1cfcfab7382ea624df843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 17 Jun 2023 10:44:24 +0800 Subject: [PATCH 083/141] =?UTF-8?q?feat(invoice):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=8F=91=E7=A5=A8=E7=9B=B8=E5=85=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E6=9C=8D=E5=8A=A1=E6=93=8D=E4=BD=9C=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/invoice.go | 180 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 service/invoice.go diff --git a/service/invoice.go b/service/invoice.go new file mode 100644 index 0000000..59b4716 --- /dev/null +++ b/service/invoice.go @@ -0,0 +1,180 @@ +package service + +import ( + "electricity_bill_calc/exceptions" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "electricity_bill_calc/types" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/samber/lo" + "github.com/shopspring/decimal" + "go.uber.org/zap" +) + +type _InvoiceSerivce struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var InvoiceService = _InvoiceSerivce{ + log: logger.Named("Service", "Invoice"), + ds: goqu.Dialect("postgres"), +} + +// 获取指定的发票信息,包括发票覆盖的商户核算信息 +func (is _InvoiceSerivce) GetInvoice(invoiceNo string) (*model.Invoice, []*model.SimplifiedTenementCharge, error) { + is.log.Info("获取指定发票的信息", zap.String("InvoiceNo", invoiceNo)) + invoice, err := repository.InvoiceRepository.GetInvoiceDetail(invoiceNo) + if err != nil || invoice == nil { + is.log.Error("获取指定发票的信息失败", zap.Error(err)) + return nil, nil, exceptions.NewNotFoundError("指定发票信息不存在。") + } + + charges, err := repository.InvoiceRepository.GetSimplifiedTenementCharges(invoice.Tenement, invoice.Covers) + if err != nil { + is.log.Error("获取指定发票的信息失败", zap.Error(err)) + return nil, nil, err + } + return invoice, charges, nil +} + +// 根据给定的商户核算记录和发票基本信息,计算发票中的货物信息 +func (is _InvoiceSerivce) CalculateInvoiceAmount(method int16, rate decimal.Decimal, reports []*model.SimplifiedTenementCharge) (decimal.Decimal, []*model.InvoiceCargo, error) { + is.log.Info("计算指定商户发票中的货物信息", zap.Int16("Method", method), logger.DecimalField("Rate", rate)) + tenementConsumptionTotal := lo.Reduce(reports, func(agg decimal.Decimal, r *model.SimplifiedTenementCharge, _ int) decimal.Decimal { + return agg.Add(r.TotalConsumption) + }, decimal.Zero) + tenementChargeTotal := lo.Reduce(reports, func(agg decimal.Decimal, r *model.SimplifiedTenementCharge, _ int) decimal.Decimal { + return agg.Add(r.FinalCharge) + }, decimal.Zero) + if tenementConsumptionTotal.IsZero() { + err := exceptions.NewInsufficientDataError("TotalConsumption", "商户核算记录中没有电量消耗数据。") + is.log.Warn("计算指定商户发票中的货物信息失败", zap.Error(err)) + return decimal.Zero, nil, err + } + var tenementTaxTotal, chargePrice, cargoTotal decimal.Decimal + switch method { + case model.TAX_METHOD_INCLUSIVE: + tenementTaxTotal = tenementChargeTotal.Div(rate.Add(decimal.NewFromInt(1))).Mul(rate) + chargePrice = (tenementChargeTotal.Sub(tenementTaxTotal)).Div(tenementConsumptionTotal) + cargoTotal = tenementChargeTotal + case model.TAX_METHOD_EXCLUSIVE: + tenementTaxTotal = tenementChargeTotal.Mul(rate) + chargePrice = tenementChargeTotal.Div(tenementConsumptionTotal) + cargoTotal = tenementChargeTotal.Add(tenementTaxTotal) + default: + return decimal.Zero, make([]*model.InvoiceCargo, 0), exceptions.NewIllegalArgumentsError("不支持的税率计算方式。") + } + cargos := []*model.InvoiceCargo{ + { + Name: "电费", + Unit: "千瓦时", + Quantity: tenementConsumptionTotal, + Price: chargePrice.RoundBank(2), + Total: tenementChargeTotal.RoundBank(2), + TaxRate: rate.RoundBank(2), + Tax: tenementTaxTotal.RoundBank(2), + }, + } + return cargoTotal.RoundBank(2), cargos, nil +} + +// 利用用户提供的内容对发票数据进行试计算 +func (is _InvoiceSerivce) TestCalculateInvoice(pid, tid string, method int16, rate decimal.NullDecimal, covers []string) (decimal.Decimal, []*model.InvoiceCargo, error) { + is.log.Info("试计算发票票面数据", zap.String("Park", pid), zap.String("Tenement", tid), zap.Int16("Method", method), logger.DecimalField("Rate", rate.Decimal)) + park, err := repository.ParkRepository.RetrieveParkDetail(pid) + if err != nil || park == nil { + is.log.Error("试计算发票票面数据失败,未能获取到指定园区的信息", zap.Error(err)) + return decimal.Zero, nil, exceptions.NewNotFoundError("指定的园区不存在。") + } + if !rate.Valid && !park.TaxRate.Valid { + is.log.Error("试计算发票票面数据失败,必须要设定发票税率") + return decimal.Zero, nil, exceptions.NewIllegalArgumentsError("必须要设定发票税率。") + } + taxRate := park.TaxRate.Decimal + if rate.Valid { + taxRate = rate.Decimal + } + reports, err := repository.InvoiceRepository.GetSimplifiedTenementCharges(tid, covers) + if err != nil { + is.log.Error("试计算发票票面数据失败,未能获取到指定商户的核算记录", zap.Error(err)) + return decimal.Zero, nil, err + } + return is.CalculateInvoiceAmount(method, taxRate, reports) +} + +// 记录一个新的发票信息 +func (is _InvoiceSerivce) SaveInvoice(pid, tid, invoiceNo string, invoiceType *string, method int16, rate decimal.NullDecimal, covers []string) error { + is.log.Info("记录一个新的发票信息", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("InvoiceNo", invoiceNo)) + park, err := repository.ParkRepository.RetrieveParkDetail(pid) + if err != nil || park == nil { + is.log.Error("记录一个新的发票信息失败,未能获取到指定园区的信息", zap.Error(err)) + return exceptions.NewNotFoundError("指定的园区不存在。") + } + if !rate.Valid && park.TaxRate.Valid { + is.log.Error("记录一个新的发票信息失败,必须要设定发票税率") + return exceptions.NewIllegalArgumentsError("必须要设定发票税率。") + } + taxRate := park.TaxRate.Decimal + if rate.Valid { + taxRate = rate.Decimal + } + reports, err := repository.InvoiceRepository.GetSimplifiedTenementCharges(tid, covers) + if err != nil { + is.log.Error("记录一个新的发票信息失败,未能获取到指定商户的核算记录", zap.Error(err)) + return exceptions.NewUnsuccessQueryError("未能获取到指定商户的核算记录。") + } + total, cargos, err := is.CalculateInvoiceAmount(method, taxRate, reports) + if err != nil { + is.log.Error("记录一个新的发票信息失败,未能计算发票票面数据", zap.Error(err)) + return exceptions.NewUnsuccessCalculateError("未能计算发票票面数据。") + } + issuedAt := types.Now() + + err = repository.InvoiceRepository.Create(pid, tid, invoiceNo, invoiceType, total, issuedAt, method, taxRate, &cargos, &covers) + if err != nil { + is.log.Error("记录一个新的发票信息失败,未能保存发票信息", zap.Error(err)) + return exceptions.NewUnsuccessCreateError("未能保存发票信息。") + } + return nil +} + +// 删除指定的发票信息 +func (is _InvoiceSerivce) DeleteInvoice(invoiceNo string) error { + is.log.Info("删除指定的发票信息", zap.String("InvoiceNo", invoiceNo)) + invoice, err := repository.InvoiceRepository.GetInvoiceDetail(invoiceNo) + if err != nil || invoice == nil { + is.log.Error("删除指定的发票信息失败,未能获取到指定发票的信息", zap.Error(err)) + return exceptions.NewNotFoundError("指定的发票信息不存在。") + } + ctx, cancel := global.TimeoutContext() + defer cancel() + tx, err := global.DB.Begin(ctx) + if err != nil { + is.log.Error("删除指定的发票信息失败,未能开启事务", zap.Error(err)) + return exceptions.NewUnsuccessDBTransactionError("未能开启事务。") + } + err = repository.InvoiceRepository.Delete(tx, ctx, invoiceNo) + if err != nil { + is.log.Error("删除指定的发票信息失败,未能删除发票信息", zap.Error(err)) + tx.Rollback(ctx) + return exceptions.NewUnsuccessDeleteError("未能删除发票信息。") + } + err = repository.InvoiceRepository.DeleteInvoiceTenementRelation(tx, ctx, invoiceNo) + if err != nil { + is.log.Error("删除指定的发票信息失败,未能删除发票与商户核算记录之间的关联", zap.Error(err)) + tx.Rollback(ctx) + return exceptions.NewUnsuccessDeleteError("未能删除发票与商户核算记录之间的关联。") + } + err = tx.Commit(ctx) + if err != nil { + is.log.Error("删除指定的发票信息失败,未能提交事务", zap.Error(err)) + tx.Rollback(ctx) + return exceptions.NewUnsuccessDBTransactionError("未能提交事务。") + } + return nil +} From 541932d62e2802c8db2e763260d7919f324b5476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 19 Jun 2023 13:20:30 +0800 Subject: [PATCH 084/141] =?UTF-8?q?feat(invoice):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=95=86=E6=88=B7=E5=8F=91=E7=A5=A8=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=BE=85=E6=B5=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/invoice.go | 227 ++++++++++++++++++++++++++++++++++++++++++ router/router.go | 1 + vo/invoice.go | 12 +++ 3 files changed, 240 insertions(+) create mode 100644 controller/invoice.go diff --git a/controller/invoice.go b/controller/invoice.go new file mode 100644 index 0000000..c96622d --- /dev/null +++ b/controller/invoice.go @@ -0,0 +1,227 @@ +package controller + +import ( + "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" + + "github.com/gofiber/fiber/v2" + "github.com/jinzhu/copier" + "go.uber.org/zap" +) + +var invoiceLog = logger.Named("Controller", "Invoice") + +func InitializeInvoiceHandler(router *fiber.App) { + router.Get("/invoice", security.MustAuthenticated, listInvoices) + router.Post("/invoice", security.EnterpriseAuthorize, createNewInvoiceRecord) + router.Post("/invoice/precalculate", security.EnterpriseAuthorize, testCalculateInvoice) + router.Get("/invoice/:code", security.EnterpriseAuthorize, getInvoiceDetail) + router.Delete("/invoice/:code", security.EnterpriseAuthorize, deleteInvoiceRecord) + router.Get("/uninvoiced/tenemennt/:tid/report", security.EnterpriseAuthorize, getUninvoicedTenementReports) +} + +// 列出指定园区中的符合条件的发票记录 +func listInvoices(c *fiber.Ctx) error { + invoiceLog.Info("列出指定园区中的符合条件的发票记录") + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,不能获取到有效的用户会话。", zap.Error(err)) + return result.Unauthorized("未能获取到有效的用户会话。") + } + park := tools.EmptyToNil(c.Query("park")) + if session.Type == model.USER_TYPE_ENT && park != nil && len(*park) > 0 { + pass, err := checkParkBelongs(*park, invoiceLog, c, &result) + if err != nil || !pass { + return err + } + } + startDate, err := types.ParseDatep(c.Query("start_date")) + if err != nil { + invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,开始日期参数解析错误。", zap.Error(err)) + return result.BadRequest("开始日期参数解析错误。") + } + endDate, err := types.ParseDatep(c.Query("end_date")) + if err != nil { + invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,结束日期参数解析错误。", zap.Error(err)) + return result.BadRequest("结束日期参数解析错误。") + } + keyword := tools.EmptyToNil(c.Query("keyword")) + page := c.QueryInt("page", 1) + invoices, total, err := repository.InvoiceRepository.ListInvoice(park, startDate, endDate, keyword, uint(page)) + if err != nil { + invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,检索符合条件的发票记录出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "检索符合条件的发票记录出现错误。") + } + var invoiceResponse []*vo.InvoiceResponse + copier.Copy(&invoiceResponse, &invoices) + return result.Success( + "已经获取到符合条件的发票列表。", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{ + "invoices": invoiceResponse, + }, + ) +} + +// 获取指定发票的详细信息 +func getInvoiceDetail(c *fiber.Ctx) error { + result := response.NewResult(c) + invoiceNo := tools.EmptyToNil(c.Params("code")) + invoiceLog.Info("获取指定发票的详细信息", zap.Stringp("InvoiceNo", invoiceNo)) + if invoiceNo == nil { + invoiceLog.Error("获取指定发票的详细信息失败,未指定发票编号。") + return result.BadRequest("未指定发票编号。") + } + session, err := _retreiveSession(c) + if err != nil { + invoiceLog.Error("获取指定发票的详细信息失败,不能获取到有效的用户会话。", zap.Error(err)) + return result.Unauthorized("未能获取到有效的用户会话。") + } + pass, err := repository.InvoiceRepository.IsBelongsTo(*invoiceNo, session.Uid) + if err != nil { + invoiceLog.Error("获取指定发票的详细信息失败,检查发票所属权时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "检查发票所属权时出现错误。") + } + if !pass { + invoiceLog.Error("获取指定发票的详细信息失败,发票不属于当前用户。") + return result.Forbidden("不能访问不属于自己的发票。") + } + invoice, err := repository.InvoiceRepository.GetInvoiceDetail(*invoiceNo) + if err != nil { + invoiceLog.Error("获取指定发票的详细信息失败,检索发票信息时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "检索发票信息时出现错误。") + } + if invoice == nil { + invoiceLog.Error("获取指定发票的详细信息失败,指定发票不存在。") + return result.NotFound("指定发票不存在。") + } + var invoiceResponse vo.ExtendedInvoiceResponse + copier.Copy(&invoiceResponse, &invoice) + return result.Success( + "已经获取到指定发票的详细信息。", + fiber.Map{ + "invoice": invoiceResponse, + }, + ) +} + +// 获取指定商户下所有尚未开票的核算项目 +func getUninvoicedTenementReports(c *fiber.Ctx) error { + result := response.NewResult(c) + tenement := tools.EmptyToNil(c.Params("tid")) + invoiceLog.Info("获取指定商户下所有尚未开票的核算项目", zap.Stringp("Tenement", tenement)) + if tenement == nil { + invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,未指定商户。") + return result.BadRequest("未指定商户。") + } + session, err := _retreiveSession(c) + if err != nil { + invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,不能获取到有效的用户会话。", zap.Error(err)) + return result.Unauthorized("未能获取到有效的用户会话。") + } + pass, err := repository.TenementRepository.IsTenementBelongs(*tenement, session.Uid) + if err != nil { + invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,检查商户所属权时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "检查商户所属权时出现错误。") + } + if !pass { + invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,商户不属于当前用户。") + return result.Forbidden("不能访问不属于自己的商户。") + } + reports, err := repository.InvoiceRepository.ListUninvoicedTenementCharges(*tenement) + if err != nil { + invoiceLog.Error("获取指定商户下所有尚未开票的核算项目失败,检索核算项目时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "检索核算项目时出现错误。") + } + return result.Success( + "已经获取到指定商户下所有尚未开票的核算项目。", + fiber.Map{ + "records": reports, + }, + ) +} + +// 试计算指定发票的票面信息 +func testCalculateInvoice(c *fiber.Ctx) error { + result := response.NewResult(c) + var form vo.InvoiceCreationForm + if err := c.BodyParser(&form); err != nil { + invoiceLog.Error("试计算指定发票的票面信息失败,请求表单数据解析错误。", zap.Error(err)) + return result.BadRequest("请求表单数据解析错误。") + } + invoiceLog.Info("试计算指定发票的票面信息", zap.String("Park", form.Park), zap.String("Tenement", form.Tenement)) + if pass, err := checkParkBelongs(form.Park, invoiceLog, c, &result); err != nil || !pass { + return err + } + total, cargos, err := service.InvoiceService.TestCalculateInvoice(form.Park, form.Tenement, form.TaxMethod, form.TaxRate, form.Covers) + if err != nil { + invoiceLog.Error("试计算指定发票的票面信息失败,试计算发票时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "试计算发票时出现错误。") + } + return result.Success( + "已经计算出指定发票的票面信息。", + fiber.Map{ + "total": total, + "cargos": cargos, + }, + ) +} + +// 创建一个新的发票记录 +func createNewInvoiceRecord(c *fiber.Ctx) error { + result := response.NewResult(c) + var form vo.ExtendedInvoiceCreationForm + if err := c.BodyParser(&form); err != nil { + invoiceLog.Error("创建一个新的发票记录失败,请求表单数据解析错误。", zap.Error(err)) + return result.BadRequest("请求表单数据解析错误。") + } + invoiceLog.Info("创建一个新的发票记录", zap.String("Park", form.Park), zap.String("Tenement", form.Tenement)) + if pass, err := checkParkBelongs(form.Park, invoiceLog, c, &result); err != nil || !pass { + return err + } + err := service.InvoiceService.SaveInvoice(form.Park, form.Tenement, form.InvoiceNo, form.InvoiceType, form.TaxMethod, form.TaxRate, form.Covers) + if err != nil { + invoiceLog.Error("创建一个新的发票记录失败,保存发票时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "保存发票时出现错误。") + } + return result.Created("已经创建了一个新的发票记录。") +} + +// 删除指定的发票记录 +func deleteInvoiceRecord(c *fiber.Ctx) error { + result := response.NewResult(c) + invoiceNo := tools.EmptyToNil(c.Params("code")) + invoiceLog.Info("删除指定的发票记录", zap.Stringp("InvoiceNo", invoiceNo)) + if invoiceNo == nil { + invoiceLog.Error("删除指定的发票记录失败,未指定发票编号。") + return result.BadRequest("未指定发票编号。") + } + session, err := _retreiveSession(c) + if err != nil { + invoiceLog.Error("删除指定的发票记录失败,不能获取到有效的用户会话。", zap.Error(err)) + return result.Unauthorized("未能获取到有效的用户会话。") + } + pass, err := repository.InvoiceRepository.IsBelongsTo(*invoiceNo, session.Uid) + if err != nil { + invoiceLog.Error("删除指定的发票记录失败,检查发票所属权时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "检查发票所属权时出现错误。") + } + if !pass { + invoiceLog.Error("删除指定的发票记录失败,发票不属于当前用户。") + return result.Forbidden("不能删除不属于自己的发票。") + } + err = service.InvoiceService.DeleteInvoice(*invoiceNo) + if err != nil { + invoiceLog.Error("删除指定的发票记录失败,删除发票时出现错误。", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "删除发票时出现错误。") + } + return result.Success("已经删除了指定的发票记录。") +} diff --git a/router/router.go b/router/router.go index 02b8f2f..251260c 100644 --- a/router/router.go +++ b/router/router.go @@ -49,6 +49,7 @@ func App() *fiber.App { controller.InitializeChargeHandlers(app) controller.InitializeParkHandlers(app) controller.InitializeMeterHandlers(app) + controller.InitializeInvoiceHandler(app) return app } diff --git a/vo/invoice.go b/vo/invoice.go index e60a3fa..79015e3 100644 --- a/vo/invoice.go +++ b/vo/invoice.go @@ -25,3 +25,15 @@ type InvoiceCreationForm struct { TaxRate decimal.NullDecimal `json:"taxRate"` Covers []string `json:"covers"` } + +type ExtendedInvoiceResponse struct { + InvoiceResponse + Cargos []*model.InvoiceCargo `json:"cargos"` + Covers []*model.SimplifiedTenementCharge `json:"covers"` +} + +type ExtendedInvoiceCreationForm struct { + InvoiceCreationForm + InvoiceType *string `json:"invoiceType"` + InvoiceNo string `json:"invoiceNo"` +} From 234e811324e1159f37422ae1db84e50340eddf0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 19 Jun 2023 14:46:21 +0800 Subject: [PATCH 085/141] =?UTF-8?q?feat(topup):=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=95=86=E6=88=B7=E5=85=85=E5=80=BC=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=E5=BA=93=E6=93=8D=E4=BD=9C=E9=83=A8?= =?UTF-8?q?=E5=88=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/top_up.go | 22 +++++ repository/top_up.go | 186 +++++++++++++++++++++++++++++++++++++++++++ vo/top_up.go | 9 +++ 3 files changed, 217 insertions(+) create mode 100644 model/top_up.go create mode 100644 repository/top_up.go create mode 100644 vo/top_up.go diff --git a/model/top_up.go b/model/top_up.go new file mode 100644 index 0000000..64f2b35 --- /dev/null +++ b/model/top_up.go @@ -0,0 +1,22 @@ +package model + +import ( + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type TopUp struct { + TopUpCode string `json:"topUpCode" db:"top_up_code"` + Park string `json:"parkId" db:"park_id"` + Tenement string `json:"tenementId" db:"tenement_id"` + TenementName string `json:"tenementName" db:"tenement_name"` + Meter string `json:"meterId" db:"meter_id"` + MeterAddress *string `json:"meterAddress" db:"meter_address"` + ToppedUpAt types.DateTime `json:"toppedUpAt" db:"topped_up_at"` + Amount decimal.Decimal `json:"amount" db:"amount"` + PaymentType int16 `json:"paymentType" db:"payment_type"` + SuccessfulSynchronized bool `json:"successfulSynchronized" db:"successful_synchronized"` + SynchronizedAt *types.DateTime `json:"synchronizedAt" db:"synchronized_at"` + CancelledAt *types.DateTime `json:"cancelledAt" db:"cancelled_at"` +} diff --git a/repository/top_up.go b/repository/top_up.go new file mode 100644 index 0000000..83b87cf --- /dev/null +++ b/repository/top_up.go @@ -0,0 +1,186 @@ +package repository + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/config" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/tools/serial" + "electricity_bill_calc/types" + "electricity_bill_calc/vo" + "fmt" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _TopUpRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var TopUpRepository = _TopUpRepository{ + log: logger.Named("Repository", "TopUp"), + ds: goqu.Dialect("postgres"), +} + +// 检索符合条件的商户充值记录 +func (tur _TopUpRepository) ListTopUps(pid string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.TopUp, int64, error) { + tur.log.Info("查询符合条件的商户充值记录", zap.String("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("keyword", keyword), zap.Uint("page", page)) + cacheConditions := []string{ + pid, + cache.NullableStringKey(keyword), + cache.NullableConditionKey(startDate), + cache.NullableConditionKey(endDate), + fmt.Sprintf("%d", page), + } + if topUps, total, err := cache.RetrievePagedSearch[[]*model.TopUp]("top_ups", cacheConditions...); err == nil && topUps != nil && len(*topUps) > 0 { + tur.log.Info("从缓存中获取到商户充值记录", zap.Int("Count", len(*topUps)), zap.Int64("Total", total)) + return *topUps, total, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + topUpQuery := tur.ds. + From(goqu.T("tenement_top_ups").As("t")). + LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))). + LeftJoin(goqu.T("meter").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))). + Select("t.*", goqu.I("te.full_name").As("tenement_name"), goqu.I("m.address").As("meter_address")). + Where(goqu.I("t.park_id").Eq(pid)) + countQuery := tur.ds. + From(goqu.T("tenement_top_ups").As("t")). + LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))). + LeftJoin(goqu.T("meter").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))). + Select(goqu.COUNT("t.*")). + Where(goqu.I("t.park_id").Eq(pid)) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + topUpQuery = topUpQuery.Where(goqu.Or( + goqu.I("te.full_name").ILike(pattern), + goqu.I("te.short_name").ILike(pattern), + goqu.I("te.abbr").ILike(pattern), + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("te.full_name").ILike(pattern), + goqu.I("te.short_name").ILike(pattern), + goqu.I("te.abbr").ILike(pattern), + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + )) + } + + if startDate != nil { + topUpQuery = topUpQuery.Where(goqu.I("t.topped_up_at").Gte(startDate.ToBeginningOfDate())) + countQuery = countQuery.Where(goqu.I("t.topped_up_at").Gte(startDate.ToBeginningOfDate())) + } + + if endDate != nil { + topUpQuery = topUpQuery.Where(goqu.I("t.topped_up_at").Lte(endDate.ToEndingOfDate())) + countQuery = countQuery.Where(goqu.I("t.topped_up_at").Lte(endDate.ToEndingOfDate())) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + topUpQuery = topUpQuery.Order(goqu.I("t.topped_up_at").Desc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + topUpSql, topUpArgs, _ := topUpQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + + var ( + topUps []*model.TopUp = make([]*model.TopUp, 0) + total int64 = 0 + ) + if err := pgxscan.Select(ctx, global.DB, &topUps, topUpSql, topUpArgs...); err != nil { + tur.log.Error("查询商户充值记录失败", zap.Error(err)) + return topUps, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { + tur.log.Error("查询商户充值记录总数失败", zap.Error(err)) + return topUps, 0, err + } + cache.CachePagedSearch(topUps, total, []string{fmt.Sprintf("top_up:%s", pid), fmt.Sprintf("tenement:%s", pid), fmt.Sprintf("meter:%s", pid)}, "top_ups", cacheConditions...) + return topUps, total, nil +} + +// 取得一个充值记录的详细信息 +func (tur _TopUpRepository) GetTopUp(pid, topUpCode string) (*model.TopUp, error) { + tur.log.Info("查询充值记录", zap.String("Park", pid), zap.String("TopUpCode", topUpCode)) + if topUp, err := cache.RetrieveEntity[model.TopUp]("top_up", topUpCode); err == nil && topUp != nil { + tur.log.Info("从缓存中获取到充值记录") + return topUp, err + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + topUpSql, topUpArgs, _ := tur.ds. + From(goqu.T("tenement_top_ups").As("t")). + LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))). + LeftJoin(goqu.T("meter").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))). + Select("t.*", goqu.I("te.full_name").As("tenement_name"), goqu.I("m.address").As("meter_address")). + Where(goqu.I("t.park_id").Eq(pid), goqu.I("t.top_up_code").Eq(topUpCode)). + Prepared(true).ToSQL() + + var topUp model.TopUp + if err := pgxscan.Get(ctx, global.DB, &topUp, topUpSql, topUpArgs...); err != nil { + tur.log.Error("查询充值记录失败", zap.Error(err)) + return nil, err + } + cache.CacheEntity(&topUp, []string{fmt.Sprintf("top_up:%s", pid), fmt.Sprintf("tenement:%s", pid), fmt.Sprintf("meter:%s", pid)}, "top_up", topUpCode) + return &topUp, nil +} + +// 创建一条新的充值记录 +func (tur _TopUpRepository) CreateTopUp(pid string, form *vo.TopUpCreationForm) error { + tur.log.Info("创建一条新的充值记录", zap.String("Park", pid), zap.String("Tenement", form.Tenement), zap.String("Meter", form.Meter)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + serial.StringSerialRequestChan <- 1 + topUpCode := serial.Prefix("U", <-serial.StringSerialResponseChan) + topUpTime := types.Now() + topUpSql, topUpArgs, _ := tur.ds. + Insert("tenement_top_ups"). + Cols("top_up_code", "park_id", "tenement_id", "meter_id", "topped_up_at", "amount", "payment_type"). + Vals(goqu.Vals{ + topUpCode, + pid, + form.Tenement, + form.Meter, + topUpTime, + form.Amount, + model.PAYMENT_CASH, + }). + Prepared(true).ToSQL() + + if _, err := global.DB.Exec(ctx, topUpSql, topUpArgs...); err != nil { + tur.log.Error("创建充值记录失败", zap.Error(err)) + return err + } + cache.AbolishRelation(fmt.Sprintf("top_ups:%s", pid)) + return nil +} + +// 删除一条充值记录 +func (tur _TopUpRepository) DeleteTopUp(pid, topUpCode string) error { + tur.log.Info("删除一条充值记录", zap.String("Park", pid), zap.String("TopUpCode", topUpCode)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + topUpSql, topUpArgs, _ := tur.ds. + Update("tenement_top_ups"). + Set(goqu.Record{"cancelled_at": types.Now()}). + Where(goqu.I("park_id").Eq(pid), goqu.I("top_up_code").Eq(topUpCode)). + Prepared(true).ToSQL() + + if _, err := global.DB.Exec(ctx, topUpSql, topUpArgs...); err != nil { + tur.log.Error("删除充值记录失败", zap.Error(err)) + return err + } + cache.AbolishRelation(fmt.Sprintf("top_ups:%s", pid)) + return nil +} diff --git a/vo/top_up.go b/vo/top_up.go new file mode 100644 index 0000000..fb7f01f --- /dev/null +++ b/vo/top_up.go @@ -0,0 +1,9 @@ +package vo + +import "github.com/shopspring/decimal" + +type TopUpCreationForm struct { + Tenement string `json:"tenement"` + Meter string `json:"meter"` + Amount decimal.Decimal `json:"amount"` +} From 2b5272dad91ac5c24179f75034935d5721408cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 19 Jun 2023 15:49:16 +0800 Subject: [PATCH 086/141] =?UTF-8?q?feat(topup):=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=95=86=E6=88=B7=E5=85=85=E5=80=BC=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=BE=85=E6=B5=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/top_up.go | 140 +++++++++++++++++++++++++++++++++++++++++++ model/top_up.go | 11 ++++ router/router.go | 1 + vo/top_up.go | 12 ++++ 4 files changed, 164 insertions(+) create mode 100644 controller/top_up.go diff --git a/controller/top_up.go b/controller/top_up.go new file mode 100644 index 0000000..092d62a --- /dev/null +++ b/controller/top_up.go @@ -0,0 +1,140 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "electricity_bill_calc/repository" + "electricity_bill_calc/response" + "electricity_bill_calc/security" + "electricity_bill_calc/tools" + "electricity_bill_calc/types" + "electricity_bill_calc/vo" + + "github.com/gofiber/fiber/v2" + "github.com/jinzhu/copier" + "go.uber.org/zap" +) + +var topUpLog = logger.Named("Controller", "TopUp") + +func InitializeTopUpHandlers(router *fiber.App) { + router.Get("/topup/:pid", security.EnterpriseAuthorize, listTopUps) + router.Post("/topup/:pid", security.EnterpriseAuthorize, createTopUp) + router.Get("/topup/:pid/:code", security.EnterpriseAuthorize, getTopUp) + router.Delete("/topup/:pid/:code", security.EnterpriseAuthorize, deleteTopUp) +} + +// 查询符合条件的商户充值记录 +func listTopUps(c *fiber.Ctx) error { + result := response.NewResult(c) + park := tools.EmptyToNil(c.Params("pid")) + if park == nil { + topUpLog.Error("查询符合条件的商户充值记录,未指定要访问的园区") + return result.BadRequest("未指定要访问的园区") + } + if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass { + return err + } + keyword := tools.EmptyToNil(c.Query("keyword")) + startDate, err := types.ParseDatep(c.Query("start_date")) + if err != nil { + topUpLog.Error("查询符合条件的商户充值记录,查询起始日期格式错误", zap.Error(err)) + return result.BadRequest("查询起始日期格式错误") + } + endDate, err := types.ParseDatep(c.Query("end_date")) + if err != nil { + topUpLog.Error("查询符合条件的商户充值记录,查询结束日期格式错误", zap.Error(err)) + return result.BadRequest("查询结束日期格式错误") + } + page := c.QueryInt("page", 1) + topUps, total, err := repository.TopUpRepository.ListTopUps(*park, startDate, endDate, keyword, uint(page)) + if err != nil { + topUpLog.Error("查询符合条件的商户充值记录,查询失败", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "商户充值记录查询不成功") + } + var topUpDetails []*vo.TopUpDetailQueryResponse + copier.Copy(&topUpDetails, &topUps) + return result.Success( + "已经获取到符合条件的商户充值记录", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{"topUps": topUpDetails}, + ) +} + +// 获取指定充值记录的详细内容 +func getTopUp(c *fiber.Ctx) error { + result := response.NewResult(c) + park := tools.EmptyToNil(c.Params("pid")) + if park == nil { + topUpLog.Error("获取指定充值记录的详细内容,未指定要访问的园区") + return result.BadRequest("未指定要访问的园区") + } + if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass { + return err + } + topUpCode := tools.EmptyToNil(c.Params("code")) + if topUpCode == nil { + topUpLog.Error("获取指定充值记录的详细内容,未指定要查询的充值记录") + return result.BadRequest("未指定要查询的充值记录") + } + topUp, err := repository.TopUpRepository.GetTopUp(*park, *topUpCode) + if err != nil { + topUpLog.Error("获取指定充值记录的详细内容,查询失败", zap.Error(err)) + return result.NotFound("未找到指定的商户充值记录") + } + var topUpDetail vo.TopUpDetailQueryResponse + copier.Copy(&topUpDetail, &topUp) + return result.Success( + "已经获取到指定充值记录的详细内容", + fiber.Map{"topup": topUpDetail}, + ) +} + +// 创建一条新的商户充值记录 +func createTopUp(c *fiber.Ctx) error { + result := response.NewResult(c) + park := tools.EmptyToNil(c.Params("pid")) + if park == nil { + topUpLog.Error("创建一条新的商户充值记录,未指定要访问的园区") + return result.BadRequest("未指定要访问的园区") + } + if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass { + return err + } + var form vo.TopUpCreationForm + if err := c.BodyParser(&form); err != nil { + topUpLog.Error("创建一条新的商户充值记录,请求体解析失败", zap.Error(err)) + return result.BadRequest("请求体解析失败") + } + if err := repository.TopUpRepository.CreateTopUp(*park, &form); err != nil { + topUpLog.Error("创建一条新的商户充值记录,创建失败", zap.Error(err)) + return result.NotAccept("商户充值记录创建不成功") + } + return result.Created( + "已经创建一条新的商户充值记录", + ) +} + +// 删除一条指定的商户充值记录 +func deleteTopUp(c *fiber.Ctx) error { + result := response.NewResult(c) + park := tools.EmptyToNil(c.Params("pid")) + if park == nil { + topUpLog.Error("删除一条指定的商户充值记录,未指定要访问的园区") + return result.BadRequest("未指定要访问的园区") + } + if pass, err := checkParkBelongs(*park, topUpLog, c, &result); !pass { + return err + } + topUpCode := tools.EmptyToNil(c.Params("code")) + if topUpCode == nil { + topUpLog.Error("删除一条指定的商户充值记录,未指定要删除的充值记录") + return result.BadRequest("未指定要删除的充值记录") + } + if err := repository.TopUpRepository.DeleteTopUp(*park, *topUpCode); err != nil { + topUpLog.Error("删除一条指定的商户充值记录,删除失败", zap.Error(err)) + return result.NotAccept("商户充值记录删除不成功") + } + return result.Deleted( + "已经删除一条指定的商户充值记录", + ) +} diff --git a/model/top_up.go b/model/top_up.go index 64f2b35..fe4c1c1 100644 --- a/model/top_up.go +++ b/model/top_up.go @@ -20,3 +20,14 @@ type TopUp struct { SynchronizedAt *types.DateTime `json:"synchronizedAt" db:"synchronized_at"` CancelledAt *types.DateTime `json:"cancelledAt" db:"cancelled_at"` } + +func (t TopUp) SyncStatus() int16 { + switch { + case t.SuccessfulSynchronized && t.SynchronizedAt != nil: + return 1 + case !t.SuccessfulSynchronized && t.SynchronizedAt != nil: + return 2 + default: + return 0 + } +} diff --git a/router/router.go b/router/router.go index 251260c..ac51adb 100644 --- a/router/router.go +++ b/router/router.go @@ -50,6 +50,7 @@ func App() *fiber.App { controller.InitializeParkHandlers(app) controller.InitializeMeterHandlers(app) controller.InitializeInvoiceHandler(app) + controller.InitializeTopUpHandlers(app) return app } diff --git a/vo/top_up.go b/vo/top_up.go index fb7f01f..c1c8f39 100644 --- a/vo/top_up.go +++ b/vo/top_up.go @@ -7,3 +7,15 @@ type TopUpCreationForm struct { Meter string `json:"meter"` Amount decimal.Decimal `json:"amount"` } + +type TopUpDetailQueryResponse struct { + Id string `json:"id" copier:"topUpCode"` + Tenement string `json:"tenement"` + TenementName string `json:"tenementName"` + Meter string `json:"meter"` + MeterAddress string `json:"meterAddress"` + ToppedUpAt string `json:"toppedUpAt"` + Amount decimal.Decimal `json:"amount"` + PaymentType int16 `json:"paymentType"` + SyncStatus int16 `json:"syncStatus" copier:"SyncStatus"` +} From 316553d81a30fd9e85d816002d30695395b5540b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 19 Jun 2023 17:30:17 +0800 Subject: [PATCH 087/141] =?UTF-8?q?enhance(report):=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E6=95=B0=E6=8D=AE=E5=BA=93=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E6=96=87=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/calculate/calculate.go | 92 ++++++++++++++++++++++++++++++++++++ model/enums.go | 7 +++ repository/report.go | 19 ++++++++ vo/report.go | 1 + 4 files changed, 119 insertions(+) create mode 100644 model/calculate/calculate.go create mode 100644 repository/report.go create mode 100644 vo/report.go diff --git a/model/calculate/calculate.go b/model/calculate/calculate.go new file mode 100644 index 0000000..da5177c --- /dev/null +++ b/model/calculate/calculate.go @@ -0,0 +1,92 @@ +package calculate + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type Reading struct { + ReadAt types.DateTime + Ratio decimal.Decimal + Overall decimal.Decimal + Critical decimal.Decimal + Peak decimal.Decimal + Flat decimal.Decimal + Valley decimal.Decimal +} + +type Pooling struct { + Code string + Detail model.ConsumptionUnit +} + +type Meter struct { + Code string + Detail model.MeterDetail + CoveredArea decimal.Decimal + LastTermReading *Reading + CurrentTermReading *Reading + Overall model.ConsumptionUnit + Critical model.ConsumptionUnit + Peak model.ConsumptionUnit + Flat model.ConsumptionUnit + Valley model.ConsumptionUnit + AdjustLoss model.ConsumptionUnit + PooledBasic model.ConsumptionUnit + PooledAdjust model.ConsumptionUnit + PooledLoss model.ConsumptionUnit + PooledPublic model.ConsumptionUnit + SharedPoolingProportion decimal.Decimal + Poolings []*Pooling +} + +type TenementCharge struct { + Tenement string + Overall model.ConsumptionUnit + Critical model.ConsumptionUnit + Peak model.ConsumptionUnit + Flat model.ConsumptionUnit + Valley model.ConsumptionUnit + BasicFee decimal.Decimal + AdjustFee decimal.Decimal + LossPooled decimal.Decimal + PublicPooled decimal.Decimal + FinalCharges decimal.Decimal + Submeters []*Meter + Poolings []*Meter +} + +type Summary struct { + ReportId string + OverallArea decimal.Decimal + Overall model.ConsumptionUnit + ConsumptionFee decimal.Decimal + Critical model.ConsumptionUnit + Peak model.ConsumptionUnit + Flat model.ConsumptionUnit + Valley model.ConsumptionUnit + Loss decimal.Decimal + LossFee decimal.Decimal + LossProportion decimal.Decimal + AuthoizeLoss model.ConsumptionUnit + BasicFee decimal.Decimal + BasicPooledPriceConsumption decimal.Decimal + BasicPooledPriceArea decimal.Decimal + AdjustFee decimal.Decimal + AdjustPooledPriceConsumption decimal.Decimal + AdjustPooledPriceArea decimal.Decimal + LossDilutedPrice decimal.Decimal + TotalConsumption decimal.Decimal + FinalDilutedOverall decimal.Decimal +} + +type PoolingSummary struct { + Tenement string + Meter string + TargetMeter string + Area decimal.NullDecimal + OverallAmount decimal.Decimal + PoolingProportion decimal.Decimal +} diff --git a/model/enums.go b/model/enums.go index 18eb8fe..79b5225 100644 --- a/model/enums.go +++ b/model/enums.go @@ -81,3 +81,10 @@ const ( REPORT_CALCULATE_TASK_STATUS_UNKNOWN_ERROR REPORT_CALCULATE_TASK_STATUS_UNEXISTS = 99 ) + +const ( + REPORT_WITHDRAW_NON int16 = iota + REPORT_WITHDRAW_APPLYING + REPORT_WITHDRAW_DENIED + REPORT_WITHDRAW_GRANTED +) diff --git a/repository/report.go b/repository/report.go new file mode 100644 index 0000000..4e251d6 --- /dev/null +++ b/repository/report.go @@ -0,0 +1,19 @@ +package repository + +import ( + "electricity_bill_calc/logger" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "go.uber.org/zap" +) + +type _ReportRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var ReportRepository = _ReportRepository{ + log: logger.Named("Repository", "Report"), + ds: goqu.Dialect("postgres"), +} diff --git a/vo/report.go b/vo/report.go new file mode 100644 index 0000000..d685e69 --- /dev/null +++ b/vo/report.go @@ -0,0 +1 @@ +package vo From 0d73665313ef52446a9777a66f0cbbd5ec667084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 19 Jun 2023 20:56:31 +0800 Subject: [PATCH 088/141] =?UTF-8?q?refactor(cache):=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E9=99=8D=E7=BA=A7=EF=BC=8C=E5=81=9C=E7=94=A8?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E6=95=B0=E6=8D=AE=E7=BC=93=E5=AD=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/charge.go | 13 ----- repository/invoice.go | 55 ------------------ repository/meter.go | 129 ----------------------------------------- repository/park.go | 102 -------------------------------- repository/region.go | 32 ---------- repository/tenement.go | 61 ------------------- repository/top_up.go | 20 ------- repository/user.go | 82 -------------------------- service/charge.go | 5 -- service/meter.go | 21 ------- service/tenement.go | 9 --- 11 files changed, 529 deletions(-) diff --git a/repository/charge.go b/repository/charge.go index 573badb..3800217 100644 --- a/repository/charge.go +++ b/repository/charge.go @@ -2,12 +2,10 @@ package repository import ( "context" - "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" - "electricity_bill_calc/tools" "electricity_bill_calc/types" "fmt" @@ -32,16 +30,6 @@ var ChargeRepository = &_ChargeRepository{ // 分页查询用户的充值记录 func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Date, keyword *string) ([]*model.UserChargeDetail, int64, error) { cr.log.Info("查询用户的充值记录。", logger.DateFieldp("beginTime", beginTime), logger.DateFieldp("endTime", endTime), zap.Stringp("keyword", keyword), zap.Uint("page", page)) - cacheConditions := []string{ - fmt.Sprintf("%d", page), - tools.DefaultTo(keyword, ""), - tools.CondFn(func(t *types.Date) bool { return t != nil }, beginTime, beginTime.Format("2006-01-02"), "UNDEF"), - tools.CondFn(func(t *types.Date) bool { return t != nil }, endTime, endTime.Format("2006-01-02"), "UNDEF"), - } - if charges, total, err := cache.RetrievePagedSearch[[]*model.UserChargeDetail]("charges", cacheConditions...); err == nil && charges != nil { - cr.log.Info("从缓存中获取用户的充值记录成功。", zap.Int("count", len(*charges)), zap.Int64("total", total)) - return *charges, total, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -103,7 +91,6 @@ func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Dat cr.log.Error("查询用户的充值记录总数失败。", zap.Error(err)) return make([]*model.UserChargeDetail, 0), 0, err } - cache.CachePagedSearch(charges, total, []string{"charges"}, "charges", cacheConditions...) return charges, total, nil } diff --git a/repository/invoice.go b/repository/invoice.go index 413e726..21a5505 100644 --- a/repository/invoice.go +++ b/repository/invoice.go @@ -2,7 +2,6 @@ package repository import ( "context" - "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" @@ -10,7 +9,6 @@ import ( "electricity_bill_calc/types" "errors" "fmt" - "strings" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" @@ -33,17 +31,6 @@ var InvoiceRepository = _InvoiceRepository{ // 查询指定园区中符合条件的发票 func (ir _InvoiceRepository) ListInvoice(pid *string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.Invoice, int64, error) { ir.log.Info("查询指定园区的发票。", zap.Stringp("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("Keyword", keyword), zap.Uint("Page", page)) - cacheCondition := []string{ - cache.NullableStringKey(pid), - fmt.Sprintf("%d", page), - cache.NullableStringKey(keyword), - cache.NullableConditionKey(startDate), - cache.NullableConditionKey(endDate), - } - if invoices, total, err := cache.RetrievePagedSearch[[]*model.Invoice]("invoice", cacheCondition...); err != nil && invoices != nil && len(*invoices) > 0 { - ir.log.Info("从缓存中获取到了符合条件的发票记录。", zap.Int("Count", len(*invoices)), zap.Int64("Total", total)) - return *invoices, total, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -112,26 +99,12 @@ func (ir _InvoiceRepository) ListInvoice(pid *string, startDate, endDate *types. ir.log.Error("查询发票记录数失败。", zap.Error(err)) return invoices, 0, err } - var relationName string - if pid != nil && len(*pid) > 0 { - relationName = fmt.Sprintf("invoice:%s", *pid) - } else { - relationName = "invoice" - } - cache.CachePagedSearch(invoices, total, []string{relationName}, "invoice", cacheCondition...) return invoices, total, nil } // 查询指定商户未开票的核算记录,改记录将只包括商户整体核算,不包括商户各个表计的详细 func (ir _InvoiceRepository) ListUninvoicedTenementCharges(tid string) ([]*model.SimplifiedTenementCharge, error) { ir.log.Info("查询指定商户的未开票核算记录", zap.String("Tenement", tid)) - cacheConditions := []string{ - tid, - } - if records, err := cache.RetrieveSearch[[]*model.SimplifiedTenementCharge]("uninvoiced_tenement_charge", cacheConditions...); err == nil && records != nil && len(*records) > 0 { - ir.log.Info("从缓存中获取到了符合条件的未开票核算记录。", zap.Int("Count", len(*records))) - return *records, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -154,11 +127,6 @@ func (ir _InvoiceRepository) ListUninvoicedTenementCharges(tid string) ([]*model ir.log.Error("查询未开票核算记录失败。", zap.Error(err)) return charges, err } - cache.CacheSearch(charges, []string{fmt.Sprintf("uninvoiced_tenement_charge:%s", tid)}, "uninvoiced_tenement_charge", cacheConditions...) - searchKey := cache.AssembleSearchKey("uninvoiced_tenement_charge", cacheConditions...) - for _, charge := range charges { - cache.CacheRelation(fmt.Sprintf("report:%s", charge.ReportId), cache.STORE_TYPE_KEY, searchKey) - } return charges, nil } @@ -186,10 +154,6 @@ func (ir _InvoiceRepository) UpdateTenementInvoicedState(tx pgx.Tx, ctx context. // 查询指定发票的详细记录信息 func (ir _InvoiceRepository) GetInvoiceDetail(invoiceNo string) (*model.Invoice, error) { ir.log.Info("查询指定发票的详细信息", zap.String("InvoiceNo", invoiceNo)) - if invoice, err := cache.RetrieveEntity[model.Invoice]("invoice", invoiceNo); err == nil && invoice != nil { - ir.log.Info("从缓存中获取到了符合条件的发票记录。") - return invoice, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -203,21 +167,12 @@ func (ir _InvoiceRepository) GetInvoiceDetail(invoiceNo string) (*model.Invoice, ir.log.Error("查询发票记录失败。", zap.Error(err)) return nil, err } - cache.CacheEntity(invoice, []string{fmt.Sprintf("invoice:%s", invoiceNo)}, "invoice", invoiceNo) return &invoice, nil } // 获取指定商户的简化核算记录 func (ir _InvoiceRepository) GetSimplifiedTenementCharges(tid string, rids []string) ([]*model.SimplifiedTenementCharge, error) { ir.log.Info("查询庄园商户的简化核算记录", zap.String("Tenement", tid), zap.Strings("Reports", rids)) - cacheConditions := []string{ - tid, - strings.Join(rids, ":"), - } - if records, err := cache.RetrieveSearch[[]*model.SimplifiedTenementCharge]("simplified_tenement_charge", cacheConditions...); err == nil && records != nil && len(*records) > 0 { - ir.log.Info("从缓存中获取到了符合条件的简化核算记录。", zap.Int("Count", len(*records))) - return *records, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -240,11 +195,6 @@ func (ir _InvoiceRepository) GetSimplifiedTenementCharges(tid string, rids []str ir.log.Error("查询简化核算记录失败。", zap.Error(err)) return charges, err } - cache.CacheSearch(charges, []string{fmt.Sprintf("tenement:%s", tid)}, "simplified_tenement_charge", cacheConditions...) - searchKey := cache.AssembleSearchKey("simplified_tenement_charge", cacheConditions...) - for _, charge := range charges { - cache.CacheRelation(fmt.Sprintf("report:%s", charge.ReportId), cache.STORE_TYPE_KEY, searchKey) - } return charges, nil } @@ -391,10 +341,5 @@ func (ir _InvoiceRepository) Create(pid, tid, invoiceNo string, invoiceType *str tx.Rollback(ctx) return err } - for _, rid := range *covers { - cache.AbolishRelation(fmt.Sprintf("report:%s", rid)) - } - cache.AbolishRelation(fmt.Sprintf("invoice:%s", pid)) - cache.AbolishRelation("invoice") return nil } diff --git a/repository/meter.go b/repository/meter.go index 9241ea7..95c11fa 100644 --- a/repository/meter.go +++ b/repository/meter.go @@ -35,11 +35,6 @@ var MeterRepository = _MeterRepository{ // 获取指定园区中所有的表计信息 func (mr _MeterRepository) AllMeters(pid string) ([]*model.MeterDetail, error) { mr.log.Info("列出指定园区中的所有表计", zap.String("park id", pid)) - cacheConditions := []string{pid} - if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("all_meters_in", cacheConditions...); err == nil { - mr.log.Info("从缓存中获取到了指定园区中的表计信息", zap.Int("count", len(*meters))) - return *meters, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -62,19 +57,12 @@ func (mr _MeterRepository) AllMeters(pid string) ([]*model.MeterDetail, error) { return make([]*model.MeterDetail, 0), err } - cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", pid)}, "all_meters_in", cacheConditions...) - return meters, nil } // 列出指定园区下的所有表计信息,包含已经拆除的表计 func (mr _MeterRepository) AllUsedMeters(pid string) ([]*model.MeterDetail, error) { mr.log.Info("列出指定园区中的所有使用过的表计", zap.String("park id", pid)) - cacheConditions := []string{pid} - if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("all_used_meters_in", cacheConditions...); err == nil { - mr.log.Info("从缓存中获取到了指定园区中的表计信息", zap.Int("count", len(*meters))) - return *meters, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -96,8 +84,6 @@ func (mr _MeterRepository) AllUsedMeters(pid string) ([]*model.MeterDetail, erro return make([]*model.MeterDetail, 0), err } - cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", pid)}, "all_used_meters_in", cacheConditions...) - return meters, nil } @@ -138,15 +124,6 @@ func (mr _MeterRepository) AllUsedMetersInReport(rid string) ([]*model.MeterDeta // 分页列出指定园区下的表计信息 func (mr _MeterRepository) MetersIn(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) { mr.log.Info("分页列出指定园区下的表计信息", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, ""))) - cacheConditions := []string{ - pid, - tools.DefaultOrEmptyStr(keyword, "UNDEF"), - fmt.Sprintf("%d", page), - } - if meters, total, err := cache.RetrievePagedSearch[[]*model.MeterDetail]("meter", cacheConditions...); err == nil { - mr.log.Info("从缓存中获取到了指定园区中的表计信息", zap.Int("count", len(*meters)), zap.Int64("total", total)) - return *meters, total, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -203,22 +180,12 @@ func (mr _MeterRepository) MetersIn(pid string, page uint, keyword *string) ([]* return make([]*model.MeterDetail, 0), 0, err } - cache.CachePagedSearch(meters, total, []string{fmt.Sprintf("meter:%s", pid)}, "meter", cacheConditions...) - return meters, total, nil } // 列出指定园区中指定列表中所有表计的详细信息,将忽略所有表计的当前状态 func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.MeterDetail, error) { mr.log.Info("列出指定园区中指定列表中所有表计的详细信息", zap.String("park id", pid), zap.Strings("meter ids", ids)) - cacheConditions := []string{ - pid, - strings.Join(ids, ","), - } - if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("meter_slice", cacheConditions...); err == nil && meters != nil { - mr.log.Info("从缓存中获取到了指定园区中所需的表计信息", zap.Int("count", len(*meters))) - return *meters, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -241,19 +208,12 @@ func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.M return make([]*model.MeterDetail, 0), err } - cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", pid), fmt.Sprintf("meter_slice:%s", pid)}, "meter_slice", cacheConditions...) - return meters, nil } // 获取指定表计的详细信息 func (mr _MeterRepository) FetchMeterDetail(pid, code string) (*model.MeterDetail, error) { mr.log.Info("获取指定表计的详细信息", zap.String("park id", pid), zap.String("meter code", code)) - cacheConditions := fmt.Sprintf("%s:%s", pid, code) - if meter, err := cache.RetrieveEntity[*model.MeterDetail]("meter", cacheConditions); err == nil { - mr.log.Info("从缓存中获取到了指定表计的详细信息", zap.String("code", (**meter).Code)) - return *meter, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -275,8 +235,6 @@ func (mr _MeterRepository) FetchMeterDetail(pid, code string) (*model.MeterDetai return nil, err } - cache.CacheEntity(meter, []string{fmt.Sprintf("meter:%s", pid), "park"}, "meter", cacheConditions) - return &meter, nil } @@ -302,12 +260,6 @@ func (mr _MeterRepository) CreateMeter(tx pgx.Tx, ctx context.Context, pid strin mr.log.Error("创建表计信息失败", zap.Error(err)) return false, err } - - if ok.RowsAffected() > 0 { - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) - cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, meter.Code)) - } - return ok.RowsAffected() > 0, nil } @@ -368,10 +320,6 @@ func (mr _MeterRepository) RecordReading(tx pgx.Tx, ctx context.Context, pid, co return false, err } - if ok.RowsAffected() > 0 { - cache.AbolishRelation(fmt.Sprintf("meter_reading:%s", pid)) - } - return ok.RowsAffected() > 0, nil } @@ -406,10 +354,6 @@ func (mr _MeterRepository) UpdateMeter(tx pgx.Tx, ctx context.Context, pid, code return false, err } - if ok.RowsAffected() > 0 { - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) - } - return ok.RowsAffected() > 0, nil } @@ -439,8 +383,6 @@ func (mr _MeterRepository) ListMeterCodes(pid string) ([]string, error) { return make([]string, 0), err } - cache.CacheSearch(codes, []string{fmt.Sprintf("meter:%s", pid), fmt.Sprintf("park:%s", pid)}, "meter_codes", cacheConditions...) - return codes, nil } @@ -468,11 +410,6 @@ func (mr _MeterRepository) DetachMeter(tx pgx.Tx, ctx context.Context, pid, code return false, err } - if ok.RowsAffected() > 0 { - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) - cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, code)) - } - return ok.RowsAffected() > 0, nil } @@ -523,10 +460,6 @@ func (mr _MeterRepository) BindMeter(tx pgx.Tx, ctx context.Context, pid, master return false, err } - if ok.RowsAffected() > 0 { - cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, masterMeter)) - } - return ok.RowsAffected() > 0, nil } @@ -554,10 +487,6 @@ func (mr _MeterRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, mast return false, err } - if ok.RowsAffected() > 0 { - cache.AbolishRelation(fmt.Sprintf("meter_relations:%s:%s", pid, masterMeter)) - } - return ok.RowsAffected() > 0, nil } @@ -647,15 +576,6 @@ func (mr _MeterRepository) ListMeterRelations(pid, code string) ([]*model.MeterR // 列出指定园区中的所有公摊表计 func (mr _MeterRepository) ListPoolingMeters(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) { mr.log.Info("列出指定园区中的所有公摊表计", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, ""))) - cacheConditions := []string{ - pid, - tools.DefaultOrEmptyStr(keyword, "UNDEF"), - fmt.Sprintf("%d", page), - } - if meters, total, err := cache.RetrievePagedSearch[[]*model.MeterDetail]("pooling_meters", cacheConditions...); err == nil { - mr.log.Info("从缓存中获取到了指定园区中的公摊表计信息", zap.Int("count", len(*meters)), zap.Int64("total", total)) - return *meters, total, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -714,23 +634,12 @@ func (mr _MeterRepository) ListPoolingMeters(pid string, page uint, keyword *str return make([]*model.MeterDetail, 0), 0, err } - cache.CachePagedSearch(meters, total, []string{fmt.Sprintf("meter:%s", pid), "park"}, "pooling_meters", cacheConditions...) - return meters, total, nil } // 列出目前尚未绑定到公摊表计的商户表计 func (mr _MeterRepository) ListUnboundMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) { mr.log.Info("列出目前尚未绑定到公摊表计的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0))) - cacheConditions := []string{ - tools.DefaultTo(pid, "UNDEF"), - tools.DefaultOrEmptyStr(keyword, "UNDEF"), - tools.DefaultStrTo("%d", limit, "0"), - } - if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("unbound_pooling_meters", cacheConditions...); err == nil { - mr.log.Info("从缓存中获取到了指定园区中的商户表计信息", zap.Int("count", len(*meters))) - return *meters, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -796,23 +705,12 @@ func (mr _MeterRepository) ListUnboundMeters(uid string, pid *string, keyword *s return make([]*model.MeterDetail, 0), err } - cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", tools.DefaultTo(pid, "ALL")), "park"}, "unbound_pooling_meters", cacheConditions...) - return meters, nil } // 列出目前未绑定到商户的商户表计 func (mr _MeterRepository) ListUnboundTenementMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) { mr.log.Info("列出目前未绑定到商户的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0))) - cacheConditions := []string{ - tools.DefaultTo(pid, "UNDEF"), - tools.DefaultOrEmptyStr(keyword, "UNDEF"), - tools.DefaultStrTo("%d", limit, "0"), - } - if meters, err := cache.RetrieveSearch[[]*model.MeterDetail]("unbound_tenement_meters", cacheConditions...); err == nil { - mr.log.Info("从缓存中获取到了指定园区中的商户表计信息", zap.Int("count", len(*meters))) - return *meters, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -878,26 +776,12 @@ func (mr _MeterRepository) ListUnboundTenementMeters(uid string, pid *string, ke return make([]*model.MeterDetail, 0), err } - cache.CacheSearch(meters, []string{fmt.Sprintf("meter:%s", tools.DefaultTo(pid, "ALL")), "park"}, "unbound_tenement_meters", cacheConditions...) - return meters, nil } // 查询指定园区中的符合条件的抄表记录 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), logger.DateFieldp("start", start), logger.DateFieldp("end", end), zap.String("building", tools.DefaultTo(buidling, ""))) - cacheConditions := []string{ - pid, - cache.NullableStringKey(keyword), - fmt.Sprintf("%d", page), - cache.NullableConditionKey(start), - cache.NullableConditionKey(end), - cache.NullableStringKey(buidling), - } - 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)) - return *readings, total, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -978,8 +862,6 @@ func (mr _MeterRepository) ListMeterReadings(pid string, keyword *string, page u return make([]*model.MeterReading, 0), 0, err } - cache.CachePagedSearch(readings, total, []string{fmt.Sprintf("meter_reading:%s", pid), "park"}, "meter_reading", cacheConditions...) - return readings, total, nil } @@ -1013,10 +895,6 @@ func (mr _MeterRepository) UpdateMeterReading(pid, mid string, readAt types.Date return false, err } - if ok.RowsAffected() > 0 { - cache.AbolishRelation(fmt.Sprintf("meter_reading:%s", pid)) - } - return ok.RowsAffected() > 0, nil } @@ -1079,11 +957,6 @@ func (mr _MeterRepository) ListLastMeterReading(pid string, date types.Date) ([] // 列出指定园区中的表计与商户的关联详细记录,用于写入Excel模板文件 func (mr _MeterRepository) ListMeterDocForTemplate(pid string) ([]*model.SimpleMeterDocument, error) { mr.log.Info("列出指定园区中的表计与商户的关联详细记录", zap.String("park id", pid)) - cacheConditions := []string{pid} - if docs, err := cache.RetrieveSearch[[]*model.SimpleMeterDocument]("simple_meter_doc", cacheConditions...); err == nil && docs != nil { - mr.log.Info("从缓存中获取到了指定园区中的表计与商户的关联详细记录", zap.Int("count", len(*docs))) - return *docs, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -1120,7 +993,5 @@ func (mr _MeterRepository) ListMeterDocForTemplate(pid string) ([]*model.SimpleM return make([]*model.SimpleMeterDocument, 0), err } - cache.CacheSearch(docs, []string{fmt.Sprintf("park:%s", pid), fmt.Sprintf("meter:%s", pid), "park"}, "simple_meter_doc", cacheConditions...) - return docs, nil } diff --git a/repository/park.go b/repository/park.go index c3a7a36..91afbc4 100644 --- a/repository/park.go +++ b/repository/park.go @@ -2,15 +2,12 @@ package repository import ( "context" - "electricity_bill_calc/cache" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/tools" "electricity_bill_calc/tools/serial" "electricity_bill_calc/types" - "fmt" - "strings" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" @@ -32,13 +29,6 @@ var ParkRepository = _ParkRepository{ // 列出指定用户下的所有园区 func (pr _ParkRepository) ListAllParks(uid string) ([]*model.Park, error) { pr.log.Info("列出指定用户下的所有园区", zap.String("uid", uid)) - cacheConditions := []string{ - uid, - } - if parks, err := cache.RetrieveSearch[[]*model.Park]("park_belongs", cacheConditions...); err == nil && parks != nil { - pr.log.Info("已经从缓存获取到了指定用户下的所有园区。") - return *parks, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -61,22 +51,12 @@ func (pr _ParkRepository) ListAllParks(uid string) ([]*model.Park, error) { pr.log.Error("列出指定用户下的所有园区失败!", zap.Error(err)) return make([]*model.Park, 0), err } - - cache.CacheSearch(parks, []string{"park", fmt.Sprintf("park:%s", uid)}, "park_belongs", cacheConditions...) - return parks, nil } // 检查并确定指定园区的归属情况 func (pr _ParkRepository) IsParkBelongs(pid, uid string) (bool, error) { pr.log.Info("检查并确定指定园区的归属情况", zap.String("pid", pid), zap.String("uid", uid)) - cacheConditions := []string{ - pid, "belongs", uid, - } - if exists, err := cache.CheckExists("park", cacheConditions...); err == nil && exists { - pr.log.Info("已经从缓存获取到了指定园区的归属情况。") - return true, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -93,11 +73,6 @@ func (pr _ParkRepository) IsParkBelongs(pid, uid string) (bool, error) { pr.log.Error("检查并确定指定园区的归属情况失败!", zap.Error(err)) return false, err } - - if count > 0 { - cache.CacheExists([]string{"park", fmt.Sprintf("park:%s", uid)}, "park", cacheConditions...) - } - return count > 0, nil } @@ -130,19 +105,12 @@ func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, er pr.log.Error("创建一个属于指定用户的新园区失败!", zap.Error(err)) return false, err } - - cache.AbolishRelation("park") - return rs.RowsAffected() > 0, nil } // 获取指定园区的详细信息 func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) { pr.log.Info("获取指定园区的详细信息", zap.String("pid", pid)) - if park, err := cache.RetrieveEntity[model.Park]("park", pid); err == nil && park != nil { - pr.log.Info("已经从缓存获取到了指定园区的详细信息。") - return park, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -161,19 +129,12 @@ func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) { pr.log.Error("获取指定园区的详细信息失败!", zap.Error(err)) return nil, err } - - cache.CacheEntity(park, []string{"park", fmt.Sprintf("park:%s", pid)}, "park", pid) - return &park, nil } // 获取园区对应的用户ID func (pr _ParkRepository) RetrieveParkBelongs(pid string) (string, error) { pr.log.Info("获取园区对应的用户ID", zap.String("pid", pid)) - if uid, err := cache.RetrieveEntity[string]("park_belongs", pid); err == nil && uid != nil { - pr.log.Info("已经从缓存获取到了园区对应的用户ID。") - return *uid, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -187,9 +148,6 @@ func (pr _ParkRepository) RetrieveParkBelongs(pid string) (string, error) { pr.log.Error("获取园区对应的用户ID失败!", zap.Error(err)) return "", err } - - cache.CacheEntity(uid, []string{"park", fmt.Sprintf("park:%s", pid)}, "park_belongs", pid) - return uid, nil } @@ -230,11 +188,6 @@ func (pr _ParkRepository) UpdatePark(pid string, park *model.Park) (bool, error) pr.log.Error("更新指定园区的信息失败!", zap.Error(err)) return false, err } - - if ok.RowsAffected() > 0 { - cache.AbolishRelation("park") - cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) - } return ok.RowsAffected() > 0, nil } @@ -259,11 +212,6 @@ func (pr _ParkRepository) EnablingPark(pid string, enabled bool) (bool, error) { pr.log.Error("设定园区的可用状态失败!", zap.Error(err)) return false, err } - - if ok.RowsAffected() > 0 { - cache.AbolishRelation("park") - cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) - } return ok.RowsAffected() > 0, nil } @@ -288,11 +236,6 @@ func (pr _ParkRepository) DeletePark(pid string) (bool, error) { pr.log.Error("删除指定园区(软删除)失败!", zap.Error(err)) return false, err } - - if ok.RowsAffected() > 0 { - cache.AbolishRelation("park") - cache.AbolishRelation(fmt.Sprintf("park:%s", pid)) - } return ok.RowsAffected() > 0, nil } @@ -303,11 +246,6 @@ func (pr _ParkRepository) RetrieveParks(pids []string) ([]*model.Park, error) { pr.log.Info("给定要检索的园区ID列表为空,执行快速返回。") return make([]*model.Park, 0), nil } - cacheConditions := []string{strings.Join(pids, ",")} - if parks, err := cache.RetrieveSearch[[]*model.Park]("park", cacheConditions...); err == nil && parks != nil { - pr.log.Info("已经从缓存获取到了给定的园区详细信息列表。") - return *parks, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -320,19 +258,12 @@ func (pr _ParkRepository) RetrieveParks(pids []string) ([]*model.Park, error) { pr.log.Error("检索给定的园区详细信息列表失败!", zap.Error(err)) return nil, err } - - cache.CacheSearch(parks, []string{"park", fmt.Sprintf("park:%s", strings.Join(pids, ":"))}, "park", cacheConditions...) - return parks, nil } // 获取指定园区中的建筑 func (pr _ParkRepository) RetrieveParkBuildings(pid string) ([]*model.ParkBuilding, error) { pr.log.Info("获取指定园区中的建筑", zap.String("pid", pid)) - if buildings, err := cache.RetrieveSearch[[]*model.ParkBuilding]("park_building", pid); err == nil && buildings != nil { - pr.log.Info("已经从缓存获取到了指定园区中的建筑。") - return *buildings, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -350,9 +281,6 @@ func (pr _ParkRepository) RetrieveParkBuildings(pid string) ([]*model.ParkBuildi pr.log.Error("获取指定园区中的建筑失败!", zap.Error(err)) return nil, err } - - cache.CacheSearch(buildings, []string{"park_building", fmt.Sprintf("park_building:%s", pid)}, "park_building", pid) - return buildings, nil } @@ -381,12 +309,6 @@ func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (b 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 } @@ -411,12 +333,6 @@ func (pr _ParkRepository) CreateParkBuildingWithTransaction(tx pgx.Tx, ctx conte 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 } @@ -445,12 +361,6 @@ func (pr _ParkRepository) ModifyParkBuilding(id, pid, name string, floor *string 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 } @@ -478,12 +388,6 @@ func (pr _ParkRepository) EnablingParkBuilding(id, pid string, enabled bool) (bo 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 } @@ -511,11 +415,5 @@ func (pr _ParkRepository) DeleteParkBuilding(id, pid string) (bool, error) { 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 } diff --git a/repository/region.go b/repository/region.go index 70785db..e350b52 100644 --- a/repository/region.go +++ b/repository/region.go @@ -1,7 +1,6 @@ package repository import ( - "electricity_bill_calc/cache" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" @@ -25,14 +24,6 @@ var RegionRepository = _RegionRepository{ // 获取指定行政区划下所有直接子级行政区划 func (r *_RegionRepository) FindSubRegions(parent string) ([]model.Region, error) { r.log.Info("获取指定行政区划下所有直接子级行政区划", zap.String("parent", parent)) - cacheConditions := []string{ - "parent", parent, - } - if regions, err := cache.RetrieveSearch[[]model.Region]("region", cacheConditions...); err == nil && regions != nil { - r.log.Info("已经从缓存获取到了指定的子级行政区划。") - return *regions, nil - } - ctx, cancel := global.TimeoutContext() defer cancel() @@ -45,21 +36,12 @@ func (r *_RegionRepository) FindSubRegions(parent string) ([]model.Region, error r.log.Error("获取指定行政区划下所有直接子级行政区划失败!", zap.Error(err)) return nil, err } - if len(regions) > 0 { - cache.CacheSearch(regions, []string{"region"}, "region", cacheConditions...) - } - return regions, nil } // 获取一个指定编号的行政区划详细信息 func (r *_RegionRepository) FindRegion(code string) (*model.Region, error) { r.log.Info("获取指定行政区划信息", zap.String("code", code)) - if region, err := cache.RetrieveEntity[model.Region]("region", code); err == nil && region != nil { - r.log.Info("已经从缓存获取到了指定的行政区划详细信息。") - return region, nil - } - ctx, cancel := global.TimeoutContext() defer cancel() @@ -73,22 +55,12 @@ func (r *_RegionRepository) FindRegion(code string) (*model.Region, error) { r.log.Error("获取指定行政区划信息失败!", zap.Error(err)) return nil, err } - - cache.CacheEntity(region, []string{"region"}, "region", code) return ®ion, nil } // 获取指定行政区划的所有直接和非直接父级 func (r *_RegionRepository) FindParentRegions(code string) ([]*model.Region, error) { r.log.Info("获取指定行政区划的所有直接和非直接父级", zap.String("code", code)) - cacheConditions := []string{ - "parent", code, - } - if regions, err := cache.RetrieveSearch[[]*model.Region]("region", cacheConditions...); err == nil && regions != nil { - r.log.Info("已经从缓存获取到了指定的父级行政区划。") - return *regions, nil - } - var ( regionsScanTask = []string{code} regions = make([]*model.Region, 0) @@ -103,9 +75,5 @@ func (r *_RegionRepository) FindParentRegions(code string) ([]*model.Region, err } } } - - if len(regions) > 0 { - cache.CacheSearch(regions, []string{"region"}, "region", cacheConditions...) - } return regions, nil } diff --git a/repository/tenement.go b/repository/tenement.go index 48642a6..2f130e7 100644 --- a/repository/tenement.go +++ b/repository/tenement.go @@ -2,7 +2,6 @@ package repository import ( "context" - "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" @@ -33,13 +32,6 @@ var TenementRepository = _TenementRepository{ // 判断指定商户是否属于指定用户的管辖 func (tr _TenementRepository) IsTenementBelongs(tid, uid string) (bool, error) { tr.log.Info("检查指定商户是否属于指定企业管辖", zap.String("Tenement", tid), zap.String("Enterprise", uid)) - cacheConditions := []string{ - tid, "belongs", uid, - } - if exists, err := cache.CheckExists("tenement", cacheConditions...); err != nil && exists { - tr.log.Error("检查指定商户是否属于指定企业管辖失败", zap.Error(err)) - return exists, err - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -57,10 +49,6 @@ func (tr _TenementRepository) IsTenementBelongs(tid, uid string) (bool, error) { tr.log.Error("检查指定商户是否属于指定企业管辖失败", zap.Error(err)) return false, err } - if count > 0 { - cache.CacheExists([]string{fmt.Sprintf("tenement:%s", tid)}, "tenement", cacheConditions...) - } - return count > 0, nil } @@ -76,21 +64,6 @@ func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, buil logger.DateFieldp("EndDate", endDate), zap.Stringp("State", state), ) - cacheConditions := []string{ - pid, - fmt.Sprintf("%d", page), - cache.NullableStringKey(keyword), - cache.NullableStringKey(building), - cache.NullableConditionKey(startDate), - cache.NullableConditionKey(endDate), - cache.NullableStringKey(state), - } - - if tenements, total, err := cache.RetrievePagedSearch[[]*model.Tenement]("tenements", cacheConditions...); err != nil && tenements != nil { - tr.log.Info("从缓存中获取到了符合条件的商户记录", zap.Int64("Total", total), zap.Int("Count", len(*tenements))) - return *tenements, total, nil - } - ctx, cancel := global.TimeoutContext() defer cancel() @@ -188,22 +161,12 @@ func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, buil tr.log.Error("检索查询指定园区中符合条件的商户总数量失败", zap.Error(err)) return tenements, 0, err } - - cache.CachePagedSearch(tenements, total, []string{fmt.Sprintf("tenements:%s", pid)}, "tenements", cacheConditions...) - return tenements, total, nil } // 查询指定园区中某一商户下的所有表计编号,不包含公摊表计 func (tr _TenementRepository) ListMeterCodesBelongsTo(pid, tid string) ([]string, error) { tr.log.Info("查询指定商户下所有的表计编号", zap.String("Park", pid), zap.String("Tenement", tid)) - cacheConditions := []string{ - pid, tid, - } - if meterCodes, err := cache.RetrieveSearch[[]string]("tenement_submeter", cacheConditions...); err != nil && meterCodes != nil { - tr.log.Info("从缓存中获取到了指定商户下所有的表计编号", zap.Int("Count", len(*meterCodes))) - return *meterCodes, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -222,9 +185,6 @@ func (tr _TenementRepository) ListMeterCodesBelongsTo(pid, tid string) ([]string tr.log.Error("查询指定商户下所有的表计编号失败", zap.Error(err)) return meterCodes, err } - - cache.CacheSearch(&meterCodes, []string{"tenement", "tenement_submeter", "meter", fmt.Sprintf("tenement:%s", pid)}, "tenement_submeter", cacheConditions...) - return meterCodes, nil } @@ -362,7 +322,6 @@ func (tr _TenementRepository) UpdateTenement(pid, tid string, tenement *vo.Tenem tr.log.Error("修改指定商户的信息失败", zap.Error(err)) return err } - cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) return nil } @@ -392,16 +351,6 @@ func (tr _TenementRepository) MoveOut(tx pgx.Tx, ctx context.Context, pid, tid s // 列出用于下拉列表的符合指定条件的商户信息 func (tr _TenementRepository) ListForSelect(uid string, pid, keyword *string, limit *uint) ([]*model.Tenement, error) { tr.log.Info("列出用于下拉列表的符合指定条件的商户信息", zap.String("Ent", uid), zap.String("Park", tools.DefaultOrEmptyStr(pid, "All")), zap.Stringp("Keyword", keyword), zap.Uintp("Limit", limit)) - cacheConditions := []string{ - uid, - cache.NullableStringKey(pid), - cache.NullableStringKey(keyword), - fmt.Sprintf("%d", tools.DefaultTo(limit, 0)), - } - if tenements, err := cache.RetrieveSearch[[]*model.Tenement]("tenement_choice", cacheConditions...); err != nil && tenements != nil { - tr.log.Info("从缓存中获取到了用于下拉列表的符合指定条件的商户信息", zap.Int("Count", len(*tenements))) - return *tenements, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -445,9 +394,6 @@ func (tr _TenementRepository) ListForSelect(uid string, pid, keyword *string, li tr.log.Error("列出用于下拉列表的符合指定条件的商户信息失败", zap.Error(err)) return tenements, err } - - cache.CacheSearch(&tenements, []string{"tenement"}, "tenement_choice", cacheConditions...) - return tenements, nil } @@ -487,10 +433,6 @@ func (tr _TenementRepository) ListTenementsInTimeRange(pid string, start, end ty // 获取指定园区中指定商户的详细信息 func (tr _TenementRepository) RetrieveTenementDetail(pid, tid string) (*model.Tenement, error) { tr.log.Info("获取指定园区中指定商户的详细信息", zap.String("Park", pid), zap.String("Tenement", tid)) - if tenement, err := cache.RetrieveEntity[model.Tenement](fmt.Sprintf("tenement:%s", pid), tid); err != nil && tenement != nil { - tr.log.Info("从缓存中获取到了指定园区中指定商户的详细信息") - return tenement, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -510,8 +452,5 @@ func (tr _TenementRepository) RetrieveTenementDetail(pid, tid string) (*model.Te tr.log.Error("获取指定园区中指定商户的详细信息失败", zap.Error(err)) return nil, err } - - cache.CacheEntity(&tenement, []string{"tenement", fmt.Sprintf("tenement:%s", pid)}, fmt.Sprintf("tenement:%s", pid), tid) - return &tenement, nil } diff --git a/repository/top_up.go b/repository/top_up.go index 83b87cf..1047c11 100644 --- a/repository/top_up.go +++ b/repository/top_up.go @@ -1,7 +1,6 @@ package repository import ( - "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" @@ -30,17 +29,6 @@ var TopUpRepository = _TopUpRepository{ // 检索符合条件的商户充值记录 func (tur _TopUpRepository) ListTopUps(pid string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.TopUp, int64, error) { tur.log.Info("查询符合条件的商户充值记录", zap.String("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("keyword", keyword), zap.Uint("page", page)) - cacheConditions := []string{ - pid, - cache.NullableStringKey(keyword), - cache.NullableConditionKey(startDate), - cache.NullableConditionKey(endDate), - fmt.Sprintf("%d", page), - } - if topUps, total, err := cache.RetrievePagedSearch[[]*model.TopUp]("top_ups", cacheConditions...); err == nil && topUps != nil && len(*topUps) > 0 { - tur.log.Info("从缓存中获取到商户充值记录", zap.Int("Count", len(*topUps)), zap.Int64("Total", total)) - return *topUps, total, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -103,17 +91,12 @@ func (tur _TopUpRepository) ListTopUps(pid string, startDate, endDate *types.Dat tur.log.Error("查询商户充值记录总数失败", zap.Error(err)) return topUps, 0, err } - cache.CachePagedSearch(topUps, total, []string{fmt.Sprintf("top_up:%s", pid), fmt.Sprintf("tenement:%s", pid), fmt.Sprintf("meter:%s", pid)}, "top_ups", cacheConditions...) return topUps, total, nil } // 取得一个充值记录的详细信息 func (tur _TopUpRepository) GetTopUp(pid, topUpCode string) (*model.TopUp, error) { tur.log.Info("查询充值记录", zap.String("Park", pid), zap.String("TopUpCode", topUpCode)) - if topUp, err := cache.RetrieveEntity[model.TopUp]("top_up", topUpCode); err == nil && topUp != nil { - tur.log.Info("从缓存中获取到充值记录") - return topUp, err - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -130,7 +113,6 @@ func (tur _TopUpRepository) GetTopUp(pid, topUpCode string) (*model.TopUp, error tur.log.Error("查询充值记录失败", zap.Error(err)) return nil, err } - cache.CacheEntity(&topUp, []string{fmt.Sprintf("top_up:%s", pid), fmt.Sprintf("tenement:%s", pid), fmt.Sprintf("meter:%s", pid)}, "top_up", topUpCode) return &topUp, nil } @@ -161,7 +143,6 @@ func (tur _TopUpRepository) CreateTopUp(pid string, form *vo.TopUpCreationForm) tur.log.Error("创建充值记录失败", zap.Error(err)) return err } - cache.AbolishRelation(fmt.Sprintf("top_ups:%s", pid)) return nil } @@ -181,6 +162,5 @@ func (tur _TopUpRepository) DeleteTopUp(pid, topUpCode string) error { tur.log.Error("删除充值记录失败", zap.Error(err)) return err } - cache.AbolishRelation(fmt.Sprintf("top_ups:%s", pid)) return nil } diff --git a/repository/user.go b/repository/user.go index 4f61829..d54d197 100644 --- a/repository/user.go +++ b/repository/user.go @@ -2,7 +2,6 @@ package repository import ( "context" - "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" @@ -10,7 +9,6 @@ import ( "electricity_bill_calc/tools" "electricity_bill_calc/types" "fmt" - "strings" "time" "github.com/doug-martin/goqu/v9" @@ -35,10 +33,6 @@ var UserRepository = _UserRepository{ // 使用用户名查询指定用户的基本信息 func (ur _UserRepository) FindUserByUsername(username string) (*model.User, error) { ur.log.Info("根据用户名查询指定用户的基本信息。", zap.String("username", username)) - if cachedUser, _ := cache.RetrieveEntity[model.User]("user", username); cachedUser != nil { - ur.log.Info("已经从缓存获取到了符合指定用户名条件的用户基本信息。", zap.String("username", username)) - return cachedUser, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -48,17 +42,12 @@ func (ur _UserRepository) FindUserByUsername(username string) (*model.User, erro ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) return nil, err } - cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", username), fmt.Sprintf("user:%s", user.Id)}, "user", username) return &user, nil } // 使用用户唯一编号查询指定用户的基本信息 func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { ur.log.Info("根据用户唯一编号查询指定用户的基本信息。", zap.String("user id", uid)) - if cachedUser, _ := cache.RetrieveEntity[model.User]("user", uid); cachedUser != nil { - ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") - return cachedUser, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -68,17 +57,12 @@ func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) return nil, err } - cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user", uid) return &user, nil } // 使用用户的唯一编号获取用户的详细信息 func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, error) { ur.log.Info("根据用户唯一编号查询指定用户的详细信息。", zap.String("user id", uid)) - if cachedUser, _ := cache.RetrieveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { - ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户详细信息。") - return cachedUser, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -88,17 +72,12 @@ func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, err ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) return nil, err } - cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_detail", uid) return &user, nil } // 使用用户唯一编号获取用户的综合详细信息 func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail, error) { ur.log.Info("根据用户唯一编号查询用户的综合详细信息", zap.String("user id", uid)) - if cachedUser, _ := cache.RetrieveEntity[model.UserWithDetail]("user_information", uid); cachedUser != nil { - ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户综合详细信息。") - return cachedUser, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -122,17 +101,12 @@ func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) return nil, err } - cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_information", uid) return &user, nil } // 检查指定用户唯一编号是否存在对应的用户 func (ur _UserRepository) IsUserExists(uid string) (bool, error) { ur.log.Info("检查指定用户唯一编号是否存在对应的用户。", zap.String("user id", uid)) - if exists, _ := cache.CheckExists("user", uid); exists { - ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") - return exists, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -142,19 +116,12 @@ func (ur _UserRepository) IsUserExists(uid string) (bool, error) { ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) return false, err } - if userCount > 0 { - cache.CacheExists([]string{"user", fmt.Sprintf("user:%s", uid)}, "user", uid) - } return userCount > 0, nil } // 检查指定用户名在数据库中是否已经存在 func (ur _UserRepository) IsUsernameExists(username string) (bool, error) { ur.log.Info("检查指定用户名在数据库中是否已经存在。", zap.String("username", username)) - if exists, _ := cache.CheckExists("user", username); exists { - ur.log.Info("已经从缓存获取到了符合指定用户名的用户基本信息。") - return exists, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -164,9 +131,6 @@ func (ur _UserRepository) IsUsernameExists(username string) (bool, error) { ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) return false, err } - if userCount > 0 { - cache.CacheExists([]string{"user", fmt.Sprintf("user:%s", username)}, "user", username) - } return userCount > 0, nil } @@ -222,8 +186,6 @@ func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, o ur.log.Error("提交数据库事务失败。", zap.Error(err)) tx.Rollback(ctx) return false, err - } else { - cache.AbolishRelation("user") } return userResult.RowsAffected() > 0 && detailResult.RowsAffected() > 0, nil } @@ -231,23 +193,6 @@ func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, o // 根据给定的条件检索用户 func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) ([]*model.UserWithDetail, int64, error) { ur.log.Info("根据给定的条件检索用户。", zap.Uint("page", page), zap.Stringp("keyword", keyword), zap.Int16("user type", userType), zap.Boolp("state", state)) - cacheConditions := []string{ - fmt.Sprintf("%d", page), - tools.CondFn( - func(v int16) bool { - return v != -1 - }, - userType, - fmt.Sprintf("%d", userType), - "UNDEF", - ), - tools.DefaultStrTo("%s", state, "UNDEF"), - tools.DefaultTo(keyword, ""), - } - if users, total, err := cache.RetrievePagedSearch[[]*model.UserWithDetail]("user_with_detail", cacheConditions...); err == nil && users != nil && total != -1 { - return *users, total, nil - } - ctx, cancel := global.TimeoutContext() defer cancel() @@ -312,13 +257,6 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, ur.log.Error("从数据库查询用户列表总数失败。", zap.Error(err)) return make([]*model.UserWithDetail, 0), 0, err } - cache.CachePagedSearch( - userWithDetails, - userCount, - []string{"user"}, - "user_with_detail", - cacheConditions..., - ) return userWithDetails, userCount, nil } @@ -349,7 +287,6 @@ func (ur _UserRepository) UpdateDetail(uid string, userDetail model.UserModifica ur.log.Error("向数据库更新指定用户的详细信息失败。", zap.String("user id", uid), zap.Error(err)) return false, err } else { - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) return res.RowsAffected() > 0, nil } } @@ -372,7 +309,6 @@ func (ur _UserRepository) UpdatePassword(uid, newCredential string, needReset bo ur.log.Error("向数据库更新指定用户的登录凭据失败。", zap.String("user id", uid), zap.Error(err)) return false, err } else { - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) return res.RowsAffected() > 0, nil } } @@ -395,7 +331,6 @@ func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) { ur.log.Error("向数据库更新指定用户的可用性状态失败。", zap.String("user id", uid), zap.Error(err)) return false, err } else { - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) return res.RowsAffected() > 0, nil } } @@ -404,14 +339,6 @@ func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) { func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) ([]*model.UserWithDetail, error) { ur.log.Info("检索条目数量有限的用户详细信息。", zap.Int16p("user type", userType), zap.Uint("limit", limit), zap.Stringp("keyword", keyword)) actualUserType := tools.DefaultTo(userType, model.USER_TYPE_ENT) - cacheConditions := []string{ - fmt.Sprintf("%d", actualUserType), - tools.DefaultTo(keyword, ""), - fmt.Sprintf("%d", limit), - } - if users, err := cache.RetrieveSearch[[]*model.UserWithDetail]("user_with_detail_limited", cacheConditions...); err == nil && users != nil { - return *users, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -446,7 +373,6 @@ func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) return nil, err } - cache.CacheSearch(users, []string{"user"}, "user_with_detail_limited", cacheConditions...) return users, nil } @@ -466,7 +392,6 @@ func (ur _UserRepository) UpdateServiceExpiration(tx pgx.Tx, ctx context.Context ur.log.Error("向数据库更新指定用户的服务有效期限失败。", zap.String("user id", uid), zap.Error(err)) return false, err } else { - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) return res.RowsAffected() > 0, nil } } @@ -477,12 +402,6 @@ func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]*model.UserDetai if len(uids) == 0 { return make([]*model.UserDetail, 0), nil } - cacheConditions := []string{ - strings.Join(uids, ","), - } - if users, err := cache.RetrieveSearch[[]*model.UserDetail]("user_detail", cacheConditions...); err == nil && users != nil { - return *users, nil - } ctx, cancel := global.TimeoutContext() defer cancel() @@ -496,6 +415,5 @@ func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]*model.UserDetai ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) return make([]*model.UserDetail, 0), err } - cache.CacheSearch(users, []string{"user", "user_detail"}, "user", cacheConditions...) return users, nil } diff --git a/service/charge.go b/service/charge.go index 807e617..5dfa2cc 100644 --- a/service/charge.go +++ b/service/charge.go @@ -1,7 +1,6 @@ package service import ( - "electricity_bill_calc/cache" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/repository" @@ -68,8 +67,6 @@ func (cs _ChargeService) RecordUserCharge(uid string, fee, discount, amount *flo cs.log.Error("提交数据库事务失败。", zap.Error(err)) return false, err } - cache.AbolishRelation("charge") - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) return true, nil } @@ -114,7 +111,5 @@ func (cs _ChargeService) CancelUserCharge(uid string, seq int64) (bool, error) { cs.log.Error("提交数据库事务失败。", zap.Error(err)) return false, err } - cache.AbolishRelation("charge") - cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) return true, nil } diff --git a/service/meter.go b/service/meter.go index 1eb32ec..bea9bb1 100644 --- a/service/meter.go +++ b/service/meter.go @@ -1,13 +1,11 @@ package service import ( - "electricity_bill_calc/cache" "electricity_bill_calc/excel" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/repository" - "electricity_bill_calc/tools" "electricity_bill_calc/types" "electricity_bill_calc/vo" "fmt" @@ -69,8 +67,6 @@ func (ms _MeterService) CreateMeterRecord(pid string, form *vo.MeterCreationForm tx.Rollback(ctx) return err } - - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return nil } @@ -104,8 +100,6 @@ func (ms _MeterService) UpdateMeterRecord(pid string, code string, form *vo.Mete tx.Rollback(ctx) return err } - - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return nil } @@ -446,7 +440,6 @@ func (ms _MeterService) ReplaceMeter( tx.Rollback(ctx) return err } - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return nil } @@ -474,16 +467,6 @@ func (ms _MeterService) ListPooledMeterRelations(pid, masterMeter string) ([]*mo // 列出指定园区中所有的公摊表计 func (ms _MeterService) SearchPooledMetersDetail(pid string, page uint, keyword *string) ([]*model.PooledMeterDetailCompound, int64, error) { ms.log.Info("列出指定园区中所有的公摊表计", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", *keyword)) - cacheConditions := []string{ - pid, - fmt.Sprintf("%d", page), - tools.DefaultTo(keyword, "UNDEFINED"), - } - if meters, total, err := cache.RetrievePagedSearch[[]*model.PooledMeterDetailCompound]("assemble_pooled_meters_detail", cacheConditions...); err == nil { - ms.log.Info("已经从缓存中获取到了指定园区中所有的公摊表计。", zap.Int("count", len(*meters)), zap.Int64("total", total)) - return *meters, total, nil - } - poolingMeters, total, err := repository.MeterRepository.ListPoolingMeters(pid, page, keyword) if err != nil { ms.log.Error("无法列出指定园区中所有的公摊表计。", zap.Error(err)) @@ -530,8 +513,6 @@ func (ms _MeterService) SearchPooledMetersDetail(pid string, page uint, keyword }) } - cache.CachePagedSearch(assembled, total, []string{fmt.Sprintf("meter:%s", pid), fmt.Sprintf("meter_relation:%s", pid)}, "assemble_pooled_meter_detail", cacheConditions...) - return assembled, total, nil } @@ -567,7 +548,6 @@ func (ms _MeterService) BindMeter(pid, masterMeter string, slaveMeters []string) tx.Rollback(ctx) return false, err } - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return true, nil } @@ -603,7 +583,6 @@ func (ms _MeterService) UnbindMeter(pid, masterMeter string, slaveMeters []strin tx.Rollback(ctx) return false, err } - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return true, nil } diff --git a/service/tenement.go b/service/tenement.go index 9103ca5..ab56299 100644 --- a/service/tenement.go +++ b/service/tenement.go @@ -1,7 +1,6 @@ package service import ( - "electricity_bill_calc/cache" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" @@ -62,7 +61,6 @@ func (ts _TenementService) CreateTenementRecord(pid string, creationForm *vo.Ten tx.Rollback(ctx) return fmt.Errorf("未能提交数据库事务,%w", err) } - cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) return nil } @@ -108,8 +106,6 @@ func (ts _TenementService) BindMeter(pid, tid, meterCode string, reading *vo.Met tx.Rollback(ctx) return fmt.Errorf("未能提交数据库事务,%w", err) } - cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return nil } @@ -155,8 +151,6 @@ func (ts _TenementService) UnbindMeter(pid, tid, meterCode string, reading *vo.M tx.Rollback(ctx) return fmt.Errorf("未能提交数据库事务,%w", err) } - cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return nil } @@ -243,8 +237,5 @@ func (ts _TenementService) MoveOutTenement(pid, tid string, reading []*vo.MeterR tx.Rollback(ctx) return fmt.Errorf("未能提交数据库事务,%w", err) } - - cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) - cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) return nil } From b2e4fb809fafef8f2bef422deeaa9aeb0d7a9602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 20 Jun 2023 13:19:36 +0800 Subject: [PATCH 089/141] =?UTF-8?q?build(container):=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E7=94=A8=E5=AE=B9=E5=99=A8=E9=95=9C=E5=83=8F=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E5=9B=BD=E5=86=85=E9=95=9C=E5=83=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b4df38b..062c966 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19-alpine AS builder +FROM dockerproxy.com/library/golang:1.20-alpine AS builder ENV GO111MODULE=on ENV GOPROXY="https://goproxy.io" @@ -9,7 +9,7 @@ RUN go mod download && go mod verify ADD . /app RUN CGO_ENABLED=0 GOOS=linux go build -tags=jsoniter -v -o server . -FROM alpine:latest AS production +FROM dockerproxy.com/library/alpine:latest AS production RUN echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main/" > /etc/apk/repositories RUN apk add --no-cache tzdata RUN apk update \ From fbe4036389d8a9811f1274258da5a7c904e8959d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 20 Jun 2023 16:50:34 +0800 Subject: [PATCH 090/141] =?UTF-8?q?enhance(report):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=8A=A5=E8=A1=A8=E9=83=A8=E5=88=86=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=9F=A5=E8=AF=A2=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/report.go | 827 +++++++++++++++++++++++++++++++++++++++++++ vo/report.go | 41 +++ 2 files changed, 868 insertions(+) diff --git a/repository/report.go b/repository/report.go index 4e251d6..9cf928f 100644 --- a/repository/report.go +++ b/repository/report.go @@ -1,10 +1,20 @@ package repository import ( + "electricity_bill_calc/config" + "electricity_bill_calc/exceptions" + "electricity_bill_calc/global" "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/tools/serial" + "electricity_bill_calc/types" + "electricity_bill_calc/vo" + "fmt" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/samber/lo" "go.uber.org/zap" ) @@ -17,3 +27,820 @@ var ReportRepository = _ReportRepository{ log: logger.Named("Repository", "Report"), ds: goqu.Dialect("postgres"), } + +// 检查指定核算报表的归属情况 +func (rr _ReportRepository) IsBelongsTo(rid, uid string) (bool, error) { + rr.log.Info("检查指定核算报表的归属", zap.String("User", uid), zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + querySql, queryParams, _ := rr.ds. + From(goqu.T("report").As("r")). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))). + Select(goqu.COUNT("r.*")). + Where( + goqu.I("r.id").Eq(rid), + goqu.I("p.user_id").Eq(uid), + ). + Prepared(true).ToSQL() + + var count int64 + if err := pgxscan.Get(ctx, global.DB, &count, querySql, queryParams...); err != nil { + rr.log.Error("检查指定核算报表的归属出现错误", zap.Error(err)) + return false, err + } + return count > 0, nil +} + +// 获取指定用户下所有园区的尚未发布的简易核算报表索引内容 +func (rr _ReportRepository) ListDraftReportIndicies(uid string) ([]*model.ReportIndex, error) { + rr.log.Info("获取指定用户下的所有尚未发布的报表索引", zap.String("User", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + querySql, queryParams, _ := rr.ds. + From(goqu.T("report").As("r")). + Join(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))). + Select("r.*", goqu.I("t.status"), goqu.I("t.message")). + Where( + goqu.I("p.user_id").Eq(uid), + goqu.I("r.published").IsFalse(), + ). + Order(goqu.I("r.created_at").Desc()). + Prepared(true).ToSQL() + + var indicies []*model.ReportIndex = make([]*model.ReportIndex, 0) + if err := pgxscan.Select(ctx, global.DB, &indicies, querySql, queryParams...); err != nil { + rr.log.Error("获取指定用户下的所有尚未发布的报表索引出现错误", zap.Error(err)) + return indicies, err + } + return indicies, nil +} + +// 获取指定报表的详细索引内容 +func (rr _ReportRepository) GetReportIndex(rid string) (*model.ReportIndex, error) { + rr.log.Info("获取指定报表的详细索引", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + querySql, queryParams, _ := rr.ds. + From(goqu.T("report").As("r")). + Join(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))). + Select("r.*", goqu.I("t.status"), goqu.I("t.message")). + Where(goqu.I("r.id").Eq(rid)). + Prepared(true).ToSQL() + + var index model.ReportIndex + if err := pgxscan.Get(ctx, global.DB, &index, querySql, queryParams...); err != nil { + rr.log.Error("获取指定报表的详细索引出现错误", zap.Error(err)) + return nil, err + } + return &index, nil +} + +// 为指园区创建一个新的核算报表 +func (rr _ReportRepository) CreateReport(form *vo.ReportCreationForm) (bool, error) { + rr.log.Info("为指定园区创建一个新的核算报表", zap.String("Park", form.Park)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + rr.log.Error("未能开始一个数据库事务", zap.Error(err)) + return false, err + } + park, err := ParkRepository.RetrieveParkDetail(form.Park) + if err != nil || park == nil { + rr.log.Error("未能获取指定园区的详细信息", zap.Error(err)) + tx.Rollback(ctx) + return false, exceptions.NewNotFoundErrorFromError("未能获取指定园区的详细信息", err) + } + createTime := types.Now() + periodRange := types.NewDateRange(&form.PeriodBegin, &form.PeriodEnd) + serial.StringSerialRequestChan <- 1 + reportId := serial.Prefix("R", <-serial.StringSerialResponseChan) + + createSql, createArgs, _ := rr.ds. + Insert(goqu.T("report")). + Cols( + "id", "park_id", "period", "category", "meter_o4kv_type", "price_policy", + "basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", + "last_modified_at", + ). + Vals(goqu.Vals{ + reportId, park.Id, periodRange, park.Category, park.MeterType, park.PricePolicy, + park.BasicPooled, park.AdjustPooled, park.LossPooled, park.PublicPooled, createTime, + createTime, + }). + Prepared(true).ToSQL() + summarySql, summaryArgs, _ := rr.ds. + Insert(goqu.T("report_summary")). + Cols( + "report_id", "overall", "critical", "peak", "flat", "valley", "basic_fee", + "adjust_fee", + ). + Vals(goqu.Vals{ + reportId, + model.ConsumptionUnit{ + Amount: form.Overall, + Fee: form.OverallFee, + }, + model.ConsumptionUnit{ + Amount: form.Critical, + Fee: form.CriticalFee, + }, + model.ConsumptionUnit{ + Amount: form.Peak, + Fee: form.PeakFee, + }, + model.ConsumptionUnit{ + Amount: form.Flat, + Fee: form.FlatFee, + }, + model.ConsumptionUnit{ + Amount: form.Valley, + Fee: form.ValleyFee, + }, + form.BasicFee, + form.AdjustFee, + }). + Prepared(true).ToSQL() + taskSql, taskArgs, _ := rr.ds. + Insert(goqu.T("report_task")). + Cols("id", "status", "last_modified_at"). + Vals(goqu.Vals{reportId, model.REPORT_CALCULATE_TASK_STATUS_PENDING, createTime}). + Prepared(true).ToSQL() + + resIndex, err := tx.Exec(ctx, createSql, createArgs...) + if err != nil { + rr.log.Error("创建核算报表索引时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + if resIndex.RowsAffected() == 0 { + rr.log.Error("保存核算报表索引时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, exceptions.NewUnsuccessCreateError("创建核算报表索引时出现错误") + } + resSummary, err := tx.Exec(ctx, summarySql, summaryArgs...) + if err != nil { + rr.log.Error("创建核算报表汇总时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + if resSummary.RowsAffected() == 0 { + rr.log.Error("保存核算报表汇总时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, exceptions.NewUnsuccessCreateError("创建核算报表汇总时出现错误") + } + resTask, err := tx.Exec(ctx, taskSql, taskArgs...) + if err != nil { + rr.log.Error("创建核算报表任务时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + if resTask.RowsAffected() == 0 { + rr.log.Error("保存核算报表任务时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, exceptions.NewUnsuccessCreateError("创建核算报表任务时出现错误") + } + err = tx.Commit(ctx) + if err != nil { + rr.log.Error("提交核算报表创建事务时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + return resIndex.RowsAffected() > 0 && resSummary.RowsAffected() > 0 && resTask.RowsAffected() > 0, nil +} + +// 更新报表的基本信息 +func (rr _ReportRepository) UpdateReportSummary(rid string, form *vo.ReportModifyForm) (bool, error) { + rr.log.Info("更新指定报表的基本信息", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + rr.log.Error("未能开始一个数据库事务", zap.Error(err)) + return false, err + } + + updateTime := types.Now() + newPeriod := types.NewDateRange(&form.PeriodBegin, &form.PeriodEnd) + + udpateIndexSql, updateIndexArgs, _ := rr.ds. + Update(goqu.T("report")). + Set(goqu.Record{ + "period": newPeriod, + "last_modified_at": updateTime, + }). + Where(goqu.I("id").Eq(rid)). + Prepared(true).ToSQL() + + updateSummarySql, updateSummaryArgs, _ := rr.ds. + Update(goqu.T("report_summary")). + Set(goqu.Record{ + "overall": model.ConsumptionUnit{Amount: form.Overall, Fee: form.OverallFee}, + "critical": model.ConsumptionUnit{Amount: form.Critical, Fee: form.CriticalFee}, + "peak": model.ConsumptionUnit{Amount: form.Peak, Fee: form.PeakFee}, + "flat": model.ConsumptionUnit{Amount: form.Flat, Fee: form.FlatFee}, + "valley": model.ConsumptionUnit{Amount: form.Valley, Fee: form.ValleyFee}, + "basic_fee": form.BasicFee, + "adjust_fee": form.AdjustFee, + }). + Where(goqu.I("report_id").Eq(rid)). + Prepared(true).ToSQL() + + resIndex, err := tx.Exec(ctx, udpateIndexSql, updateIndexArgs...) + if err != nil { + rr.log.Error("更新核算报表索引时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + if resIndex.RowsAffected() == 0 { + rr.log.Error("保存核算报表索引时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, exceptions.NewUnsuccessUpdateError("更新核算报表索引时出现错误") + } + resSummary, err := tx.Exec(ctx, updateSummarySql, updateSummaryArgs...) + if err != nil { + rr.log.Error("更新核算报表汇总时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + if resSummary.RowsAffected() == 0 { + rr.log.Error("保存核算报表汇总时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, exceptions.NewUnsuccessUpdateError("更新核算报表汇总时出现错误") + } + + err = tx.Commit(ctx) + if err != nil { + rr.log.Error("提交核算报表更新事务时出现错误", zap.Error(err)) + tx.Rollback(ctx) + return false, err + } + return resIndex.RowsAffected() > 0 && resSummary.RowsAffected() > 0, nil +} + +// 获取指定报表的总览信息 +func (rr _ReportRepository) RetrieveReportSummary(rid string) (*model.ReportSummary, error) { + rr.log.Info("获取指定报表的总览信息", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + querySql, queryParams, _ := rr.ds. + From(goqu.T("report_summary")). + Select("*"). + Where(goqu.I("report_id").Eq(rid)). + Prepared(true).ToSQL() + + var summary model.ReportSummary + if err := pgxscan.Get(ctx, global.DB, &summary, querySql, queryParams...); err != nil { + rr.log.Error("获取指定报表的总览信息时出现错误", zap.Error(err)) + return nil, err + } + return &summary, nil +} + +// 获取指定用户的尚未发布的核算报表的计算状态 +func (rr _ReportRepository) GetReportTaskStatus(uid string) ([]*model.ReportTask, error) { + rr.log.Info("获取指定用户的尚未发布的核算报表的计算状态", zap.String("User", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + querySql, queryParams, _ := rr.ds. + From(goqu.T("report_task").As("t")). + Join(goqu.T("report").As("r"), goqu.On(goqu.I("r.id").Eq(goqu.I("t.id")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))). + Select( + goqu.I("t.*"), + ). + Where( + goqu.I("p.user_id").Eq(uid), + goqu.I("r.published").IsFalse(), + ). + Prepared(true).ToSQL() + + var tasks []*model.ReportTask = make([]*model.ReportTask, 0) + if err := pgxscan.Select(ctx, global.DB, &tasks, querySql, queryParams...); err != nil { + rr.log.Error("获取指定用户的尚未发布的核算报表的计算状态时出现错误", zap.Error(err)) + return tasks, err + } + return tasks, nil +} + +// 检索指定核算报表中园区公共表计的核算记录 +func (rr _ReportRepository) ListPublicMetersInReport(rid string, page uint, keyword *string) ([]*model.ReportDetailedPublicConsumption, int64, error) { + rr.log.Info("检索指定核算报表中园区公共表计的核算记录", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + reportQuery := rr.ds. + From(goqu.T("report_public_consumption").As("r")). + Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))). + Select( + goqu.I("r.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"), + ). + Where(goqu.I("r.report_id").Eq(rid)) + countQuery := rr.ds. + From(goqu.T("report_public_consumption").As("r")). + Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))). + Select(goqu.COUNT(goqu.I("r.*"))). + Where(goqu.I("r.report_id").Eq(rid)) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + reportQuery = reportQuery.Where(goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + )) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + reportQuery = reportQuery. + Order(goqu.I("m.code").Asc()). + Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + var ( + consumptions []*model.ReportDetailedPublicConsumption = make([]*model.ReportDetailedPublicConsumption, 0) + count int64 + ) + querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &consumptions, querySql, queryArgs...); err != nil { + rr.log.Error("检索指定核算报表中园区公共表计的核算记录时出现错误", zap.Error(err)) + return consumptions, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil { + rr.log.Error("检索指定核算报表中园区公共表计的核算记录时出现错误", zap.Error(err)) + return consumptions, 0, err + } + return consumptions, count, nil +} + +// 检索指定核算报表中公摊表计的核算记录 +func (rr _ReportRepository) ListPooledMetersInReport(rid string, page uint, keyword *string) ([]*model.ReportDetailedPooledConsumption, int64, error) { + rr.log.Info("检索指定核算报表中公摊表计的核算记录", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + reportQuery := rr.ds. + From(goqu.T("report_pooled_consumption").As("r")). + Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))). + Select( + goqu.I("r.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"), + ). + Where(goqu.I("r.report_id").Eq(rid)) + countQuery := rr.ds. + From(goqu.T("report_pooled_consumption").As("r")). + Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))). + Select(goqu.COUNT(goqu.I("r.*"))). + Where(goqu.I("r.report_id").Eq(rid)) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + reportQuery = reportQuery.Where(goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("m.code").ILike(pattern), + goqu.I("m.address").ILike(pattern), + )) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + reportQuery = reportQuery. + Order(goqu.I("m.code").Asc()). + Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + var ( + consumptions []*model.ReportDetailedPooledConsumption = make([]*model.ReportDetailedPooledConsumption, 0) + count int64 + ) + querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &consumptions, querySql, queryArgs...); err != nil { + rr.log.Error("检索指定核算报表中公摊表计的核算记录时出现错误", zap.Error(err)) + return consumptions, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil { + rr.log.Error("检索指定核算报表中公摊表计的核算记录时出现错误", zap.Error(err)) + return consumptions, 0, err + } + return consumptions, count, nil +} + +// 列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计及分摊详细 +func (rr _ReportRepository) ListPooledMeterDetailInReport(rid, mid string) ([]*model.ReportDetailNestedMeterConsumption, error) { + rr.log.Info("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计及分摊详细", zap.String("Report", rid), zap.String("Meter", mid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + meterSql, meterArgs, _ := rr.ds. + From(goqu.T("report_pooled_consumption")). + Select("*"). + Where(goqu.I("report_id").Eq(rid), goqu.I("pooled_meter_id").Eq(mid)). + Prepared(true).ToSQL() + + var meter model.ReportPooledConsumption + if err := pgxscan.Get(ctx, global.DB, &meter, meterSql, meterArgs...); err != nil { + rr.log.Error("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计编号时出现错误", zap.Error(err)) + return make([]*model.ReportDetailNestedMeterConsumption, 0), err + } + meterCodes := lo.Map(meter.Diluted, func(m model.NestedMeter, _ int) string { + return m.MeterId + }) + + meterSql, meterArgs, _ = rr.ds. + From(goqu.T("meter_04kv").As("m")). + LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))). + Select( + goqu.I("m.*"), goqu.I("b.name").As("building_name"), + ). + Where(goqu.I("m.code").In(meterCodes)). + Prepared(true).ToSQL() + var meterDetails []*model.MeterDetail = make([]*model.MeterDetail, 0) + if err := pgxscan.Select(ctx, global.DB, &meterDetails, meterSql, meterArgs...); err != nil { + rr.log.Error("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计详细时出现错误", zap.Error(err)) + return make([]*model.ReportDetailNestedMeterConsumption, 0), err + } + assembled := lo.Map(meter.Diluted, func(m model.NestedMeter, _ int) *model.ReportDetailNestedMeterConsumption { + meterDetail, _ := lo.Find(meterDetails, func(elem *model.MeterDetail) bool { + return elem.Code == m.MeterId + }) + return &model.ReportDetailNestedMeterConsumption{ + Meter: *meterDetail, + Consumption: m, + } + }) + return assembled, nil +} + +// 列出指定核算报表下商户的简要计费信息 +func (rr _ReportRepository) ListTenementInReport(rid string, page uint, keyword *string) ([]*model.ReportTenement, int64, error) { + rr.log.Info("查询指定核算报表下的商户简要计费信息", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + reportQuery := rr.ds. + From(goqu.T("report_tenement").As("rt")). + Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("rt.tenement_id")))). + Select("rt.*"). + Where(goqu.I("rt.report_id").Eq(rid)) + countQuery := rr.ds. + From(goqu.T("report_tenement").As("rt")). + Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("rt.tenement_id")))). + Select(goqu.COUNT(goqu.I("rt.*"))). + Where(goqu.I("rt.report_id").Eq(rid)) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + reportQuery = reportQuery.Where(goqu.Or( + goqu.I("t.full_name").ILike(pattern), + goqu.T("t.short_name").ILike(pattern), + goqu.I("t.abbr").ILike(pattern), + goqu.I("t.address").ILike(pattern), + goqu.I("t.contact_name").ILike(pattern), + goqu.I("t.contact_phone").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("t.full_name").ILike(pattern), + goqu.T("t.short_name").ILike(pattern), + goqu.I("t.abbr").ILike(pattern), + goqu.I("t.address").ILike(pattern), + goqu.I("t.contact_name").ILike(pattern), + goqu.I("t.contact_phone").ILike(pattern), + )) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + reportQuery = reportQuery. + Order(goqu.I("t.movedin_at").Asc()). + Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + var ( + tenements []*model.ReportTenement = make([]*model.ReportTenement, 0) + count int64 + ) + querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &tenements, querySql, queryArgs...); err != nil { + rr.log.Error("查询指定核算报表下的商户简要计费信息时出现错误", zap.Error(err)) + return tenements, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil { + rr.log.Error("查询指定核算报表下的商户简要计费信息总数量时出现错误", zap.Error(err)) + return tenements, 0, err + } + return tenements, count, nil +} + +// 获取指定核算报表中指定商户的详细核算信息 +func (rr _ReportRepository) GetTenementDetailInReport(rid, tid string) (*model.ReportTenement, error) { + rr.log.Info("获取指定核算报表中指定商户的详细核算信息", zap.String("Report", rid), zap.String("Tenement", tid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + querySql, queryParams, _ := rr.ds. + From(goqu.T("report_tenement").As("rt")). + Select("rt.*"). + Where(goqu.I("rt.report_id").Eq(rid), goqu.I("rt.tenement_id").Eq(tid)). + Prepared(true).ToSQL() + + var tenement model.ReportTenement + if err := pgxscan.Get(ctx, global.DB, &tenement, querySql, queryParams...); err != nil { + rr.log.Error("获取指定核算报表中指定商户的详细核算信息时出现错误", zap.Error(err)) + return nil, err + } + return &tenement, nil +} + +// 更改指定核算报表的状态为已发布 +func (rr _ReportRepository) PublishReport(rid string) (bool, error) { + rr.log.Info("更改指定核算报表的状态为已发布", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + currentTime := types.Now() + updateSql, updateArgs, _ := rr.ds. + Update(goqu.T("report")). + Set(goqu.Record{ + "published": true, + "published_at": currentTime, + }). + Where(goqu.I("id").Eq(rid)). + Prepared(true).ToSQL() + + res, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + rr.log.Error("更改指定核算报表的状态为已发布时出现错误", zap.Error(err)) + return false, err + } + return res.RowsAffected() > 0, nil +} + +// 对指定核算报表进行综合检索 +func (rr _ReportRepository) ComprehensiveReportSearch(uid, pid *string, page uint, keyword *string, start, end *types.Date) ([]*model.ReportIndex, int64, error) { + rr.log.Info("对指定核算报表进行综合检索", zap.Stringp("User", uid), zap.Stringp("Park", pid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + reportQuery := rr.ds. + From(goqu.T("report").As("r")). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))). + LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))). + Select("r.*", goqu.I("t.status"), goqu.I("t.message")) + countQuery := rr.ds. + From(goqu.T("report").As("r")). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))). + LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))). + Select(goqu.COUNT(goqu.I("r.*"))) + + if uid != nil && len(*uid) > 0 { + reportQuery = reportQuery.Where(goqu.I("ud.id").Eq(*uid)) + countQuery = countQuery.Where(goqu.I("ud.id").Eq(*uid)) + } + + if pid != nil && len(*pid) > 0 { + reportQuery = reportQuery.Where(goqu.I("p.id").Eq(*pid)) + countQuery = countQuery.Where(goqu.I("p.id").Eq(*pid)) + } + + queryDateRange := types.NewDateRange(start, end) + reportQuery = reportQuery.Where(goqu.L("r.period <@ ?", queryDateRange)) + countQuery = countQuery.Where(goqu.L("r.period <@ ?", queryDateRange)) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + reportQuery = reportQuery.Where(goqu.Or( + goqu.I("p.name").ILike(pattern), + goqu.I("p.abbr").ILike(pattern), + goqu.I("p.address").ILike(pattern), + goqu.I("p.contact").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), + goqu.I("ud.contact").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("p.name").ILike(pattern), + goqu.I("p.abbr").ILike(pattern), + goqu.I("p.address").ILike(pattern), + goqu.I("p.contact").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), + goqu.I("ud.contact").ILike(pattern), + )) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + reportQuery = reportQuery. + Order(goqu.I("r.created_at").Desc()). + Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + var ( + reports []*model.ReportIndex = make([]*model.ReportIndex, 0) + count int64 + ) + querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &reports, querySql, queryArgs...); err != nil { + rr.log.Error("对指定核算报表进行综合检索时出现错误", zap.Error(err)) + return reports, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil { + rr.log.Error("对指定核算报表进行综合检索总数量时出现错误", zap.Error(err)) + return reports, 0, err + } + return reports, count, nil +} + +// 判断指定报表是否是当前园区的最后一张报表 +func (rr _ReportRepository) IsLastReport(rid string) (bool, error) { + rr.log.Info("判断指定报表是否是当前园区的最后一张报表", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + checkSql, checkArgs, _ := rr.ds. + From(goqu.T("report")). + Select(goqu.COUNT("*")). + Where( + goqu.I("r.id").Eq(rid), + goqu.Func("lower", goqu.I("r.period")).Gte(rr.ds. + From(goqu.T("report").As("ri")). + Select(goqu.Func("max", goqu.Func("upper", goqu.I("ri.period")))). + Where( + goqu.I("ri.park_id").Eq(goqu.I("r.park_id")), + goqu.I("ri.id").Neq(rid), + ), + ), + goqu.I("r.published").IsTrue(), + ). + Prepared(true).ToSQL() + + var count int64 + if err := pgxscan.Get(ctx, global.DB, &count, checkSql, checkArgs...); err != nil { + rr.log.Error("判断指定报表是否是当前园区的最后一张报表时出现错误", zap.Error(err)) + return false, err + } + return count > 0, nil +} + +// 申请撤回指定的核算报表 +func (rr _ReportRepository) ApplyWithdrawReport(rid string) (bool, error) { + rr.log.Info("申请撤回指定的核算报表", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + currentTime := types.Now() + updateSql, updateArgs, _ := rr.ds. + Update(goqu.T("report")). + Set(goqu.Record{ + "withdraw": model.REPORT_WITHDRAW_APPLYING, + "last_withdraw_applied_at": currentTime, + }). + Where(goqu.I("id").Eq(rid)). + Prepared(true).ToSQL() + + res, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + rr.log.Error("申请撤回指定的核算报表时出现错误", zap.Error(err)) + return false, err + } + return res.RowsAffected() > 0, nil +} + +// 批准核算报表的撤回申请 +func (rr _ReportRepository) ApproveWithdrawReport(rid string) (bool, error) { + rr.log.Info("批准核算报表的撤回申请", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + currentTime := types.Now() + updateSql, updateArgs, _ := rr.ds. + Update(goqu.T("report")). + Set(goqu.Record{ + "withdraw": model.REPORT_WITHDRAW_GRANTED, + "last_withdraw_audit_at": currentTime, + "published": false, + "published_at": nil, + }). + Where(goqu.I("id").Eq(rid)). + Prepared(true).ToSQL() + + res, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + rr.log.Error("批准核算报表的撤回申请时出现错误", zap.Error(err)) + return false, err + } + return res.RowsAffected() > 0, nil +} + +// 拒绝核算报表的撤回申请 +func (rr _ReportRepository) RejectWithdrawReport(rid string) (bool, error) { + rr.log.Info("拒绝核算报表的撤回申请", zap.String("Report", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + currentTime := types.Now() + updateSql, updateArgs, _ := rr.ds. + Update(goqu.T("report")). + Set(goqu.Record{ + "withdraw": model.REPORT_WITHDRAW_DENIED, + "last_withdraw_audit_at": currentTime, + }). + Where(goqu.I("id").Eq(rid)). + Prepared(true).ToSQL() + + res, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + rr.log.Error("拒绝核算报表的撤回申请时出现错误", zap.Error(err)) + return false, err + } + return res.RowsAffected() > 0, nil +} + +// 列出当前正在等待审核的已经申请撤回的核算报表 +func (rr _ReportRepository) ListWithdrawAppliedReports(page uint, keyword *string) ([]*model.ReportIndex, int64, error) { + rr.log.Info("列出当前正在等待审核的已经申请撤回的核算报表") + ctx, cancel := global.TimeoutContext() + defer cancel() + + reportQuery := rr.ds. + From(goqu.T("report").As("r")). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))). + LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))). + Select("r.*", goqu.I("t.status"), goqu.I("t.message")). + Where(goqu.I("r.withdraw").Eq(model.REPORT_WITHDRAW_APPLYING)) + countQuery := rr.ds. + From(goqu.T("report").As("r")). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))). + LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))). + Select(goqu.COUNT(goqu.I("r.*"))). + Where(goqu.I("r.withdraw").Eq(model.REPORT_WITHDRAW_APPLYING)) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + reportQuery = reportQuery.Where(goqu.Or( + goqu.I("p.name").ILike(pattern), + goqu.I("p.abbr").ILike(pattern), + goqu.I("p.address").ILike(pattern), + goqu.I("p.contact").ILike(pattern), + goqu.I("p.phone").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), + goqu.I("ud.contact").ILike(pattern), + goqu.I("ud.phone").ILike(pattern), + )) + countQuery = countQuery.Where(goqu.Or( + goqu.I("p.name").ILike(pattern), + goqu.I("p.abbr").ILike(pattern), + goqu.I("p.address").ILike(pattern), + goqu.I("p.contact").ILike(pattern), + goqu.I("p.phone").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + goqu.I("ud.abbr").ILike(pattern), + goqu.I("ud.contact").ILike(pattern), + goqu.I("ud.phone").ILike(pattern), + )) + } + + startRow := (page - 1) * config.ServiceSettings.ItemsPageSize + reportQuery = reportQuery. + Order(goqu.I("r.last_withdraw_applied_at").Desc()). + Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) + + var ( + reports []*model.ReportIndex = make([]*model.ReportIndex, 0) + count int64 + ) + querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL() + countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &reports, querySql, queryArgs...); err != nil { + rr.log.Error("列出当前正在等待审核的已经申请撤回的核算报表时出现错误", zap.Error(err)) + return reports, 0, err + } + if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil { + rr.log.Error("列出当前正在等待审核的已经申请撤回的核算报表总数量时出现错误", zap.Error(err)) + return reports, 0, err + } + return reports, count, nil +} diff --git a/vo/report.go b/vo/report.go index d685e69..d97def7 100644 --- a/vo/report.go +++ b/vo/report.go @@ -1 +1,42 @@ package vo + +import ( + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type ReportCreationForm struct { + Park string `json:"parkId"` + PeriodBegin types.Date `json:"periodBegin"` + PeriodEnd types.Date `json:"periodEnd"` + Overall decimal.Decimal `json:"overall"` + OverallFee decimal.Decimal `json:"overallFee"` + Critical decimal.Decimal `json:"critical"` + CriticalFee decimal.Decimal `json:"criticalFee"` + Peak decimal.Decimal `json:"peak"` + PeakFee decimal.Decimal `json:"peakFee"` + Flat decimal.Decimal `json:"flat,omitempty"` + FlatFee decimal.Decimal `json:"flatFee,omitempty"` + Valley decimal.Decimal `json:"valley"` + ValleyFee decimal.Decimal `json:"valleyFee"` + BasicFee decimal.Decimal `json:"basicFee"` + AdjustFee decimal.Decimal `json:"adjustFee"` +} + +type ReportModifyForm struct { + PeriodBegin types.Date `json:"periodBegin"` + PeriodEnd types.Date `json:"periodEnd"` + Overall decimal.Decimal `json:"overall"` + OverallFee decimal.Decimal `json:"overallFee"` + Critical decimal.Decimal `json:"critical"` + CriticalFee decimal.Decimal `json:"criticalFee"` + Peak decimal.Decimal `json:"peak"` + PeakFee decimal.Decimal `json:"peakFee"` + Flat decimal.Decimal `json:"flat,omitempty"` + FlatFee decimal.Decimal `json:"flatFee,omitempty"` + Valley decimal.Decimal `json:"valley"` + ValleyFee decimal.Decimal `json:"valleyFee"` + BasicFee decimal.Decimal `json:"basicFee"` + AdjustFee decimal.Decimal `json:"adjustFee"` +} From fa03bf5dbdffd13437178fd8edea2484bc05aec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 20 Jun 2023 23:06:43 +0800 Subject: [PATCH 091/141] =?UTF-8?q?enhance(report):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=8A=A5=E8=A1=A8=E6=9F=A5=E8=AF=A2=E9=83=A8?= =?UTF-8?q?=E5=88=86=E7=9A=84=E7=BB=BC=E5=90=88=E6=9C=8D=E5=8A=A1=E9=83=A8?= =?UTF-8?q?=E5=88=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/calculate.go | 70 ++++++++++++++ service/report.go | 198 ++++++++++++++++++++++++++++++++++++++++ tools/utils.go | 9 ++ types/daterange.go | 22 +++++ vo/park.go | 29 ++++++ vo/report.go | 30 ++++++ vo/user.go | 14 +++ 7 files changed, 372 insertions(+) create mode 100644 repository/calculate.go create mode 100644 service/report.go diff --git a/repository/calculate.go b/repository/calculate.go new file mode 100644 index 0000000..440d9ef --- /dev/null +++ b/repository/calculate.go @@ -0,0 +1,70 @@ +package repository + +import ( + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/types" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _CalculateRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var CalculateRepository = _CalculateRepository{ + log: logger.Named("Repository", "Calculate"), + ds: goqu.Dialect("postgres"), +} + +// 获取当前正在等待计算的核算任务ID列表 +func (cr _CalculateRepository) ListPendingTasks() ([]string, error) { + cr.log.Info("获取当前正在等待计算的核算任务ID列表") + ctx, cancel := global.TimeoutContext() + defer cancel() + + var ids []string + querySql, queryArgs, _ := cr.ds. + From("report_task"). + Select("id"). + Where(goqu.C("status").Eq(model.REPORT_CALCULATE_TASK_STATUS_PENDING)). + Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &ids, querySql, queryArgs...); err != nil { + cr.log.Error("未能获取到当前正在等待计算的核算任务ID列表", zap.Error(err)) + return nil, err + } + return ids, nil +} + +// 更新指定报表的核算状态 +func (cr _CalculateRepository) UpdateReportTaskStatus(rid string, status int16, message *string) (bool, error) { + cr.log.Info("更新指定报表的核算状态", zap.String("Report", rid), zap.Int16("Status", status)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + currentTime := types.Now() + updateSql, updateArgs, _ := cr.ds. + Update("report_task"). + Set(goqu.Record{ + "status": status, + "last_modified_at": currentTime, + "message": message, + }). + Where(goqu.C("id").Eq(rid)). + Prepared(true).ToSQL() + res, err := global.DB.Exec(ctx, updateSql, updateArgs...) + if err != nil { + cr.log.Error("未能更新指定报表的核算状态", zap.Error(err)) + return false, err + } + if res.RowsAffected() == 0 { + cr.log.Warn("未能保存指定报表的核算状态", zap.String("Report", rid)) + return false, nil + } + return res.RowsAffected() > 0, nil +} diff --git a/service/report.go b/service/report.go new file mode 100644 index 0000000..2380e83 --- /dev/null +++ b/service/report.go @@ -0,0 +1,198 @@ +package service + +import ( + "electricity_bill_calc/exceptions" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/repository" + "electricity_bill_calc/types" + "electricity_bill_calc/vo" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/jinzhu/copier" + "github.com/samber/lo" + "go.uber.org/zap" +) + +type _ReportService struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var ReportService = _ReportService{ + log: logger.Named("Service", "Report"), + ds: goqu.Dialect("postgres"), +} + +// 将指定报表列入计算任务 +func (rs _ReportService) DispatchReportCalculate(rid string) error { + rs.log.Info("将指定报表列入计算任务", zap.String("Report", rid)) + _, err := repository.CalculateRepository.UpdateReportTaskStatus(rid, model.REPORT_CALCULATE_TASK_STATUS_PENDING, nil) + if err != nil { + rs.log.Error("未能将指定报表列入计算任务", zap.Error(err)) + return err + } + return nil +} + +// 列出指定用户下的所有尚未发布的报表索引 +func (rs _ReportService) ListDraftReportIndicies(uid string) ([]*vo.ReportIndexQueryResponse, error) { + rs.log.Info("列出指定用户下的所有尚未发布的报表", zap.String("User", uid)) + indicies, err := repository.ReportRepository.ListDraftReportIndicies(uid) + if err != nil { + rs.log.Error("未能获取指定用户下所有未发布报表的索引", zap.Error(err)) + return make([]*vo.ReportIndexQueryResponse, 0), err + } + parkIds := lo.Map(indicies, func(elem *model.ReportIndex, _ int) string { + return elem.Park + }) + parks, err := repository.ParkRepository.RetrieveParks(parkIds) + if err != nil { + rs.log.Error("未能获取到相应报表对应的园区详细信息", zap.Error(err)) + return make([]*vo.ReportIndexQueryResponse, 0), err + } + assembled := lo.Reduce(indicies, func(acc []*vo.ReportIndexQueryResponse, elem *model.ReportIndex, _ int) []*vo.ReportIndexQueryResponse { + park, _ := lo.Find(parks, func(park *model.Park) bool { + return park.Id == elem.Park + }) + var ( + simplifiedPark vo.SimplifiedParkDetail + simplifiedReport vo.SimplifiedReportIndex + ) + copier.Copy(&simplifiedPark, park) + copier.Copy(&simplifiedReport, elem) + acc = append(acc, &vo.ReportIndexQueryResponse{ + Park: simplifiedPark, + Report: lo.ToPtr(simplifiedReport), + }) + return acc + }, make([]*vo.ReportIndexQueryResponse, 0)) + return assembled, nil +} + +// 获取指定报表中的包含索引、园区以及用户信息的详细信息 +func (rs _ReportService) RetrieveReportIndexDetail(rid string) (*model.UserDetail, *model.Park, *model.ReportIndex, error) { + index, err := repository.ReportRepository.GetReportIndex(rid) + if err != nil { + rs.log.Error("未能获取到指定报表的索引", zap.Error(err)) + return nil, nil, nil, exceptions.NewNotFoundErrorFromError("未能获取到指定报表的索引", err) + } + park, err := repository.ParkRepository.RetrieveParkDetail(index.Park) + if err != nil { + rs.log.Error("未能获取到指定报表对应的园区详细信息", zap.Error(err)) + return nil, nil, nil, exceptions.NewNotFoundErrorFromError("未能获取到指定报表对应的园区详细信息", err) + } + user, err := repository.UserRepository.FindUserDetailById(park.UserId) + if err != nil { + rs.log.Error("未能获取到指定报表对应的用户详细信息", zap.Error(err)) + return nil, nil, nil, exceptions.NewNotFoundErrorFromError("未能获取到指定报表对应的用户详细信息", err) + } + return user, park, index, nil +} + +// 根据给定的园区ID列表,查询园区以及用户的详细信息 +func (rs _ReportService) queryParkAndUserDetails(pids []string) ([]*model.Park, []*model.UserDetail, error) { + parks, err := repository.ParkRepository.RetrieveParks(pids) + if err != nil { + rs.log.Error("未能获取到相应报表对应的园区详细信息", zap.Error(err)) + return make([]*model.Park, 0), make([]*model.UserDetail, 0), exceptions.NewNotFoundErrorFromError("未能获取到相应报表对应的园区详细信息", err) + } + userIds := lo.Map(parks, func(elem *model.Park, _ int) string { + return elem.UserId + }) + users, err := repository.UserRepository.RetrieveUsersDetail(userIds) + if err != nil { + rs.log.Error("未能获取到相应报表对应的用户详细信息", zap.Error(err)) + return make([]*model.Park, 0), make([]*model.UserDetail, 0), exceptions.NewNotFoundErrorFromError("未能获取到相应报表对应的用户详细信息", err) + } + return parks, users, nil +} + +// 查询指定的核算报表列表 +func (rs _ReportService) QueryReports(uid, pid *string, page uint, keyword *string, periodBegin, periodEnd *types.Date) ([]*vo.ComprehensiveReportQueryResponse, int64, error) { + rs.log.Info("查询指定的核算报表列表", zap.Stringp("User", uid), zap.Stringp("Park", pid), zap.Uint("Page", page), zap.Stringp("Keyword", keyword), logger.DateFieldp("PeriodBegin", periodBegin), logger.DateFieldp("PeriodEnd", periodEnd)) + reports, total, err := repository.ReportRepository.ComprehensiveReportSearch(uid, pid, page, keyword, periodBegin, periodEnd) + if err != nil { + rs.log.Error("未能查询到指定的核算报表列表", zap.Error(err)) + return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err + } + parkIds := lo.Map(reports, func(elem *model.ReportIndex, _ int) string { + return elem.Park + }) + parks, users, err := rs.queryParkAndUserDetails(parkIds) + if err != nil { + return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err + } + assembled := lo.Reduce( + reports, + func(acc []*vo.ComprehensiveReportQueryResponse, elem *model.ReportIndex, _ int) []*vo.ComprehensiveReportQueryResponse { + park, _ := lo.Find(parks, func(park *model.Park) bool { + return park.Id == elem.Park + }) + user, _ := lo.Find(users, func(user *model.UserDetail) bool { + return user.Id == park.UserId + }) + var ( + simplifiedUser vo.SimplifiedUserDetail + simplifiedPark vo.SimplifiedParkDetail + simplifiedReport vo.SimplifiedReportIndex + ) + copier.Copy(&simplifiedUser, user) + copier.Copy(&simplifiedPark, park) + copier.Copy(&simplifiedReport, elem) + acc = append(acc, &vo.ComprehensiveReportQueryResponse{ + User: simplifiedUser, + Park: simplifiedPark, + Report: simplifiedReport, + }) + return acc + }, + make([]*vo.ComprehensiveReportQueryResponse, 0), + ) + return assembled, total, nil +} + +// 查询当前待审核的核算报表撤回申请列表 +func (rs _ReportService) ListWithdrawalRequests(page uint, keyword *string) ([]*vo.ComprehensiveReportQueryResponse, int64, error) { + rs.log.Info("查询当前待审核的核算报表撤回申请列表", zap.Uint("Page", page), zap.Stringp("Keyword", keyword)) + reports, total, err := repository.ReportRepository.ListWithdrawAppliedReports(page, keyword) + if err != nil { + rs.log.Error("未能查询到当前待审核的核算报表撤回申请列表", zap.Error(err)) + return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err + } + parkIds := lo.Map(reports, func(elem *model.ReportIndex, _ int) string { + return elem.Park + }) + parks, users, err := rs.queryParkAndUserDetails(parkIds) + if err != nil { + return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err + } + assembled := lo.Reduce( + reports, + func(acc []*vo.ComprehensiveReportQueryResponse, elem *model.ReportIndex, _ int) []*vo.ComprehensiveReportQueryResponse { + park, _ := lo.Find(parks, func(park *model.Park) bool { + return park.Id == elem.Park + }) + user, _ := lo.Find(users, func(user *model.UserDetail) bool { + return user.Id == park.UserId + }) + var ( + simplifiedUser vo.SimplifiedUserDetail + simplifiedPark vo.SimplifiedParkDetail + simplifiedReport vo.SimplifiedReportIndex + ) + copier.Copy(&simplifiedUser, user) + copier.Copy(&simplifiedPark, park) + copier.Copy(&simplifiedReport, elem) + acc = append(acc, &vo.ComprehensiveReportQueryResponse{ + User: simplifiedUser, + Park: simplifiedPark, + Report: simplifiedReport, + }) + return acc + }, + make([]*vo.ComprehensiveReportQueryResponse, 0), + ) + return assembled, total, nil +} diff --git a/tools/utils.go b/tools/utils.go index ad82f23..6312dd5 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -135,3 +135,12 @@ func EmptyToNil(val string) *string { } return &val } + +// 将一个`decimal.NullDecimal`类型的值转换为字符串指针,并且在转换的过程中设定其展示位数,默认使用银行进位法。 +func NullDecimalToString(d decimal.NullDecimal, precision ...int32) *string { + precision = append(precision, 2) + if !d.Valid { + return nil + } + return lo.ToPtr(d.Decimal.StringFixedBank(precision[0])) +} diff --git a/types/daterange.go b/types/daterange.go index ef46d3f..6b34a8a 100644 --- a/types/daterange.go +++ b/types/daterange.go @@ -112,3 +112,25 @@ func (dr *DateRange) SetUpperUnbounded() { dr.Range.Upper = MaxDate() dr.Range.UpperType = pgtype.Unbounded } + +func (dr DateRange) SafeUpper() Date { + switch dr.Range.UpperType { + case pgtype.Inclusive: + return dr.Range.Upper + case pgtype.Exclusive: + return Date{dr.Range.Upper.AddDate(0, 0, -1)} + default: + return MaxDate() + } +} + +func (dr DateRange) SafeLower() Date { + switch dr.Range.LowerType { + case pgtype.Inclusive: + return dr.Range.Lower + case pgtype.Exclusive: + return Date{dr.Range.Lower.AddDate(0, 0, 1)} + default: + return MinDate() + } +} diff --git a/vo/park.go b/vo/park.go index a557f4b..cbcc8a6 100644 --- a/vo/park.go +++ b/vo/park.go @@ -3,6 +3,8 @@ package vo import ( "electricity_bill_calc/model" "electricity_bill_calc/tools" + + "github.com/shopspring/decimal" ) type ParkInformationForm struct { @@ -65,3 +67,30 @@ type ParkBuildingInformationForm struct { Name string `json:"name"` Floors string `json:"floors"` } + +type SimplifiedParkDetail struct { + Id string `json:"id"` + UserId string `json:"user_id"` + Name string `json:"name"` + TenementStr *string `json:"tenement"` + AreaStr *string `json:"area"` + CapacityStr *string `json:"capacity"` + Category int16 `json:"category"` + MeterType int16 `json:"meter04kvType"` + Region *string `json:"region"` + Address *string `json:"address"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` +} + +func (spd *SimplifiedParkDetail) TenementQuantity(tq decimal.NullDecimal) { + spd.TenementStr = tools.NullDecimalToString(tq) +} + +func (spd *SimplifiedParkDetail) Area(area decimal.NullDecimal) { + spd.AreaStr = tools.NullDecimalToString(area) +} + +func (spd *SimplifiedParkDetail) Capacity(capacity decimal.NullDecimal) { + spd.CapacityStr = tools.NullDecimalToString(capacity) +} diff --git a/vo/report.go b/vo/report.go index d97def7..9bb1c6f 100644 --- a/vo/report.go +++ b/vo/report.go @@ -40,3 +40,33 @@ type ReportModifyForm struct { BasicFee decimal.Decimal `json:"basicFee"` AdjustFee decimal.Decimal `json:"adjustFee"` } + +type SimplifiedReportIndex struct { + Id string `json:"id"` + Park string `json:"parkId"` + PeriodBegin types.Date `json:"periodBegin"` + PeriodEnd types.Date `json:"periodEnd"` + Published bool `json:"published"` + PublishedAt *types.DateTime `json:"publishedAt"` + Withdraw int16 `json:"withdraw"` + LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt"` + LastWithdrawAuditAt *types.DateTime `json:"lastWithdrawAuditAt"` + Status int16 `json:"status"` + Message *string `json:"message"` +} + +func (sri *SimplifiedReportIndex) Period(p types.DateRange) { + sri.PeriodBegin = p.SafeLower() + sri.PeriodEnd = p.SafeUpper() +} + +type ReportIndexQueryResponse struct { + Park SimplifiedParkDetail `json:"park"` + Report *SimplifiedReportIndex `json:"report"` +} + +type ComprehensiveReportQueryResponse struct { + Report SimplifiedReportIndex `json:"report"` + Park SimplifiedParkDetail `json:"park"` + User SimplifiedUserDetail `json:"user"` +} diff --git a/vo/user.go b/vo/user.go index 14f2724..c7d430a 100644 --- a/vo/user.go +++ b/vo/user.go @@ -2,6 +2,7 @@ package vo import ( "electricity_bill_calc/model" + "electricity_bill_calc/tools" "electricity_bill_calc/types" "time" @@ -125,3 +126,16 @@ type RepasswordForm struct { Username string `json:"uname"` NewPassword string `json:"newPass"` } + +type SimplifiedUserDetail struct { + Id string `json:"id"` + NameStr string `json:"name"` + Contact *string `json:"contact"` + Phone *string `json:"phone"` + Region *string `json:"region"` + Address *string `json:"address"` +} + +func (sud *SimplifiedUserDetail) Name(n *string) { + sud.NameStr = tools.DefaultTo(n, "") +} From 2792959d1e2b038f5562ef2ec2c474f639877170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 13:08:34 +0800 Subject: [PATCH 092/141] =?UTF-8?q?feat(report):=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=8A=A5=E8=A1=A8=E6=9F=A5=E7=9C=8B=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=86=85=E5=AE=B9=E7=9A=84=E8=BF=81=E7=A7=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/report.go | 424 +++++++++++++++++++++++++++++++++++++++++++ model/report.go | 7 + router/router.go | 1 + vo/meter.go | 22 ++- vo/report.go | 247 +++++++++++++++++++++++++ vo/shares.go | 40 ++++ vo/tenement.go | 14 ++ 7 files changed, 754 insertions(+), 1 deletion(-) create mode 100644 controller/report.go diff --git a/controller/report.go b/controller/report.go new file mode 100644 index 0000000..d22d9cd --- /dev/null +++ b/controller/report.go @@ -0,0 +1,424 @@ +package controller + +import ( + "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" + + "github.com/gofiber/fiber/v2" + "github.com/jinzhu/copier" + "github.com/samber/lo" + "go.uber.org/zap" +) + +var reportLog = logger.Named("Handler", "Report") + +func InitializeReportHandlers(router *fiber.App) { + router.Get("/repotrs", security.MustAuthenticated, reportComprehensiveSearch) + router.Post("/report", security.EnterpriseAuthorize, initNewReportCalculateTask) + router.Get("/report/draft", security.EnterpriseAuthorize, listDraftReportIndicies) + router.Post("/report/calcualte", security.EnterpriseAuthorize, testCalculateReportSummary) + router.Get("/report/calculate/status", security.EnterpriseAuthorize, listCalculateTaskStatus) + router.Get("/report/:rid", security.EnterpriseAuthorize, getReportDetail) + router.Put("/report/:rid", security.EnterpriseAuthorize, updateReportCalculateTask) + router.Post("/report/:rid/publish", security.EnterpriseAuthorize, publishReport) + router.Put("/report/:rid/calculate", security.EnterpriseAuthorize, initiateCalculateTask) + router.Get("/report/:rid/publics", security.MustAuthenticated, listPublicMetersInReport) + router.Get("/report/:rid/summary", security.MustAuthenticated, getReportSummary) + router.Get("/report/:rid/summary/filled", security.EnterpriseAuthorize, getParkFilledSummary) + router.Get("/report/:rid/pooled", security.MustAuthenticated, listPooledMetersInReport) + router.Get("/report/:rid/:code/submeters", security.MustAuthenticated, listSubmetersInPooledMeter) + router.Get("/report/:rid/tenement", security.MustAuthenticated, listTenementsInReport) + router.Get("/report/:rid/tenement/:tid", security.MustAuthenticated, getTenementDetailInReport) +} + +// 检查指定报表是否属于当前用户 +func checkReportBelongs(reportId string, log *zap.Logger, c *fiber.Ctx, result *response.Result) (bool, error) { + session, err := _retreiveSession(c) + if err != nil { + log.Error("无法获取当前用户的会话信息", zap.Error(err)) + return false, result.Unauthorized("无法获取当前用户的会话信息。") + } + ok, err := repository.ReportRepository.IsBelongsTo(reportId, session.Uid) + if err != nil { + log.Error("无法检查核算报表的所有权", zap.Error(err)) + return false, result.Error(fiber.StatusInternalServerError, "无法检查核算报表的所有权。") + } + if !ok { + log.Error("核算报表不属于当前用户") + return false, result.Forbidden("核算报表不属于当前用户。") + } + return true, nil +} + +// 获取当前登录用户下所有园区的尚未发布的核算报表索引 +func listDraftReportIndicies(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + reportLog.Error("无法获取当前用户的会话信息", zap.Error(err)) + return result.Unauthorized("无法获取当前用户的会话信息。") + } + reportLog.Info("检索指定用户下的未发布核算报表索引", zap.String("User", session.Uid)) + indicies, err := service.ReportService.ListDraftReportIndicies(session.Uid) + if err != nil { + reportLog.Error("无法获取当前用户的核算报表索引", zap.Error(err)) + return result.NotFound("当前用户下未找到核算报表索引。") + } + return result.Success( + "已经获取到指定用户的报表索引。", + fiber.Map{"reports": indicies}, + ) +} + +// 初始化一个新的核算任务 +func initNewReportCalculateTask(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + reportLog.Error("无法获取当前用户的会话信息", zap.Error(err)) + return result.Unauthorized("无法获取当前用户的会话信息。") + } + reportLog.Info("初始化指定用户的一个新核算任务", zap.String("User", session.Uid)) + var form vo.ReportCreationForm + if err := c.BodyParser(&form); err != nil { + reportLog.Error("无法解析创建核算报表的请求数据。", zap.Error(err)) + return result.BadRequest("无法解析创建核算报表的请求数据。") + } + if pass, err := checkParkBelongs(form.Park, reportLog, c, &result); !pass { + return err + } + ok, err := repository.ReportRepository.CreateReport(&form) + if err != nil { + reportLog.Error("无法创建核算报表", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法创建核算报表。") + } + if !ok { + reportLog.Error("未能完成核算报表的保存。") + return result.NotAccept("未能完成核算报表的保存。") + } + return result.Success("已经成功创建核算报表。") +} + +// 更新指定的核算任务 +func updateReportCalculateTask(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass { + return err + } + var form vo.ReportModifyForm + if err := c.BodyParser(&form); err != nil { + reportLog.Error("无法解析更新核算报表的请求数据。", zap.Error(err)) + return result.BadRequest("无法解析更新核算报表的请求数据。") + } + ok, err := repository.ReportRepository.UpdateReportSummary(reportId, &form) + if err != nil { + reportLog.Error("无法更新核算报表", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法更新核算报表。") + } + if !ok { + reportLog.Error("未能完成核算报表的更新。") + return result.NotAccept("未能完成核算报表的更新。") + } + return result.Success("已经成功更新核算报表。") +} + +// 启动指定的核算任务 +func initiateCalculateTask(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass { + return err + } + err := service.ReportService.DispatchReportCalculate(reportId) + if err != nil { + reportLog.Error("无法启动核算报表计算任务", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法启动核算报表计算任务。") + } + return result.Success("已经成功启动核算报表计算任务。") +} + +// 获取自己园区的已经填写的园区电量信息 +func getParkFilledSummary(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass { + return err + } + reportLog.Info("获取园区电量信息", zap.String("Report", reportId)) + summary, err := repository.ReportRepository.RetrieveReportSummary(reportId) + if err != nil { + reportLog.Error("无法获取核算报表的园区电量信息", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表的园区电量信息。") + } + if summary == nil { + reportLog.Error("未找到核算报表的园区电量信息") + return result.NotFound("未找到核算报表的园区电量信息。") + } + var summaryResponse vo.SimplifiedReportSummary + copier.Copy(&summaryResponse, summary) + return result.Success( + "已经获取到核算报表的园区电量信息。", + fiber.Map{"summary": summaryResponse}, + ) +} + +// 对提供的园区电量信息进行试计算,返回试计算结果 +func testCalculateReportSummary(c *fiber.Ctx) error { + result := response.NewResult(c) + reportLog.Info("试计算园区电量信息") + var form vo.TestCalculateForm + if err := c.BodyParser(&form); err != nil { + reportLog.Error("无法解析试计算核算报表的请求数据。", zap.Error(err)) + return result.BadRequest("无法解析试计算核算报表的请求数据。") + } + return result.Success( + "电量电费试计算已经完成。", + fiber.Map{"summary": form.Calculate()}, + ) +} + +// 获取指定园区中尚未发布的核算报表计算状态 +func listCalculateTaskStatus(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + reportLog.Error("无法获取当前用户的会话信息", zap.Error(err)) + return result.Unauthorized("无法获取当前用户的会话信息。") + } + status, err := repository.ReportRepository.GetReportTaskStatus(session.Uid) + if err != nil { + reportLog.Error("无法获取核算报表计算状态", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表计算状态。") + } + var statusResponse []*vo.ReportCalculateTaskStatusResponse + copier.Copy(&statusResponse, &status) + return result.Success( + "已经获取到核算报表计算状态。", + fiber.Map{"status": statusResponse}, + ) +} + +// 获取指定报表的详细信息 +func getReportDetail(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + reportLog.Info("获取核算报表的详细信息", zap.String("Report", reportId)) + user, park, report, err := service.ReportService.RetrieveReportIndexDetail(reportId) + if err != nil { + reportLog.Error("无法获取核算报表的详细信息", zap.Error(err)) + return result.NotFound("无法获取核算报表的详细信息。") + } + return result.Success( + "已经获取到核算报表的详细信息。", + fiber.Map{ + "detail": vo.NewReportDetailQueryResponse(user, park, report), + }, + ) +} + +// 获取指定核算报表的总览信息 +func getReportSummary(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + report, err := repository.ReportRepository.RetrieveReportSummary(reportId) + if err != nil { + reportLog.Error("无法获取核算报表的总览信息", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表的总览信息。") + } + if report == nil { + reportLog.Error("未找到核算报表的总览信息") + return result.NotFound("未找到核算报表的总览信息。") + } + var summaryResponse vo.ParkSummaryResponse + copier.Copy(&summaryResponse, report) + return result.Success( + "已经获取到核算报表的总览信息。", + fiber.Map{"summary": summaryResponse}, + ) +} + +// 获取指定报表中分页的公共表计的核算摘要信息 +func listPublicMetersInReport(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + reportLog.Info("获取核算报表中的公共表计信息", zap.String("Report", reportId)) + page := c.QueryInt("page", 1) + keyword := tools.EmptyToNil(c.Query("keyword")) + meters, total, err := repository.ReportRepository.ListPublicMetersInReport(reportId, uint(page), keyword) + if err != nil { + reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。") + } + meterResponse := lo.Map(meters, func(meter *model.ReportDetailedPublicConsumption, _ int) *vo.ReportPublicQueryResponse { + m := &vo.ReportPublicQueryResponse{} + m.FromReportDetailPublicConsumption(meter) + return m + }) + return result.Success( + "已经获取到指定核算报表中的分页公共表计的核算信息。", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{"public": meterResponse}, + ) +} + +// 获取指定报表中的分页的公摊表计的核算摘要信息 +func listPooledMetersInReport(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + reportLog.Info("获取核算报表中的公摊表计信息", zap.String("Report", reportId)) + page := c.QueryInt("page", 1) + keyword := tools.EmptyToNil(c.Query("keyword")) + meters, total, err := repository.ReportRepository.ListPooledMetersInReport(reportId, uint(page), keyword) + if err != nil { + reportLog.Error("无法获取核算报表中的公摊表计信息", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公摊表计信息。") + } + meterResponse := lo.Map(meters, func(meter *model.ReportDetailedPooledConsumption, _ int) *vo.ReportPooledQueryResponse { + m := &vo.ReportPooledQueryResponse{} + m.FromReportDetailPooledConsumption(meter) + return m + }) + return result.Success( + "已经获取到指定核算报表中的分页公摊表计的核算信息。", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{"pooled": meterResponse}, + ) +} + +// 列出指定报表中指定公共表计下各个分摊表计的消耗数据 +func listSubmetersInPooledMeter(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + meterId := c.Params("code") + if len(meterId) == 0 { + reportLog.Error("未提供公共表计的编号") + return result.BadRequest("未提供公共表计的编号。") + } + meters, err := repository.ReportRepository.ListPooledMeterDetailInReport(reportId, meterId) + if err != nil { + reportLog.Error("无法获取核算报表中的公共表计信息", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的公共表计信息。") + } + meterResponse := lo.Map(meters, func(meter *model.ReportDetailNestedMeterConsumption, _ int) *vo.ReportPooledQueryResponse { + m := &vo.ReportPooledQueryResponse{} + m.FromReportDetailNestedMeterConsumption(meter) + return m + }) + return result.Success( + "已经获取到指定核算报表中的公共表计的核算信息。", + fiber.Map{"meters": meterResponse}, + ) +} + +// 获取指定报表中分页的商户核算电量电费概要数据 +func listTenementsInReport(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + page := c.QueryInt("page", 1) + keyword := tools.EmptyToNil(c.Query("keyword")) + tenements, total, err := repository.ReportRepository.ListTenementInReport(reportId, uint(page), keyword) + if err != nil { + reportLog.Error("无法获取核算报表中的商户信息", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的商户信息。") + } + tenementsResponse := lo.Map(tenements, func(tenement *model.ReportTenement, _ int) *vo.ReportTenementSummaryResponse { + t := &vo.ReportTenementSummaryResponse{} + t.FromReportTenement(tenement) + return t + }) + return result.Success( + "已经获取到指定核算报表中的分页商户的核算信息。", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{"tenements": tenementsResponse}, + ) +} + +// 获取指定报表中指定商户的详细核算信息 +func getTenementDetailInReport(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + tenementId := c.Params("tid") + detail, err := repository.ReportRepository.GetTenementDetailInReport(reportId, tenementId) + if err != nil { + reportLog.Error("无法获取核算报表中的商户信息", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法获取核算报表中的商户信息。") + } + var detailResponse vo.ReportTenementDetailResponse + detailResponse.FromReportTenement(detail) + return result.Success( + "已经获取到指定核算报表中的商户的详细核算信息。", + fiber.Map{"detail": detailResponse}, + ) +} + +// 发布指定的核算报表 +func publishReport(c *fiber.Ctx) error { + result := response.NewResult(c) + reportId := c.Params("rid") + if pass, err := checkReportBelongs(reportId, reportLog, c, &result); !pass { + return err + } + ok, err := repository.ReportRepository.PublishReport(reportId) + if err != nil { + reportLog.Error("无法发布核算报表", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "发布核算报表出错。") + } + if !ok { + reportLog.Error("未能完成核算报表的发布。") + return result.NotAccept("未能完成核算报表的发布。") + } + return result.Success("已经成功发布核算报表。") +} + +// 对核算报表进行综合检索 +func reportComprehensiveSearch(c *fiber.Ctx) error { + result := response.NewResult(c) + user := tools.EmptyToNil(c.Query("user")) + session, err := _retreiveSession(c) + if err != nil { + reportLog.Error("无法获取当前用户的会话信息", zap.Error(err)) + return result.Unauthorized("无法获取当前用户的会话信息。") + } + park := tools.EmptyToNil(c.Query("park_id")) + if session.Type == model.USER_TYPE_ENT && park != nil && len(*park) > 0 { + if pass, err := checkParkBelongs(*park, reportLog, c, &result); !pass { + return err + } + } + var requestUser *string + if session.Type == model.USER_TYPE_ENT { + requestUser = lo.ToPtr(tools.DefaultTo(user, session.Uid)) + } else { + requestUser = user + } + page := c.QueryInt("page", 1) + keyword := tools.EmptyToNil(c.Query("keyword")) + startDate, err := types.ParseDatep(c.Query("period_start")) + if err != nil { + reportLog.Error("无法解析核算报表查询的开始日期", zap.Error(err)) + return result.BadRequest("无法解析核算报表查询的开始日期。") + } + endDate, err := types.ParseDatep(c.Query("period_end")) + if err != nil { + reportLog.Error("无法解析核算报表查询的结束日期", zap.Error(err)) + return result.BadRequest("无法解析核算报表查询的结束日期。") + } + reports, total, err := service.ReportService.QueryReports(requestUser, park, uint(page), keyword, startDate, endDate) + if err != nil { + reportLog.Error("无法查询核算报表", zap.Error(err)) + return result.Error(fiber.StatusInternalServerError, "无法查询核算报表。") + } + return result.Success( + "已经获取到指定核算报表的分页信息。", + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{"reports": reports}, + ) +} diff --git a/model/report.go b/model/report.go index 5b58f60..fb296d2 100644 --- a/model/report.go +++ b/model/report.go @@ -52,6 +52,13 @@ type ReportSummary struct { FinalDilutedOverall decimal.NullDecimal `json:"finalDilutedOverall" db:"final_diluted_overall"` } +func (rs ReportSummary) GetConsumptionFee() decimal.Decimal { + if !rs.ConsumptionFee.Valid { + return rs.Overall.Fee.Sub(rs.BasicFee).Sub(rs.AdjustFee) + } + return rs.ConsumptionFee.Decimal +} + type ReportPublicConsumption struct { ReportId string `json:"reportId" db:"report_id"` MeterId string `json:"parkMeterId" db:"park_meter_id"` diff --git a/router/router.go b/router/router.go index ac51adb..1e268f6 100644 --- a/router/router.go +++ b/router/router.go @@ -51,6 +51,7 @@ func App() *fiber.App { controller.InitializeMeterHandlers(app) controller.InitializeInvoiceHandler(app) controller.InitializeTopUpHandlers(app) + controller.InitializeReportHandlers(app) return app } diff --git a/vo/meter.go b/vo/meter.go index 0c84e79..626bb89 100644 --- a/vo/meter.go +++ b/vo/meter.go @@ -1,6 +1,10 @@ package vo -import "github.com/shopspring/decimal" +import ( + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) type MeterCreationForm struct { Code string `json:"code"` @@ -42,3 +46,19 @@ type SimplifiedMeterQueryResponse struct { Address *string `json:"address"` Park string `json:"parkId"` } + +type SimplifiedMeterDetailResponse struct { + Code string `json:"code"` + Park string `json:"parkId"` + Address *string `json:"address"` + Seq int64 `json:"seq"` + Ratio decimal.Decimal `json:"ratio"` + Building *string `json:"building"` + BuildingName *string `json:"buildingName"` + OnFloor *string `json:"onFloor"` + Area decimal.Decimal `json:"area"` + Enabled bool `json:"enabled"` + MeterType int16 `json:"meterType"` + AttachedAt types.DateTime `json:"attachedAt"` + DetachedAt *types.DateTime `json:"detachedAt"` +} diff --git a/vo/report.go b/vo/report.go index 9bb1c6f..6ad2f5f 100644 --- a/vo/report.go +++ b/vo/report.go @@ -1,8 +1,10 @@ package vo import ( + "electricity_bill_calc/model" "electricity_bill_calc/types" + "github.com/jinzhu/copier" "github.com/shopspring/decimal" ) @@ -70,3 +72,248 @@ type ComprehensiveReportQueryResponse struct { Park SimplifiedParkDetail `json:"park"` User SimplifiedUserDetail `json:"user"` } + +type BasicReportIndexResponse struct { + Id string `json:"id"` + Park string `json:"park_id"` + PeriodBegin types.Date `json:"period_begin"` + PeriodEnd types.Date `json:"period_end"` + Category int16 `json:"category"` + MeterType int16 `json:"meter04kvType"` + PricePolicy int16 `json:"pricePolicy"` + BasisPooled int16 `json:"basisDiluted"` + AdjustPooled int16 `json:"adjustDiluted"` + LossPooled int16 `json:"lossDiluted"` + PublicPooled int16 `json:"publicDiluted"` + Published bool `json:"published"` + PublishedAt *types.DateTime `json:"published_at"` + Withdraw int16 `json:"withdraw"` + LastWithdrawAppliedAt *types.DateTime `json:"last_withdraw_applied_at"` + LastWithdrawAuditAt *types.DateTime `json:"last_withdraw_audit_at"` + Status int16 `json:"status"` + Message *string `json:"message"` + CreatedAt types.DateTime `json:"created_at"` + LastModifiedAt types.DateTime `json:"last_modified_at"` +} + +func (bri *BasicReportIndexResponse) Period(p types.DateRange) { + bri.PeriodBegin = p.SafeLower() + bri.PeriodEnd = p.SafeUpper() +} + +type ReportDetailQueryResponse struct { + Enterprise SimplifiedUserDetail `json:"enterprise"` + Park SimplifiedParkDetail `json:"park"` + Report BasicReportIndexResponse `json:"report"` +} + +func NewReportDetailQueryResponse(user *model.UserDetail, park *model.Park, report *model.ReportIndex) ReportDetailQueryResponse { + var response ReportDetailQueryResponse + copier.Copy(&response.Enterprise, user) + copier.Copy(&response.Park, park) + copier.Copy(&response.Report, report) + return response +} + +type ParkSummaryResponse struct { + Id string `json:"id"` + Overall ConsumptionDisplay `json:"overall"` + Area decimal.Decimal `json:"area"` + BasicFee decimal.Decimal `json:"basicFee"` + PooledBasicFeeByAmount decimal.Decimal `json:"pooledBasicFeeByAmount"` + PooledBasicFeeByArea decimal.Decimal `json:"pooledBasicFeeByArea"` + AdjustFee decimal.Decimal `json:"adjustFee"` + PooledAdjustFeeByAmount decimal.Decimal `json:"pooledAdjustFeeByAmount"` + PooledAdjustFeeByArea decimal.Decimal `json:"pooledAdjustFeeByArea"` + Consumption decimal.Decimal `json:"consumption"` + Loss decimal.Decimal `json:"loss"` + LossRate decimal.Decimal `json:"lossRate"` +} + +type SimplifiedReportSummary struct { + Overall model.ConsumptionUnit `json:"overall"` + Critical model.ConsumptionUnit `json:"critical"` + Peak model.ConsumptionUnit `json:"peak"` + Flat model.ConsumptionUnit `json:"flat"` + Valley model.ConsumptionUnit `json:"valley"` + BasicFee decimal.Decimal `json:"basicFee"` + AdjustFee decimal.Decimal `json:"adjustFee"` + ConsumptionFee decimal.Decimal `json:"consumptionFee" copier:"GetConsumptionFee"` +} + +type TestCalculateForm struct { + Overall decimal.Decimal `json:"overall"` + OverallFee decimal.Decimal `json:"overallFee"` + Critical decimal.Decimal `json:"critical"` + CriticalFee decimal.Decimal `json:"criticalFee"` + Peak decimal.Decimal `json:"peak"` + PeakFee decimal.Decimal `json:"peakFee"` + Valley decimal.Decimal `json:"valley"` + ValleyFee decimal.Decimal `json:"valleyFee"` + BasicFee decimal.Decimal `json:"basicFee"` + AdjustFee decimal.Decimal `json:"adjustFee"` +} + +type TestCalculateResult struct { + OverallPrice decimal.Decimal `json:"overallPrice"` + CriticalPrice decimal.Decimal `json:"criticalPrice"` + PeakPrice decimal.Decimal `json:"peakPrice"` + Flat decimal.Decimal `json:"flat"` + FlatFee decimal.Decimal `json:"flatFee"` + FlatPrice decimal.Decimal `json:"flatPrice"` + ValleyPrice decimal.Decimal `json:"valleyPrice"` + ConsumptionFee decimal.Decimal `json:"consumptionFee"` +} + +func (t TestCalculateForm) Calculate() TestCalculateResult { + var r TestCalculateResult = TestCalculateResult{} + r.ConsumptionFee = t.OverallFee.Sub(t.BasicFee).Sub(t.AdjustFee) + if t.Overall.GreaterThan(decimal.Zero) { + r.OverallPrice = r.ConsumptionFee.Div(t.Overall).RoundBank(8) + } + if t.Critical.GreaterThan(decimal.Zero) { + r.CriticalPrice = t.CriticalFee.Div(t.Critical).RoundBank(8) + } + if t.Peak.GreaterThan(decimal.Zero) { + r.PeakPrice = t.PeakFee.Div(t.Peak).RoundBank(8) + } + r.Flat = t.Overall.Sub(t.Critical).Sub(t.Peak).Sub(t.Valley) + r.FlatFee = r.ConsumptionFee.Sub(t.CriticalFee).Sub(t.PeakFee).Sub(t.ValleyFee).RoundBank(8) + if r.Flat.GreaterThan(decimal.Zero) { + r.FlatPrice = r.FlatFee.Div(r.Flat).RoundBank(8) + } + r.ConsumptionFee = r.ConsumptionFee.RoundBank(8) + return r +} + +type ReportCalculateTaskStatusResponse struct { + Id string `json:"id"` + Status int16 `json:"status"` + Message *string `json:"message"` +} + +type ReportPublicQueryResponse struct { + SimplifiedMeterQueryResponse + Overall ConsumptionDisplay `json:"overall"` + AdjustLoss ConsumptionDisplay `json:"adjustLoss"` +} + +func (rpqr *ReportPublicQueryResponse) FromReportDetailPublicConsumption(value *model.ReportDetailedPublicConsumption) { + copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.MeterDetail) + rpqr.Overall = FromConsumptionUnit(&value.ReportPublicConsumption.Overall) + rpqr.Overall.Amount(value.ReportPublicConsumption.Overall.Amount.Add(value.ReportPublicConsumption.LossAdjust.Amount)) + rpqr.AdjustLoss = FromConsumptionUnit(&value.ReportPublicConsumption.LossAdjust) +} + +type ReportPooledQueryResponse struct { + SimplifiedMeterQueryResponse + Overall ConsumptionDisplay `json:"overall"` + PoolMethod int16 `json:"poolMethod"` +} + +func (rpqr *ReportPooledQueryResponse) FromReportDetailPooledConsumption(value *model.ReportDetailedPooledConsumption) { + copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.MeterDetail) + rpqr.Overall = FromConsumptionUnit(&value.ReportPooledConsumption.Overall) + rpqr.PoolMethod = value.PublicPooled +} + +func (rqpr *ReportPooledQueryResponse) FromReportDetailNestedMeterConsumption(value *model.ReportDetailNestedMeterConsumption) { + copier.Copy(&rqpr.SimplifiedMeterQueryResponse, &value.Meter) + rqpr.Overall = FromConsumptionUnit(&value.Consumption.Overall) + rqpr.PoolMethod = -1 +} + +type ReportTenementSummaryResponse struct { + SimplifiedTenementDetailResponse + Consumption decimal.Decimal `json:"consumption"` + Fee decimal.Decimal `json:"fee"` + Pooled decimal.Decimal `json:"pooled"` + Total decimal.Decimal `json:"final"` +} + +func (rtsr *ReportTenementSummaryResponse) FromReportTenement(value *model.ReportTenement) { + copier.Copy(&rtsr.SimplifiedTenementDetailResponse, &value.Detail) + fee := value.BasicFeePooled.Add(value.AdjustFeePooled).Add(value.LossFeePooled) + rtsr.Consumption = value.Overall.Amount + rtsr.Fee = fee + rtsr.Pooled = value.FinalPooled + rtsr.Total = value.FinalCharge +} + +type ReportTenementComprehensiveDetailResponse struct { + Consumption decimal.Decimal `json:"consumption"` + Fee decimal.Decimal `json:"fee"` + Price decimal.Decimal `json:"price"` + BasicPooled decimal.Decimal `json:"basicPooled"` + AdjustPooled decimal.Decimal `json:"adjustPooled"` + LossPooled decimal.Decimal `json:"lossPooled"` + PublicPooled decimal.Decimal `json:"publicPooled"` + Total decimal.Decimal `json:"total"` +} + +func (rtcdr *ReportTenementComprehensiveDetailResponse) FromReportTenement(value *model.ReportTenement) { + rtcdr.Consumption = value.Overall.Amount + rtcdr.Fee = value.Overall.Fee + rtcdr.Price = value.Overall.Price + rtcdr.BasicPooled = value.BasicFeePooled + rtcdr.AdjustPooled = value.AdjustFeePooled + rtcdr.LossPooled = value.LossFeePooled + rtcdr.PublicPooled = value.FinalPooled + rtcdr.Total = value.FinalCharge +} + +type ReportMeterDetailResponse struct { + SimplifiedMeterDetailResponse + Overall ConsumptionDisplay `json:"overall"` + Critical ConsumptionDisplay `json:"critical"` + Peak ConsumptionDisplay `json:"peak"` + Flat ConsumptionDisplay `json:"flat"` + Valley ConsumptionDisplay `json:"valley"` +} + +func (rmdr *ReportMeterDetailResponse) FromNestedMeter(value *model.NestedMeter) { + copier.Copy(&rmdr.SimplifiedMeterDetailResponse, &value.MeterDetail) + rmdr.Overall = FromConsumptionUnit(&value.Overall) + rmdr.Critical = FromConsumptionUnit(&value.Critical) + rmdr.Peak = FromConsumptionUnit(&value.Peak) + rmdr.Flat = FromConsumptionUnit(&value.Flat) + rmdr.Valley = FromConsumptionUnit(&value.Valley) +} + +type ReportMeterExtendedDetailResponse struct { + ReportMeterDetailResponse + BasicPooled decimal.Decimal `json:"basicPooled"` + AdjustPooled decimal.Decimal `json:"adjustPooled"` + LossPooled decimal.Decimal `json:"lossPooled"` + PublicPooled decimal.Decimal `json:"publicPooled"` + FinalTotal decimal.Decimal `json:"finalTotal"` +} + +func (rmedr *ReportMeterExtendedDetailResponse) FromNestedMeter(value *model.NestedMeter) { + rmedr.ReportMeterDetailResponse.FromNestedMeter(value) + rmedr.BasicPooled = value.BasicPooled + rmedr.AdjustPooled = value.AdjustPooled + rmedr.LossPooled = value.LossPooled + rmedr.PublicPooled = value.PublicPooled + rmedr.FinalTotal = value.FinalTotal +} + +type ReportTenementDetailResponse struct { + Tenement SimplifiedTenementDetailResponse `json:"tenement"` + Comprehensive ReportTenementComprehensiveDetailResponse `json:"comprehensive"` + Meters []ReportMeterExtendedDetailResponse `json:"meters"` + Pooled []ReportMeterDetailResponse `json:"pooled"` +} + +func (rtdr *ReportTenementDetailResponse) FromReportTenement(value *model.ReportTenement) { + copier.Copy(&rtdr.Tenement, &value.Detail) + rtdr.Comprehensive.FromReportTenement(value) + rtdr.Meters = make([]ReportMeterExtendedDetailResponse, len(value.Meters)) + for i, v := range value.Meters { + rtdr.Meters[i].FromNestedMeter(&v) + } + rtdr.Pooled = make([]ReportMeterDetailResponse, len(value.Pooled)) + for i, v := range value.Pooled { + rtdr.Pooled[i].FromNestedMeter(&v) + } +} diff --git a/vo/shares.go b/vo/shares.go index 92d6f95..5612904 100644 --- a/vo/shares.go +++ b/vo/shares.go @@ -1,5 +1,45 @@ package vo +import ( + "electricity_bill_calc/model" + + "github.com/jinzhu/copier" + "github.com/shopspring/decimal" +) + type StateForm struct { Enabled bool `json:"enabled"` } + +type ConsumptionDisplay struct { + AmountStr string `json:"amount"` + FeeStr string `json:"fee"` + PriceStr string `json:"price"` + ProportionStr string `json:"proportion"` +} + +func (cd ConsumptionDisplay) Amount(a decimal.Decimal) ConsumptionDisplay { + cd.AmountStr = a.StringFixedBank(4) + return cd +} + +func (cd ConsumptionDisplay) Fee(f decimal.Decimal) ConsumptionDisplay { + cd.FeeStr = f.StringFixedBank(4) + return cd +} + +func (cd ConsumptionDisplay) Price(p decimal.Decimal) ConsumptionDisplay { + cd.PriceStr = p.StringFixedBank(8) + return cd +} + +func (cd ConsumptionDisplay) Proportion(p decimal.Decimal) ConsumptionDisplay { + cd.ProportionStr = p.StringFixedBank(8) + return cd +} + +func FromConsumptionUnit(cu *model.ConsumptionUnit) ConsumptionDisplay { + cd := &ConsumptionDisplay{} + copier.Copy(cd, cu) + return *cd +} diff --git a/vo/tenement.go b/vo/tenement.go index e5524a4..5963dec 100644 --- a/vo/tenement.go +++ b/vo/tenement.go @@ -59,3 +59,17 @@ type TenementDetailResponse struct { CreatedAt types.DateTime `json:"createdAt"` LastModifiedAt *types.DateTime `json:"lastModifiedAt"` } + +type SimplifiedTenementDetailResponse 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"` + MovedInAt *types.Date `json:"movedInAt"` + MovedOutAt *types.Date `json:"movedOutAt"` +} From 062cc6a9ea74cb97ec2381d21919f169d8f6a995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 13:27:22 +0800 Subject: [PATCH 093/141] =?UTF-8?q?fix(tenement):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=95=86=E6=88=B7=E9=83=A8=E5=88=86=E5=A4=84=E7=90=86=E5=99=A8?= =?UTF-8?q?=E7=9A=84=E6=8C=82=E8=BD=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- router/router.go | 1 + 1 file changed, 1 insertion(+) diff --git a/router/router.go b/router/router.go index 1e268f6..b436fd1 100644 --- a/router/router.go +++ b/router/router.go @@ -48,6 +48,7 @@ func App() *fiber.App { controller.InitializeRegionHandlers(app) controller.InitializeChargeHandlers(app) controller.InitializeParkHandlers(app) + controller.InitializeTenementHandler(app) controller.InitializeMeterHandlers(app) controller.InitializeInvoiceHandler(app) controller.InitializeTopUpHandlers(app) From e6d9435c146c7d7606891fdd9ee9cafed085547e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 13:29:34 +0800 Subject: [PATCH 094/141] =?UTF-8?q?fix(types):=E7=8E=B0=E5=9C=A8=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E5=92=8C=E6=97=B6=E9=97=B4=E7=9A=84=E6=8C=87=E9=92=88?= =?UTF-8?q?=E5=BD=A2=E5=BC=8F=E8=A7=A3=E6=9E=90=E4=B8=8D=E5=9C=A8=E5=AF=B9?= =?UTF-8?q?=E7=A9=BA=E7=99=BD=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/date.go | 2 +- types/datetime.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/types/date.go b/types/date.go index 188b089..ebfd690 100644 --- a/types/date.go +++ b/types/date.go @@ -59,7 +59,7 @@ func ParseDate(t string) (Date, error) { func ParseDatep(t string) (*Date, error) { if len(t) == 0 { - return nil, fmt.Errorf("不能解析空白的日期时间。") + return nil, nil } for _, layout := range dateLayouts { d, err := time.ParseInLocation(layout, t, loc) diff --git a/types/datetime.go b/types/datetime.go index a1c36cc..7331040 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -81,7 +81,7 @@ func ParseDateTime(t string) (DateTime, error) { func ParseDateTimep(t string) (*DateTime, error) { if len(t) == 0 { - return nil, fmt.Errorf("不能解析空白的日期时间。") + return nil, nil } for _, layout := range datetimeLayouts { fmt.Printf("Parse: %s, Try layout: %s\n", t, layout) From 0246eaba276b98436dd3c0f4acf52a3674433d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 14:03:37 +0800 Subject: [PATCH 095/141] =?UTF-8?q?fix(tenement):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=95=86=E6=88=B7=E6=9F=A5=E8=AF=A2=E7=BB=93=E6=9E=9C=E7=9A=84?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E4=BB=A5=E5=8F=8A=E9=94=99=E8=AF=AF=E8=BF=94?= =?UTF-8?q?=E5=9B=9Enull=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/tenement.go | 2 +- model/tenement.go | 3 ++- repository/tenement.go | 6 ++++-- vo/tenement.go | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/controller/tenement.go b/controller/tenement.go index b1bd0a8..9ba138a 100644 --- a/controller/tenement.go +++ b/controller/tenement.go @@ -58,7 +58,7 @@ func listTenement(c *fiber.Ctx) error { tenementLog.Error("列出园区中的商户失败,未能获取商户列表", zap.Error(err)) return result.Error(fiber.StatusInternalServerError, err.Error()) } - var tenementsResponse []*vo.TenementQueryResponse + tenementsResponse := make([]*vo.TenementQueryResponse, 0) copier.Copy(&tenementsResponse, &tenements) return result.Success( "已经获取到要查询的商户。", diff --git a/model/tenement.go b/model/tenement.go index 16448f4..eda08f0 100644 --- a/model/tenement.go +++ b/model/tenement.go @@ -7,10 +7,11 @@ type Tenement struct { Park string `json:"parkId" db:"park_id"` FullName string `json:"fullName" db:"full_name"` ShortName *string `json:"shortName" db:"short_name"` + Abbr string `json:"-"` Address string `json:"address"` ContactName string `json:"contactName" db:"contact_name"` ContactPhone string `json:"contactPhone" db:"contact_phone"` - Building string `json:"building"` + Building *string `json:"building"` BuildingName *string `json:"buildingName" db:"building_name"` OnFloor *string `json:"onFloor" db:"on_floor"` InvoiceInfo *InvoiceTitle `json:"invoiceInfo" db:"invoice_info"` diff --git a/repository/tenement.go b/repository/tenement.go index 2f130e7..9e1e869 100644 --- a/repository/tenement.go +++ b/repository/tenement.go @@ -70,10 +70,12 @@ func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, buil tenementQuery := tr.ds. From(goqu.T("tenement").As("t")). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). - Select("t.*", goqu.I("b.name").As("building_name")) + Select("t.*", goqu.I("b.name").As("building_name")). + Where(goqu.I("t.park_id").Eq(pid)) countQuery := tr.ds. From(goqu.T("tenement").As("t")). - Select(goqu.COUNT("t.*")) + Select(goqu.COUNT("t.*")). + Where(goqu.I("t.park_id").Eq(pid)) if keyword != nil && len(*keyword) > 0 { pattern := fmt.Sprintf("%%%s%%", *keyword) diff --git a/vo/tenement.go b/vo/tenement.go index 5963dec..c5508be 100644 --- a/vo/tenement.go +++ b/vo/tenement.go @@ -22,7 +22,7 @@ type TenementCreationForm struct { type TenementQueryResponse struct { Id string `json:"id"` - Name string `json:"name"` + FullName string `json:"fullName"` ShortName *string `json:"shortName"` Address *string `json:"address"` Contact *string `json:"contact"` From aec1655f1c8f34387e041aed806706a321179db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 15:15:41 +0800 Subject: [PATCH 096/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E6=AD=A3=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=AD=97=E6=AE=B5=E6=98=A0=E5=B0=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 2 +- model/meter.go | 4 ++-- repository/meter.go | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/controller/meter.go b/controller/meter.go index a25cc18..bf42f84 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -45,7 +45,7 @@ func InitializeMeterHandlers(router *fiber.App) { // 查询指定园区下的表计信息 func searchMetersWithinPark(c *fiber.Ctx) error { - parkId := c.Params("parkId") + parkId := c.Params("pid") meterLog.Info("查询指定园区下的表计信息", zap.String("park id", parkId)) result := response.NewResult(c) if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { diff --git a/model/meter.go b/model/meter.go index d592fdc..5f10fa8 100644 --- a/model/meter.go +++ b/model/meter.go @@ -10,7 +10,7 @@ type MeterDetail struct { Code string `json:"code" db:"code"` Park string `json:"parkId" db:"park_id"` Address *string `json:"address" db:"address"` - MeterType int16 `json:"meterType" db:"meter_type"` + MeterType int16 `json:"type" db:"meter_type"` Building *string `json:"building" db:"building"` BuildingName *string `json:"buildingName" db:"building_name"` OnFloor *string `json:"onFloor" db:"on_floor" ` @@ -31,7 +31,7 @@ type MeterRelation struct { SlaveMeter string `json:"slaveMeterId" db:"slave_meter_id"` EstablishedAt types.DateTime `json:"establishedAt"` SuspendedAt *types.DateTime `json:"suspendedAt"` - RevokeAt *types.DateTime `json:"revokeAt"` + RevokedAt *types.DateTime `json:"revokedAt"` } type MeterSynchronization struct { diff --git a/repository/meter.go b/repository/meter.go index 95c11fa..c183f06 100644 --- a/repository/meter.go +++ b/repository/meter.go @@ -135,14 +135,14 @@ func (mr _MeterRepository) MetersIn(pid string, page uint, keyword *string) ([]* ). Where( goqu.I("m.park_id").Eq(pid), - goqu.I("m.detachedAt").IsNull(), + goqu.I("m.detached_at").IsNull(), ) countQuery := mr.ds. From(goqu.T("meter_04kv").As("m")). Select(goqu.COUNT("*")). Where( goqu.I("m.park_id").Eq(pid), - goqu.I("m.detachedAt").IsNull(), + goqu.I("m.detached_at").IsNull(), ) if keyword != nil && len(*keyword) > 0 { @@ -498,12 +498,12 @@ func (mr _MeterRepository) ListPooledMeterRelations(pid, code string) ([]*model. var relations []*model.MeterRelation relationsSql, relationsArgs, _ := mr.ds. - From(goqu.T("meter_relations")). - Select("*"). + From(goqu.T("meter_relations").As("r")). + Select("r.*"). Where( goqu.I("r.park_id").Eq(pid), goqu.I("r.master_meter_id").Eq(code), - goqu.I("r.revoke_at").IsNull(), + goqu.I("r.revoked_at").IsNull(), ). Prepared(true).ToSQL() From 037e6258d12d5a182cdc8102c3b1dfaca3f1cf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 15:47:25 +0800 Subject: [PATCH 097/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E6=9F=A5=E8=AF=A2=E4=B8=AD=E7=9A=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E5=85=B3=E7=B3=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 6 +++--- model/meter.go | 2 +- repository/meter.go | 23 +++++++---------------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/controller/meter.go b/controller/meter.go index bf42f84..8374ffc 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -23,19 +23,19 @@ import ( 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("/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) router.Put("/reading/:pid/:code/:reading", security.EnterpriseAuthorize, updateMeterReading) router.Get("/reading/:pid/template", security.EnterpriseAuthorize, downloadMeterReadingsTemplate) diff --git a/model/meter.go b/model/meter.go index 5f10fa8..857280c 100644 --- a/model/meter.go +++ b/model/meter.go @@ -74,7 +74,7 @@ type NestedMeter struct { type PooledMeterDetailCompound struct { MeterDetail - BindMeters []MeterDetail `json:"bindMeters"` + BindMeters []MeterDetail `json:"bindedMeters"` } // 以下结构体用于导入表计档案数据 diff --git a/repository/meter.go b/repository/meter.go index c183f06..2c01db4 100644 --- a/repository/meter.go +++ b/repository/meter.go @@ -12,7 +12,6 @@ import ( "electricity_bill_calc/types" "electricity_bill_calc/vo" "fmt" - "strings" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" @@ -477,7 +476,7 @@ func (mr _MeterRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, mast goqu.I("park_id").Eq(pid), goqu.I("master_meter_id").Eq(masterMeter), goqu.I("slave_meter_id").Eq(slaveMeter), - goqu.I("revoke_at").IsNull(), + goqu.I("revoked_at").IsNull(), ). Prepared(true).ToSQL() @@ -518,25 +517,17 @@ func (mr _MeterRepository) ListPooledMeterRelations(pid, code string) ([]*model. // 列出指定公摊表计列表所包含的全部关联表计关系 func (mr _MeterRepository) ListPooledMeterRelationsByCodes(pid string, codes []string) ([]*model.MeterRelation, error) { mr.log.Info("列出指定公摊表计列表所包含的全部关联表计关系", zap.String("park id", pid), zap.Strings("meter codes", codes)) - cacheConditions := []string{ - pid, - strings.Join(codes, ","), - } - if relations, err := cache.RetrieveSearch[[]*model.MeterRelation]("meter_relations", cacheConditions...); err == nil { - mr.log.Info("从缓存中获取到了所需的关联表计信息", zap.Int("count", len(*relations))) - return *relations, nil - } ctx, cancel := global.TimeoutContext() defer cancel() var relations []*model.MeterRelation relationsSql, relationsArgs, _ := mr.ds. - From(goqu.T("meter_relations")). - Select("*"). + From(goqu.T("meter_relations").As("r")). + Select("r.*"). Where( goqu.I("r.park_id").Eq(pid), - goqu.I("r.master_meter_id").Eq(goqu.Func("any", codes)), - goqu.I("r.revoke_at").IsNull(), + goqu.I("r.master_meter_id").In(codes), + goqu.I("r.revoked_at").IsNull(), ). Prepared(true).ToSQL() @@ -561,7 +552,7 @@ func (mr _MeterRepository) ListMeterRelations(pid, code string) ([]*model.MeterR Where( goqu.I("r.park_id").Eq(pid), goqu.I("r.slave_meter_id").Eq(code), - goqu.I("r.revoke_at").IsNull(), + goqu.I("r.revoked_at").IsNull(), ). Prepared(true).ToSQL() @@ -687,7 +678,7 @@ func (mr _MeterRepository) ListUnboundMeters(uid string, pid *string, keyword *s )) } slaveMeterQuery = slaveMeterQuery.Where( - goqu.I("revoke_at").IsNull(), + goqu.I("revoked_at").IsNull(), ) meterQuery = meterQuery.Where( goqu.I("m.code").NotIn(slaveMeterQuery), From ac36c158c02446403775ac58c3159a2cdd7e5894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 16:03:57 +0800 Subject: [PATCH 098/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E6=AD=A3=E6=9C=AA?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E8=A1=A8=E8=AE=A1=E7=9A=84=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 6 +++--- vo/meter.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/controller/meter.go b/controller/meter.go index 8374ffc..6ef5a3e 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -300,13 +300,13 @@ func listUnboundMeters(c *fiber.Ctx) error { meterLog.Error("无法列出指定园区中尚未绑定公摊表计的表计,无法获取当前用户会话", zap.Error(err)) return result.Unauthorized(err.Error()) } - parkId := c.Params("pid") - if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { + 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) + 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()) diff --git a/vo/meter.go b/vo/meter.go index 626bb89..1ab3870 100644 --- a/vo/meter.go +++ b/vo/meter.go @@ -44,7 +44,7 @@ type MeterReplacingForm struct { type SimplifiedMeterQueryResponse struct { Code string `json:"code"` Address *string `json:"address"` - Park string `json:"parkId"` + Park string `json:"park"` } type SimplifiedMeterDetailResponse struct { From b224f516136389d8f53c437cd63028706954963f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 16:22:53 +0800 Subject: [PATCH 099/141] =?UTF-8?q?fix(meter):=E5=9C=A8=E6=8C=89=E7=85=A7?= =?UTF-8?q?=E7=BC=96=E5=8F=B7=E5=88=97=E8=A1=A8=E8=8E=B7=E5=8F=96=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E8=AF=A6=E7=BB=86=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E8=A1=A8=E8=AE=A1=E7=BC=96=E5=8F=B7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=98=AF=E7=A9=BA=E7=9A=84=E9=98=B2=E5=BE=A1=E6=8E=AA?= =?UTF-8?q?=E6=96=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/meter.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/repository/meter.go b/repository/meter.go index 2c01db4..c0d3a00 100644 --- a/repository/meter.go +++ b/repository/meter.go @@ -185,6 +185,9 @@ func (mr _MeterRepository) MetersIn(pid string, page uint, keyword *string) ([]* // 列出指定园区中指定列表中所有表计的详细信息,将忽略所有表计的当前状态 func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.MeterDetail, error) { mr.log.Info("列出指定园区中指定列表中所有表计的详细信息", zap.String("park id", pid), zap.Strings("meter ids", ids)) + if len(ids) == 0 { + return make([]*model.MeterDetail, 0), nil + } ctx, cancel := global.TimeoutContext() defer cancel() From 877b8304c3bb1314da08a059b5fae28b129d89d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 16:54:26 +0800 Subject: [PATCH 100/141] =?UTF-8?q?fix(report):=E7=A1=AE=E5=AE=9AConsumpti?= =?UTF-8?q?onUnit=E5=AF=B9=E5=BA=94=E7=9A=84ConsumptionDisplay=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=BB=93=E6=9E=84=E7=9A=84=E8=87=AA=E5=8A=A8=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E6=A8=A1=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vo/park.go | 2 +- vo/report.go | 42 +++++++++++++++++++++++------------------- vo/shares.go | 14 ++++++-------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/vo/park.go b/vo/park.go index cbcc8a6..209f066 100644 --- a/vo/park.go +++ b/vo/park.go @@ -70,7 +70,7 @@ type ParkBuildingInformationForm struct { type SimplifiedParkDetail struct { Id string `json:"id"` - UserId string `json:"user_id"` + UserId string `json:"userId"` Name string `json:"name"` TenementStr *string `json:"tenement"` AreaStr *string `json:"area"` diff --git a/vo/report.go b/vo/report.go index 6ad2f5f..941bd15 100644 --- a/vo/report.go +++ b/vo/report.go @@ -75,9 +75,9 @@ type ComprehensiveReportQueryResponse struct { type BasicReportIndexResponse struct { Id string `json:"id"` - Park string `json:"park_id"` - PeriodBegin types.Date `json:"period_begin"` - PeriodEnd types.Date `json:"period_end"` + Park string `json:"parkId"` + PeriodBegin types.Date `json:"periodBegin"` + PeriodEnd types.Date `json:"periodEnd"` Category int16 `json:"category"` MeterType int16 `json:"meter04kvType"` PricePolicy int16 `json:"pricePolicy"` @@ -86,14 +86,14 @@ type BasicReportIndexResponse struct { LossPooled int16 `json:"lossDiluted"` PublicPooled int16 `json:"publicDiluted"` Published bool `json:"published"` - PublishedAt *types.DateTime `json:"published_at"` + PublishedAt *types.DateTime `json:"publishedAt"` Withdraw int16 `json:"withdraw"` - LastWithdrawAppliedAt *types.DateTime `json:"last_withdraw_applied_at"` - LastWithdrawAuditAt *types.DateTime `json:"last_withdraw_audit_at"` + LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt"` + LastWithdrawAuditAt *types.DateTime `json:"lastWithdrawAuditAt"` Status int16 `json:"status"` Message *string `json:"message"` - CreatedAt types.DateTime `json:"created_at"` - LastModifiedAt types.DateTime `json:"last_modified_at"` + CreatedAt types.DateTime `json:"createdAt"` + LastModifiedAt types.DateTime `json:"lastModifiedAt"` } func (bri *BasicReportIndexResponse) Period(p types.DateRange) { @@ -116,8 +116,8 @@ func NewReportDetailQueryResponse(user *model.UserDetail, park *model.Park, repo } type ParkSummaryResponse struct { - Id string `json:"id"` - Overall ConsumptionDisplay `json:"overall"` + ReportId string `json:"reportId"` + OverallDisplay ConsumptionDisplay `json:"overall"` Area decimal.Decimal `json:"area"` BasicFee decimal.Decimal `json:"basicFee"` PooledBasicFeeByAmount decimal.Decimal `json:"pooledBasicFeeByAmount"` @@ -130,6 +130,10 @@ type ParkSummaryResponse struct { LossRate decimal.Decimal `json:"lossRate"` } +func (psr *ParkSummaryResponse) Overall(value model.ConsumptionUnit) { + psr.OverallDisplay.FromConsumptionUnit(&value) +} + type SimplifiedReportSummary struct { Overall model.ConsumptionUnit `json:"overall"` Critical model.ConsumptionUnit `json:"critical"` @@ -200,9 +204,9 @@ type ReportPublicQueryResponse struct { func (rpqr *ReportPublicQueryResponse) FromReportDetailPublicConsumption(value *model.ReportDetailedPublicConsumption) { copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.MeterDetail) - rpqr.Overall = FromConsumptionUnit(&value.ReportPublicConsumption.Overall) + rpqr.Overall.FromConsumptionUnit(&value.ReportPublicConsumption.Overall) rpqr.Overall.Amount(value.ReportPublicConsumption.Overall.Amount.Add(value.ReportPublicConsumption.LossAdjust.Amount)) - rpqr.AdjustLoss = FromConsumptionUnit(&value.ReportPublicConsumption.LossAdjust) + rpqr.AdjustLoss.FromConsumptionUnit(&value.ReportPublicConsumption.LossAdjust) } type ReportPooledQueryResponse struct { @@ -213,13 +217,13 @@ type ReportPooledQueryResponse struct { func (rpqr *ReportPooledQueryResponse) FromReportDetailPooledConsumption(value *model.ReportDetailedPooledConsumption) { copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.MeterDetail) - rpqr.Overall = FromConsumptionUnit(&value.ReportPooledConsumption.Overall) + rpqr.Overall.FromConsumptionUnit(&value.ReportPooledConsumption.Overall) rpqr.PoolMethod = value.PublicPooled } func (rqpr *ReportPooledQueryResponse) FromReportDetailNestedMeterConsumption(value *model.ReportDetailNestedMeterConsumption) { copier.Copy(&rqpr.SimplifiedMeterQueryResponse, &value.Meter) - rqpr.Overall = FromConsumptionUnit(&value.Consumption.Overall) + rqpr.Overall.FromConsumptionUnit(&value.Consumption.Overall) rqpr.PoolMethod = -1 } @@ -273,11 +277,11 @@ type ReportMeterDetailResponse struct { func (rmdr *ReportMeterDetailResponse) FromNestedMeter(value *model.NestedMeter) { copier.Copy(&rmdr.SimplifiedMeterDetailResponse, &value.MeterDetail) - rmdr.Overall = FromConsumptionUnit(&value.Overall) - rmdr.Critical = FromConsumptionUnit(&value.Critical) - rmdr.Peak = FromConsumptionUnit(&value.Peak) - rmdr.Flat = FromConsumptionUnit(&value.Flat) - rmdr.Valley = FromConsumptionUnit(&value.Valley) + rmdr.Overall.FromConsumptionUnit(&value.Overall) + rmdr.Critical.FromConsumptionUnit(&value.Critical) + rmdr.Peak.FromConsumptionUnit(&value.Peak) + rmdr.Flat.FromConsumptionUnit(&value.Flat) + rmdr.Valley.FromConsumptionUnit(&value.Valley) } type ReportMeterExtendedDetailResponse struct { diff --git a/vo/shares.go b/vo/shares.go index 5612904..19c7e16 100644 --- a/vo/shares.go +++ b/vo/shares.go @@ -3,7 +3,6 @@ package vo import ( "electricity_bill_calc/model" - "github.com/jinzhu/copier" "github.com/shopspring/decimal" ) @@ -18,28 +17,27 @@ type ConsumptionDisplay struct { ProportionStr string `json:"proportion"` } -func (cd ConsumptionDisplay) Amount(a decimal.Decimal) ConsumptionDisplay { +func (cd *ConsumptionDisplay) Amount(a decimal.Decimal) *ConsumptionDisplay { cd.AmountStr = a.StringFixedBank(4) return cd } -func (cd ConsumptionDisplay) Fee(f decimal.Decimal) ConsumptionDisplay { +func (cd *ConsumptionDisplay) Fee(f decimal.Decimal) *ConsumptionDisplay { cd.FeeStr = f.StringFixedBank(4) return cd } -func (cd ConsumptionDisplay) Price(p decimal.Decimal) ConsumptionDisplay { +func (cd *ConsumptionDisplay) Price(p decimal.Decimal) *ConsumptionDisplay { cd.PriceStr = p.StringFixedBank(8) return cd } -func (cd ConsumptionDisplay) Proportion(p decimal.Decimal) ConsumptionDisplay { +func (cd *ConsumptionDisplay) Proportion(p decimal.Decimal) *ConsumptionDisplay { cd.ProportionStr = p.StringFixedBank(8) return cd } -func FromConsumptionUnit(cu *model.ConsumptionUnit) ConsumptionDisplay { - cd := &ConsumptionDisplay{} - copier.Copy(cd, cu) +func (cd *ConsumptionDisplay) FromConsumptionUnit(cu *model.ConsumptionUnit) ConsumptionDisplay { + cd.Amount(cu.Amount).Fee(cu.Fee).Price(cu.Price).Proportion(cu.Proportion) return *cd } From 7806f07766600bb94bf1622850bc772e6a0f4056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 26 Jun 2023 21:52:17 +0800 Subject: [PATCH 101/141] =?UTF-8?q?fix(report):=E4=BF=AE=E6=AD=A3=E6=8A=A5?= =?UTF-8?q?=E8=A1=A8=E4=B8=AD=E8=8E=B7=E5=8F=96=E5=88=86=E6=91=8A=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E5=BA=94=E8=8E=B7=E5=8F=96=E4=BD=86=E6=9C=AA=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=9A=84=E5=AD=97=E6=AE=B5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/report.go | 2 +- repository/report.go | 6 +++--- vo/report.go | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/model/report.go b/model/report.go index fb296d2..2acabee 100644 --- a/model/report.go +++ b/model/report.go @@ -80,7 +80,7 @@ type ReportDetailedPublicConsumption struct { type ReportPooledConsumption struct { ReportId string `json:"reportId" db:"report_id"` - MeterId string `json:"parkMeterId" db:"park_meter_id"` + MeterId string `json:"pooledMeterId" db:"pooled_meter_id"` Overall ConsumptionUnit `json:"overall"` Critical ConsumptionUnit `json:"critical"` Peak ConsumptionUnit `json:"peak"` diff --git a/repository/report.go b/repository/report.go index 9cf928f..a157be0 100644 --- a/repository/report.go +++ b/repository/report.go @@ -396,16 +396,16 @@ func (rr _ReportRepository) ListPooledMetersInReport(rid string, page uint, keyw reportQuery := rr.ds. From(goqu.T("report_pooled_consumption").As("r")). - Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))). + Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.pooled_meter_id")))). Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))). Select( - goqu.I("r.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"), + goqu.I("r.*"), goqu.I("m.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"), ). Where(goqu.I("r.report_id").Eq(rid)) countQuery := rr.ds. From(goqu.T("report_pooled_consumption").As("r")). - Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))). + Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.pooled_meter_id")))). Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))). Select(goqu.COUNT(goqu.I("r.*"))). diff --git a/vo/report.go b/vo/report.go index 941bd15..bd8892c 100644 --- a/vo/report.go +++ b/vo/report.go @@ -210,9 +210,9 @@ func (rpqr *ReportPublicQueryResponse) FromReportDetailPublicConsumption(value * } type ReportPooledQueryResponse struct { - SimplifiedMeterQueryResponse - Overall ConsumptionDisplay `json:"overall"` - PoolMethod int16 `json:"poolMethod"` + SimplifiedMeterQueryResponse `copier:"MeterDetail"` + Overall ConsumptionDisplay `json:"overall"` + PoolMethod int16 `json:"poolMethod"` } func (rpqr *ReportPooledQueryResponse) FromReportDetailPooledConsumption(value *model.ReportDetailedPooledConsumption) { @@ -221,10 +221,10 @@ func (rpqr *ReportPooledQueryResponse) FromReportDetailPooledConsumption(value * rpqr.PoolMethod = value.PublicPooled } -func (rqpr *ReportPooledQueryResponse) FromReportDetailNestedMeterConsumption(value *model.ReportDetailNestedMeterConsumption) { - copier.Copy(&rqpr.SimplifiedMeterQueryResponse, &value.Meter) - rqpr.Overall.FromConsumptionUnit(&value.Consumption.Overall) - rqpr.PoolMethod = -1 +func (rpqr *ReportPooledQueryResponse) FromReportDetailNestedMeterConsumption(value *model.ReportDetailNestedMeterConsumption) { + copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.Meter) + rpqr.Overall.FromConsumptionUnit(&value.Consumption.Overall) + rpqr.PoolMethod = -1 } type ReportTenementSummaryResponse struct { From b4ce754c0d44af1abb13fb394b0837fbc7304059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 10:58:11 +0800 Subject: [PATCH 102/141] =?UTF-8?q?fix(report):=E4=BF=AE=E6=AD=A3=E4=B8=80?= =?UTF-8?q?=E7=B3=BB=E5=88=97=E6=8A=A5=E8=A1=A8=E6=9F=A5=E8=AF=A2=E4=B8=AD?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E7=9A=84=E6=97=A0=E6=B3=95=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/report.go | 6 +++--- model/park.go | 1 + model/report.go | 1 + repository/report.go | 2 +- repository/user.go | 4 ++-- vo/meter.go | 4 ++-- vo/report.go | 14 +++++++------- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/controller/report.go b/controller/report.go index d22d9cd..a99cabe 100644 --- a/controller/report.go +++ b/controller/report.go @@ -20,7 +20,7 @@ import ( var reportLog = logger.Named("Handler", "Report") func InitializeReportHandlers(router *fiber.App) { - router.Get("/repotrs", security.MustAuthenticated, reportComprehensiveSearch) + router.Get("/reports", security.MustAuthenticated, reportComprehensiveSearch) router.Post("/report", security.EnterpriseAuthorize, initNewReportCalculateTask) router.Get("/report/draft", security.EnterpriseAuthorize, listDraftReportIndicies) router.Post("/report/calcualte", security.EnterpriseAuthorize, testCalculateReportSummary) @@ -33,7 +33,7 @@ func InitializeReportHandlers(router *fiber.App) { router.Get("/report/:rid/summary", security.MustAuthenticated, getReportSummary) router.Get("/report/:rid/summary/filled", security.EnterpriseAuthorize, getParkFilledSummary) router.Get("/report/:rid/pooled", security.MustAuthenticated, listPooledMetersInReport) - router.Get("/report/:rid/:code/submeters", security.MustAuthenticated, listSubmetersInPooledMeter) + router.Get("/report/:rid/pooled/:code/submeter", security.MustAuthenticated, listSubmetersInPooledMeter) router.Get("/report/:rid/tenement", security.MustAuthenticated, listTenementsInReport) router.Get("/report/:rid/tenement/:tid", security.MustAuthenticated, getTenementDetailInReport) } @@ -198,7 +198,7 @@ func listCalculateTaskStatus(c *fiber.Ctx) error { reportLog.Error("无法获取核算报表计算状态", zap.Error(err)) return result.Error(fiber.StatusInternalServerError, "无法获取核算报表计算状态。") } - var statusResponse []*vo.ReportCalculateTaskStatusResponse + statusResponse := make([]*vo.ReportCalculateTaskStatusResponse, 0) copier.Copy(&statusResponse, &status) return result.Success( "已经获取到核算报表计算状态。", diff --git a/model/park.go b/model/park.go index b3af1fb..9dfd190 100644 --- a/model/park.go +++ b/model/park.go @@ -10,6 +10,7 @@ type Park struct { Id string `json:"id"` UserId string `json:"userId"` Name string `json:"name"` + Abbr string `json:"-"` Area decimal.NullDecimal `json:"area"` TenementQuantity decimal.NullDecimal `json:"tenement"` Capacity decimal.NullDecimal `json:"capacity"` diff --git a/model/report.go b/model/report.go index 2acabee..2972647 100644 --- a/model/report.go +++ b/model/report.go @@ -71,6 +71,7 @@ type ReportPublicConsumption struct { ConsumptionTotal decimal.Decimal `json:"consumptionTotal" db:"consumption_total"` LossAdjustTotal decimal.Decimal `json:"lossAdjustTotal" db:"loss_adjust_total"` FinalTotal decimal.Decimal `json:"finalTotal" db:"final_total"` + PublicPooled int16 `json:"publicPooled" db:"public_pooled"` } type ReportDetailedPublicConsumption struct { diff --git a/repository/report.go b/repository/report.go index a157be0..a881c97 100644 --- a/repository/report.go +++ b/repository/report.go @@ -530,7 +530,7 @@ func (rr _ReportRepository) ListTenementInReport(rid string, page uint, keyword startRow := (page - 1) * config.ServiceSettings.ItemsPageSize reportQuery = reportQuery. - Order(goqu.I("t.movedin_at").Asc()). + Order(goqu.I("t.moved_in_at").Asc()). Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize) var ( diff --git a/repository/user.go b/repository/user.go index d54d197..ce14bd8 100644 --- a/repository/user.go +++ b/repository/user.go @@ -408,10 +408,10 @@ func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]*model.UserDetai var users []*model.UserDetail userQuery := ur.ds. From("user_detail"). - Where(goqu.Ex{"id": goqu.Any(uids)}) + Where(goqu.Ex{"id": uids}) userSql, userParams, _ := userQuery.Prepared(true).ToSQL() - if err := pgxscan.Select(ctx, global.DB, users, userSql, userParams...); err != nil { + if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) return make([]*model.UserDetail, 0), err } diff --git a/vo/meter.go b/vo/meter.go index 1ab3870..d1ee990 100644 --- a/vo/meter.go +++ b/vo/meter.go @@ -44,7 +44,7 @@ type MeterReplacingForm struct { type SimplifiedMeterQueryResponse struct { Code string `json:"code"` Address *string `json:"address"` - Park string `json:"park"` + Park string `json:"parkId"` } type SimplifiedMeterDetailResponse struct { @@ -58,7 +58,7 @@ type SimplifiedMeterDetailResponse struct { OnFloor *string `json:"onFloor"` Area decimal.Decimal `json:"area"` Enabled bool `json:"enabled"` - MeterType int16 `json:"meterType"` + MeterType int16 `json:"type"` AttachedAt types.DateTime `json:"attachedAt"` DetachedAt *types.DateTime `json:"detachedAt"` } diff --git a/vo/report.go b/vo/report.go index bd8892c..0615a57 100644 --- a/vo/report.go +++ b/vo/report.go @@ -197,32 +197,32 @@ type ReportCalculateTaskStatusResponse struct { } type ReportPublicQueryResponse struct { - SimplifiedMeterQueryResponse + SimplifiedMeterDetailResponse Overall ConsumptionDisplay `json:"overall"` AdjustLoss ConsumptionDisplay `json:"adjustLoss"` } func (rpqr *ReportPublicQueryResponse) FromReportDetailPublicConsumption(value *model.ReportDetailedPublicConsumption) { - copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.MeterDetail) + copier.Copy(&rpqr.SimplifiedMeterDetailResponse, &value.MeterDetail) rpqr.Overall.FromConsumptionUnit(&value.ReportPublicConsumption.Overall) rpqr.Overall.Amount(value.ReportPublicConsumption.Overall.Amount.Add(value.ReportPublicConsumption.LossAdjust.Amount)) rpqr.AdjustLoss.FromConsumptionUnit(&value.ReportPublicConsumption.LossAdjust) } type ReportPooledQueryResponse struct { - SimplifiedMeterQueryResponse `copier:"MeterDetail"` - Overall ConsumptionDisplay `json:"overall"` - PoolMethod int16 `json:"poolMethod"` + SimplifiedMeterDetailResponse + Overall ConsumptionDisplay `json:"overall"` + PoolMethod int16 `json:"poolMethod"` } func (rpqr *ReportPooledQueryResponse) FromReportDetailPooledConsumption(value *model.ReportDetailedPooledConsumption) { - copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.MeterDetail) + copier.Copy(&rpqr.SimplifiedMeterDetailResponse, &value.MeterDetail) rpqr.Overall.FromConsumptionUnit(&value.ReportPooledConsumption.Overall) rpqr.PoolMethod = value.PublicPooled } func (rpqr *ReportPooledQueryResponse) FromReportDetailNestedMeterConsumption(value *model.ReportDetailNestedMeterConsumption) { - copier.Copy(&rpqr.SimplifiedMeterQueryResponse, &value.Meter) + copier.Copy(&rpqr.SimplifiedMeterDetailResponse, &value.Meter) rpqr.Overall.FromConsumptionUnit(&value.Consumption.Overall) rpqr.PoolMethod = -1 } From a626869f148c1332dfaf2d203e2bb65965b8fe46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 13:18:00 +0800 Subject: [PATCH 103/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E6=AD=A3=E5=AF=B9?= =?UTF-8?q?=E4=BA=8E=E8=A1=A8=E8=AE=A1=E5=88=9B=E5=BB=BA=E5=92=8C=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=A1=A8=E5=8D=95=E4=B8=AD=E7=9A=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E7=9A=84=E6=8D=95=E8=8E=B7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vo/meter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vo/meter.go b/vo/meter.go index d1ee990..eaedcbb 100644 --- a/vo/meter.go +++ b/vo/meter.go @@ -11,7 +11,7 @@ type MeterCreationForm struct { Address *string `json:"address"` Ratio decimal.Decimal `json:"ratio"` Seq int64 `json:"seq"` - MeterType int16 `json:"meterType"` + MeterType int16 `json:"type"` Building *string `json:"building"` OnFloor *string `json:"onFloor"` Area decimal.NullDecimal `json:"area"` @@ -24,7 +24,7 @@ type MeterModificationForm struct { Seq int64 `json:"seq"` Ratio decimal.Decimal `json:"ratio"` Enabled bool `json:"enabled"` - MeterType int16 `json:"meterType"` + MeterType int16 `json:"type"` Building *string `json:"building"` OnFloor *string `json:"onFloor"` Area decimal.NullDecimal `json:"area"` From a1a72b7204e7f0bb61ab75815019d5279a60cc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 14:22:43 +0800 Subject: [PATCH 104/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E5=A4=8D=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=AF=BC=E5=85=A5=E8=A1=A8=E8=AE=A1=E6=97=B6=E7=9A=84?= =?UTF-8?q?SQL=E8=AF=AD=E5=8F=A5=E5=92=8C=E6=95=B0=E6=8D=AE=E5=A4=84?= =?UTF-8?q?=E7=90=86=E8=A1=8C=E4=B8=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/meter.go | 2 +- router/router.go | 2 +- service/meter.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/repository/meter.go b/repository/meter.go index c0d3a00..e5c818d 100644 --- a/repository/meter.go +++ b/repository/meter.go @@ -281,7 +281,7 @@ func (mr _MeterRepository) CreateOrUpdateMeter(tx pgx.Tx, ctx context.Context, p }, ). OnConflict( - goqu.DoUpdate("meter_04kv_pkey", + goqu.DoUpdate("code, park_id", goqu.Record{ "address": goqu.I("excluded.address"), "seq": goqu.I("excluded.seq"), diff --git a/router/router.go b/router/router.go index b436fd1..fe8c98f 100644 --- a/router/router.go +++ b/router/router.go @@ -26,7 +26,7 @@ func init() { func App() *fiber.App { app := fiber.New(fiber.Config{ - BodyLimit: 10 * 1024 * 1024, + BodyLimit: 30 * 1024 * 1024, EnablePrintRoutes: true, EnableTrustedProxyCheck: false, Prefork: false, diff --git a/service/meter.go b/service/meter.go index bea9bb1..ff3be35 100644 --- a/service/meter.go +++ b/service/meter.go @@ -155,7 +155,7 @@ func (ms _MeterService) BatchImportMeters(pid string, file *multipart.FileHeader // 步骤2.1:获取表计档案中出现的所有建筑,并对档案中新出现的建筑进行创建操作 unexistsBuildingNames := make([]string, 0) for _, record := range records { - if !lo.Contains(buildingNames, *record.Building) { + if record.Building != nil && !lo.Contains(buildingNames, *record.Building) { unexistsBuildingNames = append(unexistsBuildingNames, *record.Building) } } @@ -186,7 +186,7 @@ func (ms _MeterService) BatchImportMeters(pid string, file *multipart.FileHeader // 步骤2.3:检测并替换表计档案中的建筑ID for _, record := range records { for _, building := range buildings { - if building.Name == *record.Building { + if record.Building != nil && building.Name == *record.Building { record.Building = &building.Id break } From 0c389e440a6205f5f62b3f92cf8869e0a127a880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 15:05:04 +0800 Subject: [PATCH 105/141] =?UTF-8?q?fix(tenement):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=95=86=E6=88=B7=E4=B8=AD=E5=B8=A6=E6=9C=89JSONB=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E7=9A=84=E6=8F=92=E5=85=A5=E8=AF=AD=E5=8F=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/tenement.go | 59 +++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/repository/tenement.go b/repository/tenement.go index 9e1e869..e3c1339 100644 --- a/repository/tenement.go +++ b/repository/tenement.go @@ -197,40 +197,33 @@ func (tr _TenementRepository) AddTenement(tx pgx.Tx, ctx context.Context, pid st serial.StringSerialRequestChan <- 1 tenementId := serial.Prefix("T", <-serial.StringSerialResponseChan) currentTime := types.Now() - createSql, createArgs, _ := tr.ds. - Insert("tenement"). - Cols( - "id", "park_id", "full_name", "short_name", "abbr", "address", "contact_name", "contact_phone", - "building", "on_floor", "invoice_info", - "moved_in_at", "created_at", "last_modified_at", - ). - Vals( - goqu.Vals{ - tenementId, - pid, - tenement.Name, - tenement.ShortName, - tools.PinyinAbbr(tenement.Name), - tenement.Address, - tenement.Contact, - tenement.Phone, - tenement.Building, - tenement.OnFloor, - &model.InvoiceTitle{ - Name: tenement.Name, - USCI: tenement.USCI, - Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""), - Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""), - Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""), - Account: tools.DefaultOrEmptyStr(tenement.Account, ""), - }, - currentTime, - currentTime, - currentTime, + if _, err := tx.Exec( + ctx, + "INSERT INTO tenement (id, park_id, full_name, short_name, abbr, address, contact_name, contact_phone, building, on_floor, invoice_info, moved_in_at, created_at, last_modified_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)", + []interface{}{ + tenementId, + pid, + tenement.Name, + tenement.ShortName, + tools.PinyinAbbr(tenement.Name), + tenement.Address, + tenement.Contact, + tenement.Phone, + tenement.Building, + tenement.OnFloor, + &model.InvoiceTitle{ + Name: tenement.Name, + USCI: tenement.USCI, + Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""), + Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""), + Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""), + Account: tools.DefaultOrEmptyStr(tenement.Account, ""), }, - ). - Prepared(true).ToSQL() - if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil { + currentTime, + currentTime, + currentTime, + }..., + ); err != nil { tr.log.Error("在指定园区中创建一个新的商户失败", zap.Error(err)) return err } From 4df3efacd8845633ad94a8e8fb8641ae13be96ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 15:11:46 +0800 Subject: [PATCH 106/141] =?UTF-8?q?fix(tenement):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=88=97=E4=B8=BE=E5=95=86=E6=88=B7=E6=97=B6=E7=9A=84=E8=BF=81?= =?UTF-8?q?=E5=85=A5=E8=BF=81=E5=87=BA=E6=9D=A1=E4=BB=B6=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/tenement.go | 2 +- repository/tenement.go | 13 ++++++++++--- vo/tenement.go | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/controller/tenement.go b/controller/tenement.go index 9ba138a..a77a902 100644 --- a/controller/tenement.go +++ b/controller/tenement.go @@ -52,7 +52,7 @@ func listTenement(c *fiber.Ctx) error { tenementLog.Error("列出园区中的商户失败,未能解析查询结束日期", zap.Error(err)) return result.BadRequest(err.Error()) } - state := tools.EmptyToNil(c.Query("state")) + state := c.QueryInt("state", 0) tenements, total, err := repository.TenementRepository.ListTenements(parkId, uint(page), keyword, building, startDate, endDate, state) if err != nil { tenementLog.Error("列出园区中的商户失败,未能获取商户列表", zap.Error(err)) diff --git a/repository/tenement.go b/repository/tenement.go index e3c1339..1d09ff3 100644 --- a/repository/tenement.go +++ b/repository/tenement.go @@ -53,7 +53,7 @@ func (tr _TenementRepository) IsTenementBelongs(tid, uid string) (bool, error) { } // 列出指定园区中的所有商户 -func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, building *string, startDate, endDate *types.Date, state *string) ([]*model.Tenement, int64, error) { +func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, building *string, startDate, endDate *types.Date, state int) ([]*model.Tenement, int64, error) { tr.log.Info( "检索查询指定园区中符合条件的商户", zap.String("Park", pid), @@ -62,7 +62,7 @@ func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, buil zap.Stringp("Building", building), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), - zap.Stringp("State", state), + zap.Int("State", state), ) ctx, cancel := global.TimeoutContext() defer cancel() @@ -136,13 +136,20 @@ func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, buil ) } - if state != nil && *state == "1" { + if state == 0 { tenementQuery = tenementQuery.Where( goqu.I("t.moved_out_at").IsNull(), ) countQuery = countQuery.Where( goqu.I("t.moved_out_at").IsNull(), ) + } else { + tenementQuery = tenementQuery.Where( + goqu.I("t.moved_out_at").IsNotNull(), + ) + countQuery = countQuery.Where( + goqu.I("t.moved_out_at").IsNotNull(), + ) } startRow := (page - 1) * config.ServiceSettings.ItemsPageSize diff --git a/vo/tenement.go b/vo/tenement.go index c5508be..1cc743f 100644 --- a/vo/tenement.go +++ b/vo/tenement.go @@ -25,8 +25,8 @@ type TenementQueryResponse struct { FullName string `json:"fullName"` ShortName *string `json:"shortName"` Address *string `json:"address"` - Contact *string `json:"contact"` - Phone *string `json:"phone"` + Contact *string `json:"contact" copier:"ContactName"` + Phone *string `json:"phone" copier:"ContactPhone"` Building *string `json:"building"` BuildingName *string `json:"buildingName"` OnFloor *string `json:"onFloor"` From 0a9d5cd1213a94e3962eb7b3c25558716b032364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 15:28:11 +0800 Subject: [PATCH 107/141] =?UTF-8?q?fix(tenement):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E8=A1=A8=E8=AE=A1=E5=9C=A8=E6=8A=84=E8=A1=A8=E6=97=B6=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E6=95=B0=E6=8D=AE=E5=90=88=E7=90=86=E6=80=A7?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/tenement.go | 4 ++-- vo/reading.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/controller/tenement.go b/controller/tenement.go index a77a902..04dab4c 100644 --- a/controller/tenement.go +++ b/controller/tenement.go @@ -27,8 +27,8 @@ func InitializeTenementHandler(router *fiber.App) { 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) + router.Post("/tenement/:pid/:tid/binding", security.EnterpriseAuthorize, bindMeterToTenement) + router.Post("/tenement/:pid/:tid/binding/:code/unbind", security.EnterpriseAuthorize, unbindMeterFromTenement) } // 列出园区中的商户 diff --git a/vo/reading.go b/vo/reading.go index 0826d01..1cbf30c 100644 --- a/vo/reading.go +++ b/vo/reading.go @@ -19,7 +19,7 @@ type MeterReadingForm struct { func (r MeterReadingForm) Validate() bool { flat := r.Overall.Sub(r.Critical).Sub(r.Peak).Sub(r.Valley) - return flat.LessThan(decimal.Zero) + return flat.GreaterThanOrEqual(decimal.Zero) } type MeterReadingFormWithCode struct { From 26a951f9706cd268e5103aee01f63fad63cd2fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 15:37:40 +0800 Subject: [PATCH 108/141] =?UTF-8?q?fix(serial):=E4=BF=AE=E6=AD=A3=E9=9B=AA?= =?UTF-8?q?=E8=8A=B1ID=E7=94=9F=E6=88=90=E7=AE=97=E6=B3=95=E4=B8=AD?= =?UTF-8?q?=E4=B8=8D=E8=83=BD=E4=BF=9D=E5=AD=98=E6=9C=80=E5=90=8E=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8F=B7=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/serial/algorithm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/serial/algorithm.go b/tools/serial/algorithm.go index 8bd08ec..91858e8 100644 --- a/tools/serial/algorithm.go +++ b/tools/serial/algorithm.go @@ -32,7 +32,7 @@ func init() { if timestamp != lastTimestamp { lastSerial = 0 } - lastSerial := lastSerial + 1 + lastSerial = lastSerial + 1 uniqueId := generateSerial(timestamp, lastSerial) SerialResponseChan <- uniqueId lastTimestamp = timestamp @@ -42,7 +42,7 @@ func init() { if timestamp != lastTimestamp { lastSerial = 0 } - lastSerial := lastSerial + 1 + lastSerial = lastSerial + 1 uniqueId := generateStringSerial(timestamp, lastSerial) StringSerialResponseChan <- uniqueId lastTimestamp = timestamp From 3ab505e446bbdfded785f9c6d7f2b551f89436d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 16:21:09 +0800 Subject: [PATCH 109/141] =?UTF-8?q?fix(meter):=E4=BF=AE=E6=AD=A3=E5=85=AC?= =?UTF-8?q?=E6=91=8A=E8=A1=A8=E8=AE=A1=E7=BB=91=E5=AE=9A=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E8=A1=A8=E8=AE=A1=E7=BC=96=E5=8F=B7=E7=9A=84?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/meter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/meter.go b/controller/meter.go index 6ef5a3e..eb34fcd 100644 --- a/controller/meter.go +++ b/controller/meter.go @@ -253,7 +253,7 @@ func unbindAssociatedMeters(c *fiber.Ctx) error { if pass, err := checkParkBelongs(parkId, meterLog, c, &result); !pass { return err } - masterMeter := c.Params("master") + masterMeter := c.Params("code") slaveMeter := c.Params("slave") if len(masterMeter) == 0 || len(slaveMeter) == 0 { meterLog.Warn("无法解除指定园区下两个表计之间的关联关系,表计编号为空。") From ccc6cac4df4a0e8a3fd740b669c2f9953516089f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 16:24:28 +0800 Subject: [PATCH 110/141] =?UTF-8?q?fix(topup):=E4=BF=AE=E6=AD=A3=E5=95=86?= =?UTF-8?q?=E6=88=B7=E5=85=85=E5=80=BC=E9=83=A8=E5=88=86=E7=9A=84=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2SQL=E8=AF=AD=E5=8F=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/top_up.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repository/top_up.go b/repository/top_up.go index 1047c11..9597936 100644 --- a/repository/top_up.go +++ b/repository/top_up.go @@ -35,13 +35,13 @@ func (tur _TopUpRepository) ListTopUps(pid string, startDate, endDate *types.Dat topUpQuery := tur.ds. From(goqu.T("tenement_top_ups").As("t")). LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))). - LeftJoin(goqu.T("meter").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))). + LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))). Select("t.*", goqu.I("te.full_name").As("tenement_name"), goqu.I("m.address").As("meter_address")). Where(goqu.I("t.park_id").Eq(pid)) countQuery := tur.ds. From(goqu.T("tenement_top_ups").As("t")). LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))). - LeftJoin(goqu.T("meter").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))). + LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))). Select(goqu.COUNT("t.*")). Where(goqu.I("t.park_id").Eq(pid)) From 06f86e3cd45398e99a405f8ca3cf5d3fbdae6c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 16:36:10 +0800 Subject: [PATCH 111/141] =?UTF-8?q?fix(tenement):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=95=86=E6=88=B7=E4=B8=8B=E6=8B=89=E5=88=97=E8=A1=A8=E6=A3=80?= =?UTF-8?q?=E7=B4=A2=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/tenement.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repository/tenement.go b/repository/tenement.go index 1d09ff3..beaffa7 100644 --- a/repository/tenement.go +++ b/repository/tenement.go @@ -359,7 +359,7 @@ func (tr _TenementRepository) ListForSelect(uid string, pid, keyword *string, li tenementQuery := tr.ds. From(goqu.T("tenement").As("t")). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). - Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("b.park_id")))). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("t.park_id")))). Select( "t.*", goqu.I("b.name").As("building_name"), ). From 1db60a0e4fee569b9cba5fc1a59f7616fbaf6e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 16:48:04 +0800 Subject: [PATCH 112/141] =?UTF-8?q?fix(topup):=E4=BF=AE=E5=A4=8D=E5=95=86?= =?UTF-8?q?=E6=88=B7=E5=85=85=E5=80=BC=E4=B8=AD=E7=9A=84=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=A4=8D=E5=88=B6=E5=AF=B9=E5=BA=94=E5=85=B3=E7=B3=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/top_up.go | 4 +++- vo/top_up.go | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/controller/top_up.go b/controller/top_up.go index 092d62a..60bd017 100644 --- a/controller/top_up.go +++ b/controller/top_up.go @@ -51,8 +51,10 @@ func listTopUps(c *fiber.Ctx) error { topUpLog.Error("查询符合条件的商户充值记录,查询失败", zap.Error(err)) return result.Error(fiber.StatusInternalServerError, "商户充值记录查询不成功") } - var topUpDetails []*vo.TopUpDetailQueryResponse + topUpLog.Debug("检查获取到的数据", zap.Any("topUps", topUps), zap.Int64("total", total)) + topUpDetails := make([]*vo.TopUpDetailQueryResponse, 0) copier.Copy(&topUpDetails, &topUps) + topUpLog.Debug("检查转换后的数据", zap.Any("topUpDetails", topUpDetails)) return result.Success( "已经获取到符合条件的商户充值记录", response.NewPagedResponse(page, total).ToMap(), diff --git a/vo/top_up.go b/vo/top_up.go index c1c8f39..5138de3 100644 --- a/vo/top_up.go +++ b/vo/top_up.go @@ -1,6 +1,10 @@ package vo -import "github.com/shopspring/decimal" +import ( + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) type TopUpCreationForm struct { Tenement string `json:"tenement"` @@ -9,12 +13,12 @@ type TopUpCreationForm struct { } type TopUpDetailQueryResponse struct { - Id string `json:"id" copier:"topUpCode"` + Id string `json:"id" copier:"TopUpCode"` Tenement string `json:"tenement"` TenementName string `json:"tenementName"` Meter string `json:"meter"` MeterAddress string `json:"meterAddress"` - ToppedUpAt string `json:"toppedUpAt"` + ToppedUpAt types.DateTime `json:"toppedUpAt"` Amount decimal.Decimal `json:"amount"` PaymentType int16 `json:"paymentType"` SyncStatus int16 `json:"syncStatus" copier:"SyncStatus"` From b7eaaffc3a39c61208defd593452f122b98258fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 17:00:44 +0800 Subject: [PATCH 113/141] =?UTF-8?q?enhance(types):=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=8C=BA=E9=97=B4=E7=B1=BB=E5=9E=8B=E4=B8=AD=E5=85=B3=E4=BA=8E?= =?UTF-8?q?=E7=A9=BA=E5=8C=BA=E9=97=B4=E7=9A=84=E5=88=A4=E6=96=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/daterange.go | 5 +++++ types/datetimerange.go | 5 +++++ types/range.go | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/types/daterange.go b/types/daterange.go index 6b34a8a..b71b89c 100644 --- a/types/daterange.go +++ b/types/daterange.go @@ -134,3 +134,8 @@ func (dr DateRange) SafeLower() Date { return MinDate() } } + +func (dr DateRange) IsEmptyOrWild() bool { + return (dr.LowerType == pgtype.Unbounded && dr.UpperType == pgtype.Unbounded) || + (dr.LowerType == pgtype.Empty && dr.UpperType == pgtype.Empty) +} diff --git a/types/datetimerange.go b/types/datetimerange.go index 5927d8d..971fcf9 100644 --- a/types/datetimerange.go +++ b/types/datetimerange.go @@ -112,3 +112,8 @@ func (dr *DateTimeRange) SetUpperUnbounded() { dr.Range.Upper = MaxDateTime() dr.Range.UpperType = pgtype.Unbounded } + +func (dr DateTimeRange) IsEmptyOrWild() bool { + return (dr.Range.LowerType == pgtype.Unbounded && dr.Range.UpperType == pgtype.Unbounded) || + (dr.Range.LowerType == pgtype.Empty || dr.Range.UpperType == pgtype.Empty) +} diff --git a/types/range.go b/types/range.go index 178b59f..374db35 100644 --- a/types/range.go +++ b/types/range.go @@ -15,6 +15,10 @@ type ToString interface { ToString() string } +type CheckEmptyOrWild interface { + IsEmptyOrWild() bool +} + // 将一个字符串拆解解析为一个 Postgresql 范围类型的值。 func destructureToRange[T any, PT interface { Parse From 9dc846c044b7695112f1eb4597e140a492bf2b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 17:01:14 +0800 Subject: [PATCH 114/141] =?UTF-8?q?fix(invoice):=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E5=8F=91=E7=A5=A8=E7=9A=84=E6=A3=80=E7=B4=A2?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repository/invoice.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/repository/invoice.go b/repository/invoice.go index 21a5505..6fc3885 100644 --- a/repository/invoice.go +++ b/repository/invoice.go @@ -40,6 +40,7 @@ func (ir _InvoiceRepository) ListInvoice(pid *string, startDate, endDate *types. Select("i.*") countQuery := ir.ds. From(goqu.T("invoice").As("i")). + Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("i.tenement_id").Eq(goqu.I("t.id")))). Select(goqu.COUNT("*")) if pid != nil && len(*pid) > 0 { @@ -76,8 +77,10 @@ func (ir _InvoiceRepository) ListInvoice(pid *string, startDate, endDate *types. if endDate != nil { queryRange.SetUpper(endDate.ToEndingOfDate()) } - invoiceQuery = invoiceQuery.Where(goqu.L("i.issued_at <@ ?", queryRange)) - countQuery = countQuery.Where(goqu.L("i.issued_at <@ ?", queryRange)) + if !queryRange.IsEmptyOrWild() { + invoiceQuery = invoiceQuery.Where(goqu.L("i.issued_at <@ ?", queryRange)) + countQuery = countQuery.Where(goqu.L("i.issued_at <@ ?", queryRange)) + } startRow := (page - 1) * config.ServiceSettings.ItemsPageSize invoiceQuery = invoiceQuery. From 7f2ec6819754efe1180508b1ee2fde5083e42c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Tue, 27 Jun 2023 17:04:21 +0800 Subject: [PATCH 115/141] =?UTF-8?q?fix(invoice):=E6=94=B9=E6=AD=A3?= =?UTF-8?q?=E5=8F=91=E7=A5=A8=E5=88=97=E8=A1=A8=E8=8E=B7=E5=8F=96=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E7=A9=BA=E5=88=97=E8=A1=A8=E8=BF=94=E5=9B=9Enull?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/invoice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/invoice.go b/controller/invoice.go index c96622d..4e88146 100644 --- a/controller/invoice.go +++ b/controller/invoice.go @@ -60,7 +60,7 @@ func listInvoices(c *fiber.Ctx) error { invoiceLog.Error("列出指定园区中的符合条件的发票记录失败,检索符合条件的发票记录出现错误。", zap.Error(err)) return result.Error(fiber.StatusInternalServerError, "检索符合条件的发票记录出现错误。") } - var invoiceResponse []*vo.InvoiceResponse + invoiceResponse := make([]*vo.InvoiceResponse, 0) copier.Copy(&invoiceResponse, &invoices) return result.Success( "已经获取到符合条件的发票列表。", From 648fc0f370236aee50da98a127f1a0aca0ba38a6 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Tue, 18 Jul 2023 16:07:56 +0800 Subject: [PATCH 116/141] =?UTF-8?q?new=EF=BC=9A=E6=96=B0=E5=A2=9Ewithdraw?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=EF=BC=8C=E8=AF=A5=E6=9A=82=E6=97=A0=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/dataSources.xml | 11 ++++ .idea/electricity_bill_calc_service.iml | 8 +++ .idea/misc.xml | 6 ++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 ++ controller/user.go | 27 ++++----- controller/withdraw.go | 74 +++++++++++++++++++++++++ doc/routerSetting.md | 13 +++++ global/db.go | 3 + global/redis.go | 7 ++- response/user_response.go | 2 +- router/router.go | 25 +++++---- service/user.go | 2 +- settings.yaml | 4 +- 14 files changed, 165 insertions(+), 31 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/electricity_bill_calc_service.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 controller/withdraw.go create mode 100644 doc/routerSetting.md diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..0973c28 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,11 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://39.105.39.8:9432/postgres + + + \ No newline at end of file diff --git a/.idea/electricity_bill_calc_service.iml b/.idea/electricity_bill_calc_service.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/electricity_bill_calc_service.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..95c0f54 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/controller/user.go b/controller/user.go index c79385e..7240728 100644 --- a/controller/user.go +++ b/controller/user.go @@ -42,34 +42,35 @@ type _LoginForm struct { } func doLogin(c *fiber.Ctx) error { - result := response.NewResult(c) - loginData := new(_LoginForm) - if err := c.BodyParser(loginData); err != nil { + result := response.NewResult(c) //创建一个相应结果对象 + loginData := new(_LoginForm) //创建一个解析登录表单数据的实体 + if err := c.BodyParser(loginData); err != nil { //解析请求体中的Json数据到loginData里,如果解析出错就返回错误 userLog.Error("表单解析失败!", zap.Error(err)) - return result.Error(http.StatusInternalServerError, "表单解析失败。") + return result.Error(http.StatusInternalServerError, "表单解析失败。") //返回一个内部服务器错误的相应结果 } var ( session *model.Session err error ) - userLog.Info("有用户请求登录。", zap.String("username", loginData.Username), zap.Int16("type", loginData.Type)) - if loginData.Type == model.USER_TYPE_ENT { - session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) + userLog.Info("有用户请求登录。", zap.String("username", loginData.Username), zap.Int16("type", loginData.Type)) //记录日志相关信息 + if loginData.Type == model.USER_TYPE_ENT { //根据登录类型选择不同的处理方法 + session, err = service.UserService.ProcessEnterpriseUserLogin(loginData.Username, loginData.Password) //企业用户 } else { - session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password) + userLog.Info("该用户是管理用户") + session, err = service.UserService.ProcessManagementUserLogin(loginData.Username, loginData.Password) //管理用户 } if err != nil { - if authError, ok := err.(*exceptions.AuthenticationError); ok { - if authError.NeedReset { + if authError, ok := err.(*exceptions.AuthenticationError); ok { //检查错误是否为身份验证错误 + if authError.NeedReset { //如果需要重置密码则返回对应结果 return result.LoginNeedReset() } - return result.Error(int(authError.Code), authError.Message) + return result.Error(int(authError.Code), authError.Message) //返回身份验证错误相应 } else { userLog.Error("用户登录请求处理失败!", zap.Error(err)) - return result.Error(http.StatusInternalServerError, err.Error()) + return result.Error(http.StatusInternalServerError, err.Error()) //返回内部服务器错误 } } - return result.LoginSuccess(session) + return result.LoginSuccess(session) //返回登录成功相应结果,包含会话信息 } func doLogout(c *fiber.Ctx) error { diff --git a/controller/withdraw.go b/controller/withdraw.go new file mode 100644 index 0000000..cc490ac --- /dev/null +++ b/controller/withdraw.go @@ -0,0 +1,74 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "electricity_bill_calc/response" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +var withdrawLog = logger.Named("Handler", "Withdraw") + +func InitializeWithdrawHandlers(router *fiber.App) { + router.Get("/withdraw", withdraw) +} + +//用于检索用户的核算报表 +func withdraw(c *fiber.Ctx) error { + //记录日志 + withdrawLog.Info("带分页的待审核的核算撤回申请列表") + //获取请求参数 + result := response.NewResult(c) + keyword := c.Query("keyword", "") + page := c.QueryInt("page", 1) + withdrawLog.Info("参数为: ", zap.String("keyword", keyword), zap.Int("page", page)) + //中间数据库操作暂且省略。。。。 + //首先进行核算报表的分页查询 + + //TODO: 2023-07-18 此处的data需要经过上面数据库查询后进行数据返回,此处只是作于演示 + data := fiber.Map{ + "report": fiber.Map{ + "id": "string", + "parkId": "string", + "periodBegin": "string", + "periodEnd": "string", + "published": true, + "publishedAt": "string", + "withdraw": 0, + "lastWithdrawAppliedAt": "string", + "lastWithdrawAuditAt": "string", + "status": 0, + "message": "string", + }, + "park": fiber.Map{ + "id": "string", + "userId": "string", + "name": "string", + "tenement": "string", + "area": "string", + "capacity": "string", + "category": 0, + "meter04kvType": 0, + "region": "string", + "address": "string", + "contact": "string", + "phone": "string", + }, + "user": fiber.Map{ + "id": "string", + "name": "string", + "contact": "string", + "phone": "string", + "region": "string", + "address": "string", + }, + } + datas := make([]interface{}, 0) + datas = append(datas, data) + //TODO: 2023-07-18 此处返回值是个示例,具体返回值需要查询数据库 + return result.Success( + "withdraw请求成功", + response.NewPagedResponse(page, 20).ToMap(), + fiber.Map{"records": datas}, + ) +} diff --git a/doc/routerSetting.md b/doc/routerSetting.md new file mode 100644 index 0000000..6e9c627 --- /dev/null +++ b/doc/routerSetting.md @@ -0,0 +1,13 @@ +## fiber +#### fiber实例 +- app(是fiber创建的实例通常用app表示,其中有可选配置选项) + - BodyLimit 设置请求正文允许的最大大小(默认为4 * 1024 * 1024) + - EnablePrintRoutes 不打印框架自带日志(默认false) + - EnableTrustedProxyCheck 禁用受信代理(默认false) + - Prefork 预处理配置(默认false) + - ErrorHandler 全局错误处理 (默认false) + - JSONEncoder json编码 (默认json.Marshal) + - JSONDecoder json解码 (默认json.Unmarshal) + - 。。。。。。。。(还有很多配置) +- Use(中间件设置,一个或者多个) +- Group(类似于gin框架中的路由分组) \ No newline at end of file diff --git a/global/db.go b/global/db.go index 1acdc74..a19c5f4 100644 --- a/global/db.go +++ b/global/db.go @@ -53,6 +53,9 @@ func (ql QueryLogger) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data ql.logger.Info("查询参数", lo.Map(data.Args, func(elem any, index int) zap.Field { return zap.Any(fmt.Sprintf("[Arg %d]: ", index), elem) })...) + // for index, arg := range data.Args { + // ql.logger.Info(fmt.Sprintf("[Arg %d]: %v", index, arg)) + // } return ctx } diff --git a/global/redis.go b/global/redis.go index e99c19f..8afb039 100644 --- a/global/redis.go +++ b/global/redis.go @@ -15,10 +15,13 @@ var ( func SetupRedisConnection() error { var err error + a := fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port) + fmt.Println(a) Rd, err = rueidis.NewClient(rueidis.ClientOption{ - InitAddress: []string{fmt.Sprintf("%s:%d", config.RedisSettings.Host, config.RedisSettings.Port)}, - Password: config.RedisSettings.Password, + InitAddress: []string{"127.0.0.1:6379"}, + Password: "", SelectDB: config.RedisSettings.DB, + DisableCache:true, }) if err != nil { return err diff --git a/response/user_response.go b/response/user_response.go index 9eaf458..a60086e 100644 --- a/response/user_response.go +++ b/response/user_response.go @@ -16,7 +16,7 @@ type LoginResponse struct { func (r Result) LoginSuccess(session *model.Session) error { res := &LoginResponse{} res.Code = http.StatusOK - res.Message = "用户已成功登录。" + res.Message = "用户已成功登录。"+ "👋!" res.NeedReset = false res.Session = session return r.Ctx.Status(fiber.StatusOK).JSON(res) diff --git a/router/router.go b/router/router.go index fe8c98f..908558d 100644 --- a/router/router.go +++ b/router/router.go @@ -25,24 +25,24 @@ func init() { } func App() *fiber.App { - app := fiber.New(fiber.Config{ - BodyLimit: 30 * 1024 * 1024, - EnablePrintRoutes: true, - EnableTrustedProxyCheck: false, - Prefork: false, - ErrorHandler: errorHandler, - JSONEncoder: json.Marshal, - JSONDecoder: json.Unmarshal, + app := fiber.New(fiber.Config{ //创建fiber实例的时候选择配置选项 + BodyLimit: 30 * 1024 * 1024, //设置请求正文允许的最大大小。 + EnablePrintRoutes: true, //自定义方案,用于启动消息 + EnableTrustedProxyCheck: false, //禁用受信代理 + Prefork: false, //禁止预处理(如果要启用预处理则需要通过shell脚本运行) + ErrorHandler: errorHandler, //相应全局处理错误 + JSONEncoder: json.Marshal, //json编码 + JSONDecoder: json.Unmarshal, //json解码 }) - app.Use(compress.New()) + app.Use(compress.New()) //压缩中间件 app.Use(recover.New(recover.Config{ EnableStackTrace: true, StackTraceHandler: stackTraceHandler, - })) + })) //恢复中间件 app.Use(logger.NewLogMiddleware(logger.LogMiddlewareConfig{ Logger: logger.Named("App"), - })) - app.Use(security.SessionRecovery) + })) //日志中间件 + app.Use(security.SessionRecovery) //会话恢复中间件 controller.InitializeUserHandlers(app) controller.InitializeRegionHandlers(app) @@ -53,6 +53,7 @@ func App() *fiber.App { controller.InitializeInvoiceHandler(app) controller.InitializeTopUpHandlers(app) controller.InitializeReportHandlers(app) + controller.InitializeWithdrawHandlers(app) return app } diff --git a/service/user.go b/service/user.go index a81dc3f..2474921 100644 --- a/service/user.go +++ b/service/user.go @@ -80,7 +80,7 @@ func (us _UserService) ProcessEnterpriseUserLogin(username, password string) (*m us.log.Error("处理企业用户登录失败。", zap.String("username", username), zap.Error(err)) return nil, err } - token, _ := uuid.NewRandom() + token, _ := uuid.NewRandom() //生成uuid作为会话的token使用 userSession := &model.Session{ Uid: user.Id, Name: user.Username, diff --git a/settings.yaml b/settings.yaml index 4a0e548..0c4d01c 100644 --- a/settings.yaml +++ b/settings.yaml @@ -1,8 +1,8 @@ Database: User: electricity Pass: nLgxPO5s8gK2tR0OL0Q - Host: postgres - Port: 5432 + Host: 39.105.39.8 + Port: 9432 DB: electricity MaxIdleConns: 0 MaxOpenConns: 20 From 61edef5c92b5bbb8c433f923c5be6547d417d796 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Thu, 20 Jul 2023 16:13:19 +0800 Subject: [PATCH 117/141] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=AE=8C?= =?UTF-8?q?=E5=96=84withdraw=E7=9A=84=E8=BF=94=E5=9B=9E=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/report.go | 3 +- controller/tenement.go | 2 + controller/withdraw.go | 50 ++--------- model/park.go | 5 ++ model/withdraw.go | 77 +++++++++++++++++ repository/withdraw.go | 184 +++++++++++++++++++++++++++++++++++++++++ router/router.go | 2 + tools/utils.go | 14 ++++ 8 files changed, 294 insertions(+), 43 deletions(-) create mode 100644 model/withdraw.go create mode 100644 repository/withdraw.go diff --git a/controller/report.go b/controller/report.go index a99cabe..b79efe5 100644 --- a/controller/report.go +++ b/controller/report.go @@ -23,7 +23,8 @@ func InitializeReportHandlers(router *fiber.App) { router.Get("/reports", security.MustAuthenticated, reportComprehensiveSearch) router.Post("/report", security.EnterpriseAuthorize, initNewReportCalculateTask) router.Get("/report/draft", security.EnterpriseAuthorize, listDraftReportIndicies) - router.Post("/report/calcualte", security.EnterpriseAuthorize, testCalculateReportSummary) + //TODO: 2023-07-20将calcualte错误请求改为正确的calculate请求 + router.Post("/report/calculate", security.EnterpriseAuthorize, testCalculateReportSummary) router.Get("/report/calculate/status", security.EnterpriseAuthorize, listCalculateTaskStatus) router.Get("/report/:rid", security.EnterpriseAuthorize, getReportDetail) router.Put("/report/:rid", security.EnterpriseAuthorize, updateReportCalculateTask) diff --git a/controller/tenement.go b/controller/tenement.go index 04dab4c..0e1c98b 100644 --- a/controller/tenement.go +++ b/controller/tenement.go @@ -25,9 +25,11 @@ func InitializeTenementHandler(router *fiber.App) { 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) + //TODO: 2023-07-19再apiFox上该请求是个PUT请求,后端接收是个POST请求,不知道是否有误或是缺少对应请求(apiFox测试请求返回值为405) router.Post("/tenement/:pid/:tid/move/out", security.EnterpriseAuthorize, moveOutTenement) router.Post("/tenement/:pid", security.EnterpriseAuthorize, addTenement) router.Post("/tenement/:pid/:tid/binding", security.EnterpriseAuthorize, bindMeterToTenement) + //TODO: 2023-07-19再apiFox上该请求是个PUT请求,后端接收是个POST请求,不知道是否有误或是缺少对应请求(apiFox测试请求返回值为405) router.Post("/tenement/:pid/:tid/binding/:code/unbind", security.EnterpriseAuthorize, unbindMeterFromTenement) } diff --git a/controller/withdraw.go b/controller/withdraw.go index cc490ac..e19d613 100644 --- a/controller/withdraw.go +++ b/controller/withdraw.go @@ -2,9 +2,11 @@ package controller import ( "electricity_bill_calc/logger" + "electricity_bill_calc/repository" "electricity_bill_calc/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" + "net/http" ) var withdrawLog = logger.Named("Handler", "Withdraw") @@ -24,51 +26,15 @@ func withdraw(c *fiber.Ctx) error { withdrawLog.Info("参数为: ", zap.String("keyword", keyword), zap.Int("page", page)) //中间数据库操作暂且省略。。。。 //首先进行核算报表的分页查询 - - //TODO: 2023-07-18 此处的data需要经过上面数据库查询后进行数据返回,此处只是作于演示 - data := fiber.Map{ - "report": fiber.Map{ - "id": "string", - "parkId": "string", - "periodBegin": "string", - "periodEnd": "string", - "published": true, - "publishedAt": "string", - "withdraw": 0, - "lastWithdrawAppliedAt": "string", - "lastWithdrawAuditAt": "string", - "status": 0, - "message": "string", - }, - "park": fiber.Map{ - "id": "string", - "userId": "string", - "name": "string", - "tenement": "string", - "area": "string", - "capacity": "string", - "category": 0, - "meter04kvType": 0, - "region": "string", - "address": "string", - "contact": "string", - "phone": "string", - }, - "user": fiber.Map{ - "id": "string", - "name": "string", - "contact": "string", - "phone": "string", - "region": "string", - "address": "string", - }, + withdraws, total, err := repository.WithdrawRepository.FindWithdraw(page, &keyword) + if err != nil { + withdrawLog.Error("检索用户核算报表失败。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) } - datas := make([]interface{}, 0) - datas = append(datas, data) //TODO: 2023-07-18 此处返回值是个示例,具体返回值需要查询数据库 return result.Success( "withdraw请求成功", - response.NewPagedResponse(page, 20).ToMap(), - fiber.Map{"records": datas}, + response.NewPagedResponse(page, total).ToMap(), + fiber.Map{"records": withdraws}, ) } diff --git a/model/park.go b/model/park.go index 9dfd190..c00df57 100644 --- a/model/park.go +++ b/model/park.go @@ -31,3 +31,8 @@ type Park struct { LastModifiedAt time.Time `json:"lastModifiedAt"` DeletedAt *time.Time `json:"deletedAt"` } + +type Parks struct { + Park + NormAuthorizedLossRate float64 `json:"norm_authorized_loss_rate"` +} diff --git a/model/withdraw.go b/model/withdraw.go new file mode 100644 index 0000000..6b5b032 --- /dev/null +++ b/model/withdraw.go @@ -0,0 +1,77 @@ +package model + +import ( + "database/sql" + "electricity_bill_calc/types" + "time" +) + +type Withdraw struct { + Park SimplifiedPark `json:"park"` + Report SimplifiedReport `json:"report"` + User UserInfos `json:"user"` // 简易用户详细信息 +} + +// 简易园区信息 +type SimplifiedPark struct { + Address *string `json:"address"` // 园区地址 + Area *string `json:"area"` // 园区面积 + Capacity *string `json:"capacity"` // 供电容量 + Category int16 `json:"category"` // 用电分类,0:两部制,1:单一峰谷,2:单一单一 + Contact *string `json:"contact"` // 园区联系人 + ID string `json:"id"` // 园区ID + Meter04KvType int16 `json:"meter04kvType"` // 户表计量类型,0:非峰谷,1:峰谷 + Name string `json:"name"` // 园区名称 + Phone *string `json:"phone"` // 园区联系人电话 + Region *string `json:"region"` // 园区所在行政区划 + Tenement *string `json:"tenement"` // 园区住户数量 + UserID string `json:"userId"` // 园区所属用户ID +} + +// 简易核算报表信息 +type SimplifiedReport struct { + ID string `json:"id"` // 报表ID + LastWithdrawAppliedAt *string `json:"lastWithdrawAppliedAt"` // 最后一次申请撤回的时间,格式为 yyyy-MM-dd HH:mm:ss + LastWithdrawAuditAt *string `json:"lastWithdrawAuditAt"` // 最后一次申请审核的时间,格式为 yyyy-MM-dd HH:mm:ss + Message *string `json:"message"` // 当前状态的错误提示 + ParkID string `json:"parkId"` // 所属园区ID + PeriodBegin string `json:"periodBegin"` // 核算起始日期,格式为 yyyy-MM-dd + PeriodEnd string `json:"periodEnd"` // 核算结束日期,格式为 yyyy-MM-dd + Published bool `json:"published"` // 是否已发布 + PublishedAt *string `json:"publishedAt"` // 发布时间 + Status float64 `json:"status,omitempty"` // 当前状态,0:计算任务已队列,1:计算任务已完成,2:计算数据不足 + Withdraw int16 `json:"withdraw"` // 报表撤回状态,0:未撤回,1:申请撤回中,2:申请拒绝,3:申请批准 +} + +// 简易用户信息 +type UserInfos struct { + Address *string `json:"address"` // 用户地址 + Contact *string `json:"contact"` // 用户联系人 + ID string `json:"id"` // 用户ID + Name *string `json:"name"` // 用户名称 + Phone *string `json:"phone"` // 用户联系人电话 + Region *string `json:"region"` // 用户所在行政区划 +} + +//用于映射数据库的报表结构体 +type Report struct { + CreatedAt time.Time `db:"created_at"` + LastModifiedAt sql.NullTime `db:"last_modified_at"` + ID string `db:"id"` + ParkID string `db:"park_id"` + Period types.DateRange `db:"period"` + Published bool `db:"published"` + PublishedAt sql.NullTime `db:"published_at"` + Withdraw int16 `db:"withdraw"` + LastWithdrawAppliedAt sql.NullTime `db:"last_withdraw_applied_at"` + LastWithdrawAuditAt sql.NullTime `db:"last_withdraw_audit_at"` + Category int16 `db:"category"` + Meter04KVType int16 `db:"meter_04kv_type"` + PricePolicy int16 `db:"price_policy"` + BasisPooled int16 `db:"basis_pooled"` + AdjustPooled int16 `db:"adjust_pooled"` + LossPooled int16 `db:"loss_pooled"` + PublicPooled int16 `db:"public_pooled"` + AuthorizedLossRate float64 `db:"authorized_loss_rate"` + AuthorizedLossRateIncr float64 `db:"authorized_loss_rate_increment"` +} diff --git a/repository/withdraw.go b/repository/withdraw.go new file mode 100644 index 0000000..f316d7f --- /dev/null +++ b/repository/withdraw.go @@ -0,0 +1,184 @@ +package repository + +import ( + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "electricity_bill_calc/tools" + "fmt" + "github.com/doug-martin/goqu/v9" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _WithdrawRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var WithdrawRepository = &_WithdrawRepository{ + log: logger.Named("Repository", "Withdraw"), + ds: goqu.Dialect("postgres"), +} + +/** + * @author: ZiHangQin + * 该方法用于分页查询核算报表 + * @param:page + * @param: keyword + * @return:[]object + * @return:total + * @return: error + */ +func (wd _WithdrawRepository) FindWithdraw(page int, keyword *string) ([]model.Withdraw, int64, error) { + wd.log.Info("查询用户的充值记录。", zap.Stringp("keyword", keyword), zap.Int("page", page)) + ctx, cancel := global.TimeoutContext() + defer cancel() + fmt.Println(ctx) + //TODO: 2023-07-18此处进行用户的核算报表分页查询的sql语句拼接逻辑。 + //1、SELECT * FROM report WHERE `withdraw` = 1(获取到所有的状态为申请撤回中的报表数据) + // + //2、循环遍历1中获取的数据{ + //查询需要的字段"id": "string", + // "parkId": "string", + // "periodBegin": "string", + // "periodEnd": "string", + // "published": true, + // "publishedAt": "string", + // "withdraw": 0, + // "lastWithdrawAppliedAt": "string", + // "lastWithdrawAuditAt": "string", + // "status": 0, + // "message": "string" + //----report简易核算报表信息获取完成 + // + //3、SELECT * FROM park WHERE `id` = report.park_id(获取园区信息) + //查询结果需要的字段 "id": "string", + // "userId": "string", + // "name": "string", + // "tenement": "string", + // "area": "string", + // "capacity": "string", + // "category": 0, + // "meter04kvType": 0, + // "region": "string", + // "address": "string", + // "contact": "string", + // "phone": "string" + //----park简易园区信息货物完成 + // + //4、SELECT * FROM user_detail WHERE `id` = park.user_id(获取用户信息) + //查询结果需要的字段 "id": "string", + // "name": "string", + // "contact": "string", + // "phone": "string", + // "region": "string", + // "address": "string" + //----user简易用户信息获取完成 + //} + + reportQuery, reportQueryArgs, _ := wd.ds. + From(goqu.T("report")). + Where(goqu.I("withdraw").Eq(1)). + Select("*").ToSQL() + + reports := make([]model.Report, 0) + + err := pgxscan.Select(ctx, global.DB, &reports, reportQuery, reportQueryArgs...) + if err != nil { + fmt.Println(err) + return []model.Withdraw{}, 0, err + } + fmt.Println("数据库中读取的指定数据:", reports) + + var withdrawReses []model.Withdraw + + for _, v := range reports { + lastWithdrawAppliedAtStr := tools.NullTime2PointerString(v.LastWithdrawAppliedAt) + lastWithdrawAuditAtStr := tools.NullTime2PointerString(v.LastWithdrawAuditAt) + publishAtStr := tools.NullTime2PointerString(v.PublishedAt) + + Begin := v.Period.SafeLower().Format("2006-01-02") + End := v.Period.SafeUpper().Format("2006-01-02") + var withdrawRes model.Withdraw + //构建简易报表信息 + simplifiedReport := model.SimplifiedReport{ + ID: v.ID, + LastWithdrawAppliedAt: lastWithdrawAppliedAtStr, + LastWithdrawAuditAt: lastWithdrawAuditAtStr, + Message: nil, + ParkID: v.ParkID, + PeriodBegin: Begin, + PeriodEnd: End, + Published: v.Published, + PublishedAt: publishAtStr, + Status: 0.00, + Withdraw: v.Withdraw, + } + + parkQuery, parkQueryArgs, _ := wd.ds. + From(goqu.T("park")). + Where(goqu.I("id").Eq(v.ParkID)). + Select("*").ToSQL() + + park := make([]model.Parks, 0) + err := pgxscan.Select(ctx, global.DB, &park, parkQuery, parkQueryArgs...) + fmt.Println("读到的园区数据:", park) + if err != nil { + fmt.Println(err) + return []model.Withdraw{}, 0, err + } + + areaStr := tools.NullDecimalToString(park[0].Area) + capacityStr := tools.NullDecimalToString(park[0].Capacity) + TenementQuantityStr := tools.NullDecimalToString(park[0].TenementQuantity) + //构建简易园区数据 + simplifiedPark := model.SimplifiedPark{ + Address: park[0].Address, + Area: areaStr, + Capacity: capacityStr, + Category: park[0].Category, + Contact: park[0].Contact, + ID: park[0].Id, + Meter04KvType: park[0].MeterType, + Name: park[0].Name, + Phone: park[0].Phone, + Region: park[0].Region, + Tenement: TenementQuantityStr, + UserID: park[0].UserId, + } + + userQuery, userQueryArgs, _ := wd.ds. + From(goqu.T("user_detail")). + Where(goqu.I("id").Eq(park[0].UserId)). + Select("*").ToSQL() + + userInfo := make([]model.UserDetail, 0) + + err = pgxscan.Select(ctx, global.DB, &userInfo, userQuery, userQueryArgs...) + fmt.Println("读到的用户数据:", userInfo) + if err != nil { + fmt.Println(err) + return []model.Withdraw{}, 0, err + } + + simplifiedUser := model.UserInfos{ + Address: userInfo[0].Address, + Contact: userInfo[0].Contact, + ID: userInfo[0].Id, + Name: userInfo[0].Name, + Phone: userInfo[0].Phone, + Region: userInfo[0].Region, + } + + withdrawRes.Report = simplifiedReport + withdrawRes.Park = simplifiedPark + withdrawRes.User = simplifiedUser + + withdrawReses = append(withdrawReses, withdrawRes) + } + + total := len(reports) + + return withdrawReses, int64(total), nil +} diff --git a/router/router.go b/router/router.go index 908558d..f1db451 100644 --- a/router/router.go +++ b/router/router.go @@ -53,6 +53,8 @@ func App() *fiber.App { controller.InitializeInvoiceHandler(app) controller.InitializeTopUpHandlers(app) controller.InitializeReportHandlers(app) + + controller.InitializeWithdrawHandlers(app) return app diff --git a/tools/utils.go b/tools/utils.go index 6312dd5..fceb81b 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -1,6 +1,7 @@ package tools import ( + "database/sql" "encoding/json" "fmt" "strings" @@ -144,3 +145,16 @@ func NullDecimalToString(d decimal.NullDecimal, precision ...int32) *string { } return lo.ToPtr(d.Decimal.StringFixedBank(precision[0])) } + +//将sql.NullTime转换为*string +func NullTime2PointerString(nullTime sql.NullTime) *string { + var strPtr *string + if nullTime.Valid { + str := nullTime.Time.String() + strPtr = &str + return strPtr + } else { + strPtr = nil + return strPtr + } +} From ab44ff5cc4a23bd54f99e579989b3f29f7d1fe09 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Tue, 25 Jul 2023 10:09:53 +0800 Subject: [PATCH 118/141] a --- .idea/codeStyles/Project.xml | 28 ++++++++++++++++++++++++++++ repository/withdraw.go | 1 - 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 .idea/codeStyles/Project.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..3cdc6ae --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repository/withdraw.go b/repository/withdraw.go index f316d7f..5529731 100644 --- a/repository/withdraw.go +++ b/repository/withdraw.go @@ -89,7 +89,6 @@ func (wd _WithdrawRepository) FindWithdraw(page int, keyword *string) ([]model.W fmt.Println(err) return []model.Withdraw{}, 0, err } - fmt.Println("数据库中读取的指定数据:", reports) var withdrawReses []model.Withdraw From 6fece99e002268c8912a0bc59b1ba26849d2835a Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Tue, 25 Jul 2023 10:45:43 +0800 Subject: [PATCH 119/141] =?UTF-8?q?=E5=B8=A6=E5=88=86=E9=A1=B5=E7=9A=84?= =?UTF-8?q?=E5=BE=85=E5=AE=A1=E6=A0=B8=E7=9A=84=E6=A0=B8=E7=AE=97=E6=92=A4?= =?UTF-8?q?=E5=9B=9E=E7=94=B3=E8=AF=B7=E5=88=97=E8=A1=A8=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/withdraw.go | 2 +- model/withdraw.go | 75 +++++++------ repository/withdraw.go | 243 ++++++++++++++++++++--------------------- tools/utils.go | 13 +++ 4 files changed, 173 insertions(+), 160 deletions(-) diff --git a/controller/withdraw.go b/controller/withdraw.go index e19d613..d6c7510 100644 --- a/controller/withdraw.go +++ b/controller/withdraw.go @@ -26,7 +26,7 @@ func withdraw(c *fiber.Ctx) error { withdrawLog.Info("参数为: ", zap.String("keyword", keyword), zap.Int("page", page)) //中间数据库操作暂且省略。。。。 //首先进行核算报表的分页查询 - withdraws, total, err := repository.WithdrawRepository.FindWithdraw(page, &keyword) + withdraws, total, err := repository.WithdrawRepository.FindWithdraw(uint(page), &keyword) if err != nil { withdrawLog.Error("检索用户核算报表失败。", zap.Error(err)) return result.Error(http.StatusInternalServerError, err.Error()) diff --git a/model/withdraw.go b/model/withdraw.go index 6b5b032..b1da5f1 100644 --- a/model/withdraw.go +++ b/model/withdraw.go @@ -1,8 +1,8 @@ package model import ( - "database/sql" "electricity_bill_calc/types" + "github.com/shopspring/decimal" "time" ) @@ -17,10 +17,10 @@ type SimplifiedPark struct { Address *string `json:"address"` // 园区地址 Area *string `json:"area"` // 园区面积 Capacity *string `json:"capacity"` // 供电容量 - Category int16 `json:"category"` // 用电分类,0:两部制,1:单一峰谷,2:单一单一 + Category int16 `json:"category"` // 用电分类,0:两部制,1:单一峰谷,2:单一单一 Contact *string `json:"contact"` // 园区联系人 ID string `json:"id"` // 园区ID - Meter04KvType int16 `json:"meter04kvType"` // 户表计量类型,0:非峰谷,1:峰谷 + Meter04KvType int16 `json:"meter04kvType"` // 户表计量类型,0:非峰谷,1:峰谷 Name string `json:"name"` // 园区名称 Phone *string `json:"phone"` // 园区联系人电话 Region *string `json:"region"` // 园区所在行政区划 @@ -30,17 +30,17 @@ type SimplifiedPark struct { // 简易核算报表信息 type SimplifiedReport struct { - ID string `json:"id"` // 报表ID - LastWithdrawAppliedAt *string `json:"lastWithdrawAppliedAt"` // 最后一次申请撤回的时间,格式为 yyyy-MM-dd HH:mm:ss - LastWithdrawAuditAt *string `json:"lastWithdrawAuditAt"` // 最后一次申请审核的时间,格式为 yyyy-MM-dd HH:mm:ss - Message *string `json:"message"` // 当前状态的错误提示 - ParkID string `json:"parkId"` // 所属园区ID - PeriodBegin string `json:"periodBegin"` // 核算起始日期,格式为 yyyy-MM-dd - PeriodEnd string `json:"periodEnd"` // 核算结束日期,格式为 yyyy-MM-dd - Published bool `json:"published"` // 是否已发布 - PublishedAt *string `json:"publishedAt"` // 发布时间 + ID string `json:"id"` // 报表ID + LastWithdrawAppliedAt *string `json:"lastWithdrawAppliedAt"` // 最后一次申请撤回的时间,格式为 yyyy-MM-dd HH:mm:ss + LastWithdrawAuditAt *string `json:"lastWithdrawAuditAt"` // 最后一次申请审核的时间,格式为 yyyy-MM-dd HH:mm:ss + Message *string `json:"message"` // 当前状态的错误提示 + ParkID string `json:"parkId"` // 所属园区ID + PeriodBegin string `json:"periodBegin"` // 核算起始日期,格式为 yyyy-MM-dd + PeriodEnd string `json:"periodEnd"` // 核算结束日期,格式为 yyyy-MM-dd + Published bool `json:"published"` // 是否已发布 + PublishedAt *string `json:"publishedAt"` // 发布时间 Status float64 `json:"status,omitempty"` // 当前状态,0:计算任务已队列,1:计算任务已完成,2:计算数据不足 - Withdraw int16 `json:"withdraw"` // 报表撤回状态,0:未撤回,1:申请撤回中,2:申请拒绝,3:申请批准 + Withdraw int16 `json:"withdraw"` // 报表撤回状态,0:未撤回,1:申请撤回中,2:申请拒绝,3:申请批准 } // 简易用户信息 @@ -48,30 +48,37 @@ type UserInfos struct { Address *string `json:"address"` // 用户地址 Contact *string `json:"contact"` // 用户联系人 ID string `json:"id"` // 用户ID - Name *string `json:"name"` // 用户名称 + Name *string `json:"name"` // 用户名称 Phone *string `json:"phone"` // 用户联系人电话 Region *string `json:"region"` // 用户所在行政区划 } //用于映射数据库的报表结构体 -type Report struct { - CreatedAt time.Time `db:"created_at"` - LastModifiedAt sql.NullTime `db:"last_modified_at"` - ID string `db:"id"` - ParkID string `db:"park_id"` - Period types.DateRange `db:"period"` - Published bool `db:"published"` - PublishedAt sql.NullTime `db:"published_at"` - Withdraw int16 `db:"withdraw"` - LastWithdrawAppliedAt sql.NullTime `db:"last_withdraw_applied_at"` - LastWithdrawAuditAt sql.NullTime `db:"last_withdraw_audit_at"` - Category int16 `db:"category"` - Meter04KVType int16 `db:"meter_04kv_type"` - PricePolicy int16 `db:"price_policy"` - BasisPooled int16 `db:"basis_pooled"` - AdjustPooled int16 `db:"adjust_pooled"` - LossPooled int16 `db:"loss_pooled"` - PublicPooled int16 `db:"public_pooled"` - AuthorizedLossRate float64 `db:"authorized_loss_rate"` - AuthorizedLossRateIncr float64 `db:"authorized_loss_rate_increment"` +type ReportRes struct { + ReportId string `db:"report_id"` + LastWithdrawAppliedAt *time.Time `db:"last_withdraw_applied_at"` + LastWithdrawAuditAt *time.Time `db:"last_withdraw_audit_at"` + ParkID string `db:"report_park_id"` + Period types.DateRange `db:"period"` + Published bool `db:"published"` + PublishedAt *time.Time `db: "published_at"` + Withdraw int16 `db:"withdraw"` + ParkAddress *string `db:"park_address"` + Area decimal.NullDecimal `db:"area"` + Capacity decimal.NullDecimal `db:"capacity"` + Category int16 + ParkContact *string `db:"park_contact"` + ParkId string `db:"park_id"` + Meter04KVType int16 `db:"meter_04kv_type"` + ParkName string `db:"park_name"` + ParkPhone *string `db:"park_phone"` + ParkRegion string `db:"park_region"` + TenementQuantity decimal.NullDecimal `db:"tenement_quantity"` + UserID string `db:"user_id"` + Address *string + Contact string `db:"user_detail_contact"` + ID string `db:"ud_id"` + Name *string `db:"user_detail_name"` + Phone string `db:"user_detail_phone"` + Region *string `db:"user_detail_region"` } diff --git a/repository/withdraw.go b/repository/withdraw.go index 5529731..4fe000c 100644 --- a/repository/withdraw.go +++ b/repository/withdraw.go @@ -1,6 +1,7 @@ package repository import ( + "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" @@ -30,154 +31,146 @@ var WithdrawRepository = &_WithdrawRepository{ * @return:total * @return: error */ -func (wd _WithdrawRepository) FindWithdraw(page int, keyword *string) ([]model.Withdraw, int64, error) { - wd.log.Info("查询用户的充值记录。", zap.Stringp("keyword", keyword), zap.Int("page", page)) +func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model.Withdraw, int64, error) { + wd.log.Info("查询用户的充值记录。", zap.Stringp("keyword", keyword), zap.Int("page", int(page))) ctx, cancel := global.TimeoutContext() defer cancel() - fmt.Println(ctx) - //TODO: 2023-07-18此处进行用户的核算报表分页查询的sql语句拼接逻辑。 - //1、SELECT * FROM report WHERE `withdraw` = 1(获取到所有的状态为申请撤回中的报表数据) - // - //2、循环遍历1中获取的数据{ - //查询需要的字段"id": "string", - // "parkId": "string", - // "periodBegin": "string", - // "periodEnd": "string", - // "published": true, - // "publishedAt": "string", - // "withdraw": 0, - // "lastWithdrawAppliedAt": "string", - // "lastWithdrawAuditAt": "string", - // "status": 0, - // "message": "string" - //----report简易核算报表信息获取完成 - // - //3、SELECT * FROM park WHERE `id` = report.park_id(获取园区信息) - //查询结果需要的字段 "id": "string", - // "userId": "string", - // "name": "string", - // "tenement": "string", - // "area": "string", - // "capacity": "string", - // "category": 0, - // "meter04kvType": 0, - // "region": "string", - // "address": "string", - // "contact": "string", - // "phone": "string" - //----park简易园区信息货物完成 - // - //4、SELECT * FROM user_detail WHERE `id` = park.user_id(获取用户信息) - //查询结果需要的字段 "id": "string", - // "name": "string", - // "contact": "string", - // "phone": "string", - // "region": "string", - // "address": "string" - //----user简易用户信息获取完成 - //} - reportQuery, reportQueryArgs, _ := wd.ds. - From(goqu.T("report")). + /** + 如果访问数据库次数过多出现时间过长的话可以用这个尝试优化,未测试的sql语句 + + wd.ds.From(goqu.T("report")). Where(goqu.I("withdraw").Eq(1)). - Select("*").ToSQL() + Select( + goqu.I("report.*"), + goqu.I("park.*"), + goqu.I("user_detail.*"), + ). + Join( + goqu.T("park"), goqu.On(goqu.I("report.park_id").Eq(goqu.I("park.id"))), + ). + Join( + goqu.T("user_detail"), goqu.On(goqu.I("park.user_id").Eq(goqu.I("user_detail.id"))), + ).ToSQL() - reports := make([]model.Report, 0) + SELECT report.*, park.*, user_detail.* + FROM report as r + JOIN park as p ON r.park_id = p.id + JOIN user_detail as ud ON p.user_id = ud.id + WHERE withdraw = 1 + AND p.name Like '%keyword%' + AND ud.name Like '%keyword%' + */ + reportQuery := wd.ds. + From(goqu.T("report").As("r")). + Where(goqu.I("withdraw").Eq(1)). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("r.park_id").Eq(goqu.I("p.id")))). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("p.user_id").Eq(goqu.I("ud.id")))). + Select( + goqu.I("r.id").As("report_id"), goqu.I("r.last_withdraw_applied_at"), goqu.I("r.last_withdraw_audit_at"), + goqu.I("r.park_id").As("report_park_id"), goqu.I("r.period"), goqu.I("r.published"), goqu.I("r.published_at"), goqu.I("r.withdraw"), + goqu.I("p.address").As("park_address"), goqu.I("p.area"), goqu.I("p.capacity"), goqu.I("p.category"), goqu.I("p.contact").As("park_contact"), + goqu.I("p.id").As("park_id"), goqu.I("p.meter_04kv_type"), goqu.I("p.name").As("park_name"), goqu.I("p.phone").As("park_phone"), goqu.I("p.region").As("park_region"), + goqu.I("p.tenement_quantity"), goqu.I("p.user_id"), goqu.I("ud.address"), goqu.I("ud.contact").As("user_detail_contact"), + goqu.I("ud.id").As("ud_id"), goqu.I("ud.name").As("user_detail_name"), goqu.I("ud.phone").As("user_detail_phone"), goqu.I("ud.region").As("user_detail_region"), + ) - err := pgxscan.Select(ctx, global.DB, &reports, reportQuery, reportQueryArgs...) - if err != nil { - fmt.Println(err) - return []model.Withdraw{}, 0, err + countReportQuery := wd.ds. + From(goqu.T("report").As("r")). + Where(goqu.I("withdraw").Eq(1)). + Join(goqu.T("park").As("p"), goqu.On(goqu.I("r.park_id").Eq(goqu.I("p.id")))). + Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("p.user_id").Eq(goqu.I("ud.id")))). + Select(goqu.COUNT("*")) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + reportQuery = reportQuery.Where(goqu.Or( + goqu.I("p.name").ILike(pattern), + goqu.I("ud.name").ILike(pattern), + )) } + reportQuery = reportQuery.Order(goqu.I("r.created_at").Desc()) + + currentPostion := (page - 1) * config.ServiceSettings.ItemsPageSize + reportQuery = reportQuery.Offset(currentPostion).Limit(config.ServiceSettings.ItemsPageSize) + + reportSql, reportArgs, _ := reportQuery.Prepared(true).ToSQL() + + countReportQuerySql, countReportQueryArgs, _ := countReportQuery.Prepared(true).ToSQL() + + + var ( + reports []*model.ReportRes = make([]*model.ReportRes, 0) + total int64 + ) + var err error + + err = pgxscan.Select(ctx, global.DB, &reports, reportSql, reportArgs...) + if err != nil { + fmt.Println(err) + wd.log.Error("查询报表记录失败。", zap.Error(err)) + return make([]model.Withdraw, 0), 0, err + } + + if err = pgxscan.Get(ctx, global.DB, &total, countReportQuerySql, countReportQueryArgs...); err != nil { + wd.log.Error("查询报表记录总数失败。", zap.Error(err)) + return make([]model.Withdraw, 0), 0, err + } + + if len(reports) <= 0 { + return make([]model.Withdraw, 0), total, nil + } + + fmt.Println(&reports) var withdrawReses []model.Withdraw - + //TODO: 2023.07.24对查询到的数据进行拼接 for _, v := range reports { - lastWithdrawAppliedAtStr := tools.NullTime2PointerString(v.LastWithdrawAppliedAt) - lastWithdrawAuditAtStr := tools.NullTime2PointerString(v.LastWithdrawAuditAt) - publishAtStr := tools.NullTime2PointerString(v.PublishedAt) - Begin := v.Period.SafeLower().Format("2006-01-02") End := v.Period.SafeUpper().Format("2006-01-02") + var withdrawRes model.Withdraw - //构建简易报表信息 - simplifiedReport := model.SimplifiedReport{ - ID: v.ID, - LastWithdrawAppliedAt: lastWithdrawAppliedAtStr, - LastWithdrawAuditAt: lastWithdrawAuditAtStr, + report := model.SimplifiedReport{ + ID: v.ReportId, + LastWithdrawAppliedAt: tools.TimeToStringPtr(v.LastWithdrawAppliedAt), + LastWithdrawAuditAt: tools.TimeToStringPtr(v.LastWithdrawAuditAt), Message: nil, ParkID: v.ParkID, PeriodBegin: Begin, PeriodEnd: End, Published: v.Published, - PublishedAt: publishAtStr, - Status: 0.00, + PublishedAt: nil, + Status: 0., Withdraw: v.Withdraw, } - - parkQuery, parkQueryArgs, _ := wd.ds. - From(goqu.T("park")). - Where(goqu.I("id").Eq(v.ParkID)). - Select("*").ToSQL() - - park := make([]model.Parks, 0) - err := pgxscan.Select(ctx, global.DB, &park, parkQuery, parkQueryArgs...) - fmt.Println("读到的园区数据:", park) - if err != nil { - fmt.Println(err) - return []model.Withdraw{}, 0, err + park := model.SimplifiedPark{ + Address: v.ParkAddress, + Area: tools.NullDecimalToString(v.Area), + Capacity: tools.NullDecimalToString(v.Capacity), + Category: int16(v.Category), + Contact: v.ParkContact, + ID: v.ParkId, + Meter04KvType: v.Meter04KVType, + Name: v.ParkName, + Phone: v.ParkPhone, + Region: &v.ParkRegion, + Tenement: tools.NullDecimalToString(v.TenementQuantity), + UserID: v.UserID, + } + userInfo := model.UserInfos{ + Address: v.Address, + Contact: &v.Contact, + ID: v.ID, + Name: v.Name, + Phone: &v.Phone, + Region: v.Region, } - areaStr := tools.NullDecimalToString(park[0].Area) - capacityStr := tools.NullDecimalToString(park[0].Capacity) - TenementQuantityStr := tools.NullDecimalToString(park[0].TenementQuantity) - //构建简易园区数据 - simplifiedPark := model.SimplifiedPark{ - Address: park[0].Address, - Area: areaStr, - Capacity: capacityStr, - Category: park[0].Category, - Contact: park[0].Contact, - ID: park[0].Id, - Meter04KvType: park[0].MeterType, - Name: park[0].Name, - Phone: park[0].Phone, - Region: park[0].Region, - Tenement: TenementQuantityStr, - UserID: park[0].UserId, - } - - userQuery, userQueryArgs, _ := wd.ds. - From(goqu.T("user_detail")). - Where(goqu.I("id").Eq(park[0].UserId)). - Select("*").ToSQL() - - userInfo := make([]model.UserDetail, 0) - - err = pgxscan.Select(ctx, global.DB, &userInfo, userQuery, userQueryArgs...) - fmt.Println("读到的用户数据:", userInfo) - if err != nil { - fmt.Println(err) - return []model.Withdraw{}, 0, err - } - - simplifiedUser := model.UserInfos{ - Address: userInfo[0].Address, - Contact: userInfo[0].Contact, - ID: userInfo[0].Id, - Name: userInfo[0].Name, - Phone: userInfo[0].Phone, - Region: userInfo[0].Region, - } - - withdrawRes.Report = simplifiedReport - withdrawRes.Park = simplifiedPark - withdrawRes.User = simplifiedUser - + withdrawRes.Report = report + withdrawRes.Park = park + withdrawRes.User = userInfo withdrawReses = append(withdrawReses, withdrawRes) } - total := len(reports) - - return withdrawReses, int64(total), nil + return withdrawReses, total, nil } diff --git a/tools/utils.go b/tools/utils.go index fceb81b..af61a41 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "github.com/mozillazg/go-pinyin" "github.com/samber/lo" @@ -158,3 +159,15 @@ func NullTime2PointerString(nullTime sql.NullTime) *string { return strPtr } } + + +//该方法用于将时间解析为字符串指针 +func TimeToStringPtr(t *time.Time) *string { + if t == nil { + return nil + } + + timeStr := t.Format("2006-01-02 15:04:05") + return &timeStr +} + From d8a29e7d17d3e54e7e2e7600318cab2449bab518 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Tue, 25 Jul 2023 15:31:35 +0800 Subject: [PATCH 120/141] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E6=A3=80=E7=B4=A2=E6=A0=B8=E7=AE=97=E6=8A=A5=E8=A1=A8=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E7=BB=86=E8=8A=82=EF=BC=8C=E5=AE=8C=E6=88=90=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E6=92=A4=E5=9B=9E=E6=8A=A5=E8=A1=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/withdraw.go | 56 +++++++++++++++++++++++--- repository/withdraw.go | 91 ++++++++++++++++++++++++++++++++++++------ vo/withdraw.go | 6 +++ 3 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 vo/withdraw.go diff --git a/controller/withdraw.go b/controller/withdraw.go index d6c7510..ed14265 100644 --- a/controller/withdraw.go +++ b/controller/withdraw.go @@ -4,6 +4,8 @@ import ( "electricity_bill_calc/logger" "electricity_bill_calc/repository" "electricity_bill_calc/response" + "electricity_bill_calc/security" + "electricity_bill_calc/vo" "github.com/gofiber/fiber/v2" "go.uber.org/zap" "net/http" @@ -12,10 +14,11 @@ import ( var withdrawLog = logger.Named("Handler", "Withdraw") func InitializeWithdrawHandlers(router *fiber.App) { - router.Get("/withdraw", withdraw) + router.Get("/withdraw", security.OPSAuthorize, withdraw) + router.Put("/withdraw/:rid", ReviewRequestWithdraw) } -//用于检索用户的核算报表 +//用于分页检索用户的核算报表 func withdraw(c *fiber.Ctx) error { //记录日志 withdrawLog.Info("带分页的待审核的核算撤回申请列表") @@ -27,14 +30,55 @@ func withdraw(c *fiber.Ctx) error { //中间数据库操作暂且省略。。。。 //首先进行核算报表的分页查询 withdraws, total, err := repository.WithdrawRepository.FindWithdraw(uint(page), &keyword) - if err != nil { - withdrawLog.Error("检索用户核算报表失败。", zap.Error(err)) - return result.Error(http.StatusInternalServerError, err.Error()) + if err != nil { + withdrawLog.Error("检索用户核算报表失败。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) } - //TODO: 2023-07-18 此处返回值是个示例,具体返回值需要查询数据库 + //TODO: 2023-07-18 此处返回值是个示例,具体返回值需要查询数据库(完成) return result.Success( "withdraw请求成功", response.NewPagedResponse(page, total).ToMap(), fiber.Map{"records": withdraws}, ) } + +//用于审核撤回报表 +func ReviewRequestWithdraw(c *fiber.Ctx) error { + Rid := c.Params("rid", "") + Data := new(vo.ReviewWithdraw) + result := response.NewResult(c) + + if err := c.BodyParser(&Data); err != nil { + withdrawLog.Error("无法解析审核指定报表的请求数据", zap.Error(err)) + return result.BadRequest("无法解析审核指定报表的请求数据。") + } + + if Data.Audit == true { //审核通过 + ok, err := repository.WithdrawRepository.ReviewTrueReportWithdraw(Rid) + if err != nil { + withdrawLog.Error("审核同意撤回报表失败") + return result.Error(http.StatusInternalServerError, err.Error()) + } + + if !ok { + withdrawLog.Error("审核同意撤回报表失败") + return result.NotAccept("审核同意撤回报表失败") + } else { + return result.Success("审核同意撤回报表成功!") + } + } else { //审核不通过 + ok, err := repository.WithdrawRepository.ReviewFalseReportWithdraw(Rid) + if err != nil { + withdrawLog.Error("审核拒绝撤回报表失败") + return result.Error(http.StatusInternalServerError, err.Error()) + } + + if !ok { + withdrawLog.Error("审核拒绝撤回报表失败") + return result.NotAccept("审核拒绝撤回报表失败") + } else { + return result.Success("审核拒绝撤回报表成功!") + } + } + +} diff --git a/repository/withdraw.go b/repository/withdraw.go index 4fe000c..540c5a3 100644 --- a/repository/withdraw.go +++ b/repository/withdraw.go @@ -6,6 +6,7 @@ import ( "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/tools" + "electricity_bill_calc/types" "fmt" "github.com/doug-martin/goqu/v9" "github.com/georgysavva/scany/v2/pgxscan" @@ -22,17 +23,9 @@ var WithdrawRepository = &_WithdrawRepository{ ds: goqu.Dialect("postgres"), } -/** - * @author: ZiHangQin - * 该方法用于分页查询核算报表 - * @param:page - * @param: keyword - * @return:[]object - * @return:total - * @return: error - */ +//该方法用于分页查询核算报表 func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model.Withdraw, int64, error) { - wd.log.Info("查询用户的充值记录。", zap.Stringp("keyword", keyword), zap.Int("page", int(page))) + wd.log.Info("查询核算报表", zap.Stringp("keyword", keyword), zap.Int("page", int(page))) ctx, cancel := global.TimeoutContext() defer cancel() @@ -66,6 +59,8 @@ func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model. Where(goqu.I("withdraw").Eq(1)). Join(goqu.T("park").As("p"), goqu.On(goqu.I("r.park_id").Eq(goqu.I("p.id")))). Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("p.user_id").Eq(goqu.I("ud.id")))). + Where(goqu.I("p.deleted_at").IsNull()). + Where(goqu.I("ud.deleted_at").IsNull()). Select( goqu.I("r.id").As("report_id"), goqu.I("r.last_withdraw_applied_at"), goqu.I("r.last_withdraw_audit_at"), goqu.I("r.park_id").As("report_park_id"), goqu.I("r.period"), goqu.I("r.published"), goqu.I("r.published_at"), goqu.I("r.withdraw"), @@ -99,7 +94,6 @@ func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model. countReportQuerySql, countReportQueryArgs, _ := countReportQuery.Prepared(true).ToSQL() - var ( reports []*model.ReportRes = make([]*model.ReportRes, 0) total int64 @@ -122,9 +116,8 @@ func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model. return make([]model.Withdraw, 0), total, nil } - fmt.Println(&reports) var withdrawReses []model.Withdraw - //TODO: 2023.07.24对查询到的数据进行拼接 + //TODO: 2023.07.24对查询到的数据进行拼接(完成) for _, v := range reports { Begin := v.Period.SafeLower().Format("2006-01-02") End := v.Period.SafeUpper().Format("2006-01-02") @@ -174,3 +167,75 @@ func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model. return withdrawReses, total, nil } + +//该方法用于审核同意报表撤回 +func (wd _WithdrawRepository) ReviewTrueReportWithdraw( rid string) (bool, error) { + wd.log.Info("审核指定的报表", zap.String("rid", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + //update report set withdraw=$2, + //last_withdraw_audit_at=$3, published=false, + //published_at=null where id=$1 + + tx, err := global.DB.Begin(ctx) + if err != nil { + wd.log.Error("开启数据库事务失败", zap.Error(err)) + } + updateQuerySql, updateArgs, _ := wd.ds. + Update(goqu.T("report")). + Set(goqu.Record{ + "withdraw": 3, + "last_withdraw_audit_at": types.Now(), + "published": false, + "published_at": nil, + }). + Where(goqu.I("id").Eq(rid)). + Prepared(true).ToSQL() + + rs, err := tx.Exec(ctx, updateQuerySql, updateArgs...) + if err != nil { + wd.log.Error("审核报表失败", zap.Error(err)) + return false, err + } + err = tx.Commit(ctx) + if err != nil { + wd.log.Error("提交数据库事务失败", zap.Error(err)) + return false, err + } + + return rs.RowsAffected() > 0, nil +} + +func (wd _WithdrawRepository) ReviewFalseReportWithdraw( rid string) (bool, error) { + wd.log.Info("审核指定的报表", zap.String("rid", rid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + wd.log.Error("开启数据库事务失败", zap.Error(err)) + } + updateQuerySql, updateArgs, _ := wd.ds. + Update(goqu.T("report")). + Set(goqu.Record{ + "withdraw": 2, + "last_withdraw_audit_at": types.Now(), + "published": false, + "published_at": nil, + }). + Where(goqu.I("id").Eq(rid)). + Prepared(true).ToSQL() + + rs, err := tx.Exec(ctx, updateQuerySql, updateArgs...) + if err != nil { + wd.log.Error("审核报表失败", zap.Error(err)) + return false, err + } + err = tx.Commit(ctx) + if err != nil { + wd.log.Error("提交数据库事务失败", zap.Error(err)) + return false, err + } + + return rs.RowsAffected() > 0, nil +} \ No newline at end of file diff --git a/vo/withdraw.go b/vo/withdraw.go new file mode 100644 index 0000000..1aa6019 --- /dev/null +++ b/vo/withdraw.go @@ -0,0 +1,6 @@ +package vo + +//用于接收审核报表的参数 +type ReviewWithdraw struct { + Audit bool `json:"audit"` +} From b3032638fcddff72d95b8475a64af1186dc4dfa7 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Tue, 25 Jul 2023 15:34:30 +0800 Subject: [PATCH 121/141] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E6=92=A4=E5=9B=9E=E6=8A=A5=E8=A1=A8=E7=94=A8=E6=88=B7=E9=89=B4?= =?UTF-8?q?=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/withdraw.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/withdraw.go b/controller/withdraw.go index ed14265..868bc72 100644 --- a/controller/withdraw.go +++ b/controller/withdraw.go @@ -15,7 +15,7 @@ var withdrawLog = logger.Named("Handler", "Withdraw") func InitializeWithdrawHandlers(router *fiber.App) { router.Get("/withdraw", security.OPSAuthorize, withdraw) - router.Put("/withdraw/:rid", ReviewRequestWithdraw) + router.Put("/withdraw/:rid",security.OPSAuthorize, ReviewRequestWithdraw) } //用于分页检索用户的核算报表 From 251c44049a3c52225e941397ba057a0fdebc804b Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Wed, 26 Jul 2023 08:52:24 +0800 Subject: [PATCH 122/141] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E6=92=A4=E5=9B=9E=E6=8A=A5=E8=A1=A8=E8=AF=B7=E6=B1=82=E7=BB=86?= =?UTF-8?q?=E8=8A=82=EF=BC=8C=E5=AE=8C=E6=88=90=E6=92=A4=E5=9B=9E=E7=94=B5?= =?UTF-8?q?=E8=B4=B9=E6=A0=B8=E7=AE=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/withdraw.go | 54 ++++++++++++++++++++++++++++++++++++++++-- repository/report.go | 2 +- repository/withdraw.go | 5 ++-- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/controller/withdraw.go b/controller/withdraw.go index 868bc72..f9d6695 100644 --- a/controller/withdraw.go +++ b/controller/withdraw.go @@ -15,7 +15,8 @@ var withdrawLog = logger.Named("Handler", "Withdraw") func InitializeWithdrawHandlers(router *fiber.App) { router.Get("/withdraw", security.OPSAuthorize, withdraw) - router.Put("/withdraw/:rid",security.OPSAuthorize, ReviewRequestWithdraw) + router.Put("/withdraw/:rid", security.OPSAuthorize, reviewRequestWithdraw) + router.Delete("/withdraw/:rid", security.EnterpriseAuthorize, recallReport) } //用于分页检索用户的核算报表 @@ -43,7 +44,7 @@ func withdraw(c *fiber.Ctx) error { } //用于审核撤回报表 -func ReviewRequestWithdraw(c *fiber.Ctx) error { +func reviewRequestWithdraw(c *fiber.Ctx) error { Rid := c.Params("rid", "") Data := new(vo.ReviewWithdraw) result := response.NewResult(c) @@ -82,3 +83,52 @@ func ReviewRequestWithdraw(c *fiber.Ctx) error { } } + +//用于撤回电费核算 +func recallReport(c *fiber.Ctx) error { + // 获取用户会话信息和参数 + rid := c.Params("rid", "") + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + withdrawLog.Error("无法获取当前用户的会话。") + return result.Unauthorized(err.Error()) + } + // 检查指定报表的所属情况 + isBelongsTo, err := repository.ReportRepository.IsBelongsTo(rid, session.Uid) + if err != nil { + withdrawLog.Error("检查报表所属情况出现错误。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + + if err == nil && isBelongsTo == true { + // 判断指定报表是否是当前园区的最后一张报表 + isLastReport, err := repository.ReportRepository.IsLastReport(rid) + if err != nil { + withdrawLog.Error("判断指定报表是否为当前园区的最后一张报表时出错", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + + if err == nil && isLastReport == true { + // 申请撤回指定的核算报表 + //TODO: 2023.07.25 申请撤回指定核算报表,正确状态未处理(完成) + ok, err := repository.ReportRepository.ApplyWithdrawReport(rid) + if err != nil { + withdrawLog.Error("申请撤回指定核算报表出错", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + if ok { + withdrawLog.Info("申请撤回指定核算报表成功") + return result.Success("申请撤回指定核算报表成功") + } + } else { + withdrawLog.Info("当前报表不是当前园区的最后一张报表") + return result.Error(http.StatusForbidden, "当前报表不是当前园区的最后一张报表") + } + } else { + withdrawLog.Info("指定的核算报表不属于当前用户。") + return result.Error(http.StatusForbidden, "指定的核算报表不属于当前用户") + } + + return result.Error(http.StatusInternalServerError, "其他错误") +} diff --git a/repository/report.go b/repository/report.go index a881c97..b8a4478 100644 --- a/repository/report.go +++ b/repository/report.go @@ -678,7 +678,7 @@ func (rr _ReportRepository) IsLastReport(rid string) (bool, error) { defer cancel() checkSql, checkArgs, _ := rr.ds. - From(goqu.T("report")). + From(goqu.T("report").As("r")). Select(goqu.COUNT("*")). Where( goqu.I("r.id").Eq(rid), diff --git a/repository/withdraw.go b/repository/withdraw.go index 540c5a3..a83b43a 100644 --- a/repository/withdraw.go +++ b/repository/withdraw.go @@ -184,7 +184,7 @@ func (wd _WithdrawRepository) ReviewTrueReportWithdraw( rid string) (bool, error updateQuerySql, updateArgs, _ := wd.ds. Update(goqu.T("report")). Set(goqu.Record{ - "withdraw": 3, + "withdraw": model.REPORT_WITHDRAW_GRANTED, "last_withdraw_audit_at": types.Now(), "published": false, "published_at": nil, @@ -206,6 +206,7 @@ func (wd _WithdrawRepository) ReviewTrueReportWithdraw( rid string) (bool, error return rs.RowsAffected() > 0, nil } +//该方法用于审核拒绝报表撤回 func (wd _WithdrawRepository) ReviewFalseReportWithdraw( rid string) (bool, error) { wd.log.Info("审核指定的报表", zap.String("rid", rid)) ctx, cancel := global.TimeoutContext() @@ -218,7 +219,7 @@ func (wd _WithdrawRepository) ReviewFalseReportWithdraw( rid string) (bool, erro updateQuerySql, updateArgs, _ := wd.ds. Update(goqu.T("report")). Set(goqu.Record{ - "withdraw": 2, + "withdraw": model.REPORT_WITHDRAW_DENIED, "last_withdraw_audit_at": types.Now(), "published": false, "published_at": nil, From 39e404451edc2491516f04e4b9385c30c30617f0 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Wed, 26 Jul 2023 10:03:01 +0800 Subject: [PATCH 123/141] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=9F=BA=E5=87=86=E7=BA=BF=E6=8D=9F=E7=8E=87?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.go | 11 +++++++++++ controller/foundation.go | 24 ++++++++++++++++++++++++ router/router.go | 1 + settings.yaml | 2 ++ 4 files changed, 38 insertions(+) create mode 100644 controller/foundation.go diff --git a/config/settings.go b/config/settings.go index b8403ec..b009064 100644 --- a/config/settings.go +++ b/config/settings.go @@ -36,12 +36,18 @@ type ServiceSetting struct { HostSerial int64 } +//读取基准线损率 +type BaseLossSetting struct { + Base string +} + // 定义全局变量 var ( ServerSettings *ServerSetting DatabaseSettings *DatabaseSetting RedisSettings *RedisSetting ServiceSettings *ServiceSetting + BaseLoss *BaseLossSetting ) // 读取配置到全局变量 @@ -69,5 +75,10 @@ func SetupSetting() error { if err != nil { return err } + + err = s.ReadSection("BaselineLineLossRatio", &BaseLoss) + if err != nil { + return err + } return nil } diff --git a/controller/foundation.go b/controller/foundation.go new file mode 100644 index 0000000..65cab6b --- /dev/null +++ b/controller/foundation.go @@ -0,0 +1,24 @@ +package controller + +import ( + "electricity_bill_calc/config" + "electricity_bill_calc/logger" + "electricity_bill_calc/response" + "electricity_bill_calc/security" + "github.com/gofiber/fiber/v2" +) + +var foundLog = logger.Named("Handler", "Foundation") + +func InitializeFoundationHandlers(router *fiber.App) { + router.Get("/norm/authorized/loss/rate", security.MustAuthenticated, getNormAuthorizedLossRate) +} + +func getNormAuthorizedLossRate(c *fiber.Ctx) error { + foundLog.Info("获取系统中定义的基准核定线损率") + result := response.NewResult(c) + BaseLoss := config.BaseLoss + return result.Success("已经获取到系统设置的基准核定线损率。", + fiber.Map{"normAuthorizedLossRate": BaseLoss}, + ) +} diff --git a/router/router.go b/router/router.go index f1db451..38af715 100644 --- a/router/router.go +++ b/router/router.go @@ -56,6 +56,7 @@ func App() *fiber.App { controller.InitializeWithdrawHandlers(app) + controller.InitializeFoundationHandlers(app) return app } diff --git a/settings.yaml b/settings.yaml index 0c4d01c..9968ebe 100644 --- a/settings.yaml +++ b/settings.yaml @@ -21,3 +21,5 @@ Service: ItemsPageSize: 20 CacheLifeTime: 5m HostSerial: 5 +BaselineLineLossRatio: + Base: 基准线损率 \ No newline at end of file From 9ad3415cdb3e8c559cf335894707767eb8c404eb Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Wed, 26 Jul 2023 13:43:30 +0800 Subject: [PATCH 124/141] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E7=B3=BB=E7=BB=9F=E4=B8=AD=E5=BE=85=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E7=9A=84=E5=86=85=E5=AE=B9=E6=95=B0=E9=87=8F=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/statistics.go | 34 ++++++++++++++++++++++++++++++++++ router/router.go | 14 +++++++------- service/withdraw.go | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 controller/statistics.go create mode 100644 service/withdraw.go diff --git a/controller/statistics.go b/controller/statistics.go new file mode 100644 index 0000000..fb9ab37 --- /dev/null +++ b/controller/statistics.go @@ -0,0 +1,34 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "electricity_bill_calc/response" + "electricity_bill_calc/security" + "electricity_bill_calc/service" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "net/http" +) + +var StatisticsWithdrawLog = logger.Named("Handler", "StatisticsWithdraw") + +func InitializeStatisticsController(router *fiber.App) { + router.Get("/audits", security.OPSAuthorize, currentAuditAmount) + +} + +//获取当前系统中待审核的内容数量 +func currentAuditAmount(c *fiber.Ctx) error { + StatisticsWithdrawLog.Info("开始获取当前系统中待审核的内容数量") + result := response.NewResult(c) + amount, err := service.WithdrawService.AuditWaits() + if err != nil { + StatisticsWithdrawLog.Error("获取当前系统中待审核的内容数量出错", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + + return result.Success("已经获取到指定的统计信息", + fiber.Map{"withdraw": amount}) +} + + diff --git a/router/router.go b/router/router.go index 38af715..df7c003 100644 --- a/router/router.go +++ b/router/router.go @@ -34,15 +34,15 @@ func App() *fiber.App { JSONEncoder: json.Marshal, //json编码 JSONDecoder: json.Unmarshal, //json解码 }) - app.Use(compress.New()) //压缩中间件 + app.Use(compress.New()) //压缩中间件 app.Use(recover.New(recover.Config{ EnableStackTrace: true, StackTraceHandler: stackTraceHandler, - })) //恢复中间件 + })) //恢复中间件 app.Use(logger.NewLogMiddleware(logger.LogMiddlewareConfig{ Logger: logger.Named("App"), - })) //日志中间件 - app.Use(security.SessionRecovery) //会话恢复中间件 + })) //日志中间件 + app.Use(security.SessionRecovery) //会话恢复中间件 controller.InitializeUserHandlers(app) controller.InitializeRegionHandlers(app) @@ -54,9 +54,9 @@ func App() *fiber.App { controller.InitializeTopUpHandlers(app) controller.InitializeReportHandlers(app) - - controller.InitializeWithdrawHandlers(app) - controller.InitializeFoundationHandlers(app) + controller.InitializeWithdrawHandlers(app) // 公示撤回 + controller.InitializeFoundationHandlers(app) // 基础数据 + controller.InitializeStatisticsController(app) // 首页信息 return app } diff --git a/service/withdraw.go b/service/withdraw.go new file mode 100644 index 0000000..caa3f09 --- /dev/null +++ b/service/withdraw.go @@ -0,0 +1,39 @@ +package service + +import ( + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "github.com/doug-martin/goqu/v9" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _WithdrawService struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var WithdrawService = _WithdrawService{ + logger.Named("Service", "Withdraw"), + goqu.Dialect("postgres"), +} + +func (wd _WithdrawService) AuditWaits() (int64, error) { + wd.log.Info("获取当前系统中待审核的内容数量。") + ctx, cancel := global.TimeoutContext() + defer cancel() + + CountWithdrawQuery, CountWithdrawQueryArgs, _ := wd.ds. + From(goqu.T("report")). + Where(goqu.I("withdraw").Eq(model.REPORT_WITHDRAW_APPLYING)). + Select(goqu.COUNT("*")).ToSQL() + + var total int64 + err := pgxscan.Get(ctx, global.DB, &total, CountWithdrawQuery,CountWithdrawQueryArgs...) + if err != nil { + wd.log.Error("获取当前系统中待审核的内容数量出错。") + return 0,err + } + return total,nil +} From 5866882c2d1f4488fa1c88c0a6844e6719275ec1 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Wed, 26 Jul 2023 15:11:16 +0800 Subject: [PATCH 125/141] =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E4=B8=AD=E7=9A=84=E6=8A=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/statistics.go | 51 +++++++++++++++++++++++++++ model/park.go | 9 ++++- service/statistics.go | 74 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 service/statistics.go diff --git a/controller/statistics.go b/controller/statistics.go index fb9ab37..2d2c07e 100644 --- a/controller/statistics.go +++ b/controller/statistics.go @@ -2,6 +2,7 @@ package controller import ( "electricity_bill_calc/logger" + "electricity_bill_calc/model" "electricity_bill_calc/response" "electricity_bill_calc/security" "electricity_bill_calc/service" @@ -14,6 +15,7 @@ var StatisticsWithdrawLog = logger.Named("Handler", "StatisticsWithdraw") func InitializeStatisticsController(router *fiber.App) { router.Get("/audits", security.OPSAuthorize, currentAuditAmount) + router.Get("/stat/reports", statReports) } @@ -31,4 +33,53 @@ func currentAuditAmount(c *fiber.Ctx) error { fiber.Map{"withdraw": amount}) } +func statReports(c *fiber.Ctx) error { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + return result.Unauthorized(err.Error()) + } + var ( + enterprises int64 = 0 + parks int64 = 0 + reports []model.ParkPeriodStatistics + ) + if session.Type != 0 { + enterprises, err = service.StatisticsService.EnabledEnterprises() + if err != nil { + StatisticsWithdrawLog.Error(err.Error()) + return result.Error(http.StatusInternalServerError, err.Error()) + } + parks, err = service.StatisticsService.EnabledParks() + if err != nil { + StatisticsWithdrawLog.Error(err.Error()) + return result.Error(http.StatusInternalServerError, err.Error()) + } + //TODO: 2023.07.26 报表数据库结构改变,此处逻辑复杂放在最后处理 + reports, err = service.StatisticsService.ParkNewestState() + if err != nil { + StatisticsWithdrawLog.Error(err.Error()) + return result.Error(http.StatusInternalServerError, err.Error()) + } + } else { + parks, err = service.StatisticsService.EnabledParks(session.Uid) + if err != nil { + StatisticsWithdrawLog.Error(err.Error()) + return result.Error(http.StatusInternalServerError, err.Error()) + } + //TODO: 2023.07.26 报表数据库结构改变,此处逻辑复杂放在最后处理 + reports, err = service.StatisticsService.ParkNewestState(session.Uid) + if err != nil { + StatisticsWithdrawLog.Error(err.Error()) + return result.Error(http.StatusInternalServerError, err.Error()) + } + } + return result.Success("已经完成园区报告的统计。", fiber.Map{ + "statistics": fiber.Map{ + "enterprises": enterprises, + "parks": parks, + "reports": reports, + }, + }) +} diff --git a/model/park.go b/model/park.go index c00df57..b4e3435 100644 --- a/model/park.go +++ b/model/park.go @@ -1,6 +1,7 @@ package model import ( + "electricity_bill_calc/types" "time" "github.com/shopspring/decimal" @@ -35,4 +36,10 @@ type Park struct { type Parks struct { Park NormAuthorizedLossRate float64 `json:"norm_authorized_loss_rate"` -} +} + +type ParkPeriodStatistics struct { + Id string `json:"id"` + Name string `json:"name"` + Period *types.DateRange +} diff --git a/service/statistics.go b/service/statistics.go new file mode 100644 index 0000000..7284984 --- /dev/null +++ b/service/statistics.go @@ -0,0 +1,74 @@ +package service + +import ( + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "github.com/doug-martin/goqu/v9" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _StatisticsService struct { + l *zap.Logger + ss goqu.DialectWrapper +} + +var StatisticsService = _StatisticsService{ + logger.Named("Service", "Stat"), + goqu.Dialect("postgres"), +} + +//用于统计企业用户数量 +func (ss _StatisticsService) EnabledEnterprises() (int64, error) { + ss.l.Info("开始统计企业数量。") + ctx, cancel := global.TimeoutContext() + defer cancel() + + UserCountQuery, UserCountQueryArgs, _ := ss.ss. + From(goqu.T("user")). + Where(goqu.I("type").Eq(model.USER_TYPE_ENT)). + Where(goqu.I("enabled").Eq(true)). + Select(goqu.COUNT("*")).ToSQL() + + var c int64 + err := pgxscan.Get(ctx, global.DB, &c, UserCountQuery, UserCountQueryArgs...) + if err != nil { + ss.l.Error("统计企业数量出错", zap.Error(err)) + return 0, err + } + return c, nil +} + +//用于统计园区数量 +func (ss _StatisticsService) EnabledParks(userIds ...string) (int64, error) { + ss.l.Info("开始统计园区数量", zap.Strings("userId", userIds)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + ParkCountQuery := ss.ss. + From(goqu.T("park")). + Where(goqu.I("enabled").Eq(true)) + + if len(userIds) > 0 { + ParkCountQuery = ParkCountQuery.Where(goqu.I("user_id").In(userIds)) + } + + ParkCountQuerySql, ParkCountQueryArgs, _ := ParkCountQuery.Select(goqu.COUNT("*")).ToSQL() + + var c int64 + err := pgxscan.Get(ctx, global.DB, &c, ParkCountQuerySql, ParkCountQueryArgs...) + if err != nil { + ss.l.Error("园区数量统计错误", zap.Error(err)) + return 0, err + } + + return c, nil +} + +//用户统计报表 +func (ss _StatisticsService) ParkNewestState(userIds ...string) ([]model.ParkPeriodStatistics, error) { + //TODO: 2023.07.26 报表数据库结构改变,此处逻辑复杂放在最后处理 + //return nil,errors.New("还未处理逻辑") + return []model.ParkPeriodStatistics{}, nil +} From 1099a7c335a57248356c3d5621a4df910d7cf8e3 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Thu, 27 Jul 2023 14:01:45 +0800 Subject: [PATCH 126/141] =?UTF-8?q?[=E5=A4=A9=E7=A5=9E=E6=A8=A1=E5=BC=8F]?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8C=87=E5=AE=9A=E5=95=86=E6=88=B7=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 40 ++++++++++++++++++++++++ repository/god_mode.go | 70 ++++++++++++++++++++++++++++++++++++++++++ router/router.go | 1 + service/god_mode.go | 46 +++++++++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 controller/god_mode.go create mode 100644 repository/god_mode.go create mode 100644 service/god_mode.go diff --git a/controller/god_mode.go b/controller/god_mode.go new file mode 100644 index 0000000..134dc14 --- /dev/null +++ b/controller/god_mode.go @@ -0,0 +1,40 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "electricity_bill_calc/response" + "electricity_bill_calc/service" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +var GmLog = logger.Named("Handler", "GM") + +func InitializeGmController(router *fiber.App) { + router.Delete("/gm/tenement", DeleteTenement) +} + +//用于将参数转化为切片 +func getQueryValues(c *fiber.Ctx, paramName string) []string { + values := c.Request().URI().QueryArgs().PeekMulti(paramName) + result := make([]string, len(values)) + for i, v := range values { + result[i] = string(v) + } + return result +} + +func DeleteTenement(c *fiber.Ctx) error { + park := c.Query("park", "") + tenements := getQueryValues(c, "tenements") + result := response.NewResult(c) + GmLog.Info("[天神模式]删除指定园区中的商户", zap.String("park", park), zap.Strings("tenements", tenements)) + + err := service.GMService.DeleteTenements(park, tenements) + if err != nil { + GmLog.Error("[天神模式]删除指定园区中的商户失败", zap.Error(err)) + return result.Error(500, err.Error()) + } + + return result.Success("指定商户已经删除。") +} diff --git a/repository/god_mode.go b/repository/god_mode.go new file mode 100644 index 0000000..38f5040 --- /dev/null +++ b/repository/god_mode.go @@ -0,0 +1,70 @@ +package repository + +import ( + "context" + "electricity_bill_calc/logger" + "fmt" + "github.com/doug-martin/goqu/v9" + "github.com/jackc/pgx/v5" + "go.uber.org/zap" +) + +type _GMRepository struct { + log *zap.Logger + ds goqu.DialectWrapper +} + +var GMRepository = &_GMRepository{ + log: logger.Named("Repository", "GM"), + ds: goqu.Dialect("postgres"), +} + +func (gm _GMRepository) DeleteMeterBinding(ctx context.Context, tx pgx.Tx, pid string, tenements []string, meterCodes ...[]string) error { + DeleteQuery := gm.ds.From(goqu.T("tenement_meter")). + Where(goqu.I("park_id").Eq(pid)). + Delete() + + if len(tenements) > 0 { + DeleteQuery = DeleteQuery. + Where(goqu.I("tenement_id").In(tenements)) + } + + if len(meterCodes) > 0 { + DeleteQuery = DeleteQuery. + Where(goqu.I("meter_id").In(meterCodes)) + } + + DeleteQuerySql, DeleteQueryArgs, _ := DeleteQuery.ToSQL() + + + _, err := tx.Exec(ctx, DeleteQuerySql, DeleteQueryArgs...) + if err != nil { + gm.log.Error("数据库在删除tenement_meter表数据中出错", zap.Error(err)) + tx.Rollback(ctx) + return err + } + return nil +} + +func (gm _GMRepository) DeleteTenements(ctx context.Context, tx pgx.Tx, pid string, tenements []string) error { + DeleteTenements := gm.ds. + From("tenement"). + Where(goqu.I("park_id").Eq(pid)). + Delete() + + fmt.Println(len(tenements)) + if len(tenements) > 0 { + DeleteTenements = DeleteTenements. + Where(goqu.I("id").In(tenements)) + } + + DeleteTenementsSql, DeleteTenementsArgs, _ := DeleteTenements.ToSQL() + + _, err := tx.Exec(ctx, DeleteTenementsSql, DeleteTenementsArgs...) + if err != nil { + tx.Rollback(ctx) + gm.log.Error("删除商户信息出错",zap.Error(err)) + return err + } + return nil +} diff --git a/router/router.go b/router/router.go index df7c003..e09f7b7 100644 --- a/router/router.go +++ b/router/router.go @@ -57,6 +57,7 @@ func App() *fiber.App { controller.InitializeWithdrawHandlers(app) // 公示撤回 controller.InitializeFoundationHandlers(app) // 基础数据 controller.InitializeStatisticsController(app) // 首页信息 + controller.InitializeGmController(app) // 天神模式 return app } diff --git a/service/god_mode.go b/service/god_mode.go new file mode 100644 index 0000000..462cf9c --- /dev/null +++ b/service/god_mode.go @@ -0,0 +1,46 @@ +package service + +import ( + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/repository" + "fmt" + "github.com/doug-martin/goqu/v9" + "go.uber.org/zap" +) + +type _GMService struct { + l *zap.Logger + gm goqu.DialectWrapper +} + +var GMService = _GMService{ + logger.Named("Service", "GM"), + goqu.Dialect("postgres"), +} + +func (gm _GMService) DeleteTenements(pid string, tenements []string) error { + var err error + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + gm.l.Error("未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + err = repository.GMRepository.DeleteMeterBinding(ctx, tx, pid, tenements) + if err != nil { + tx.Rollback(ctx) + return err + } + + err = repository.GMRepository.DeleteTenements(ctx, tx, pid, tenements) + if err != nil { + tx.Rollback(ctx) + return err + } + tx.Commit(ctx) + return nil +} From b64929c10a06cc3243dd431c4d9715af55b23e54 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Thu, 27 Jul 2023 14:15:34 +0800 Subject: [PATCH 127/141] =?UTF-8?q?=E7=BB=99[=E5=A4=A9=E7=A5=9E=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F]=E5=88=A0=E9=99=A4=E6=8C=87=E5=AE=9A=E5=95=86?= =?UTF-8?q?=E6=88=B7=E5=8A=9F=E8=83=BD=E6=B7=BB=E5=8A=A0=E6=9D=83=E9=99=90?= =?UTF-8?q?=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 3 ++- controller/statistics.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/controller/god_mode.go b/controller/god_mode.go index 134dc14..eb8fcfd 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -3,6 +3,7 @@ package controller import ( "electricity_bill_calc/logger" "electricity_bill_calc/response" + "electricity_bill_calc/security" "electricity_bill_calc/service" "github.com/gofiber/fiber/v2" "go.uber.org/zap" @@ -11,7 +12,7 @@ import ( var GmLog = logger.Named("Handler", "GM") func InitializeGmController(router *fiber.App) { - router.Delete("/gm/tenement", DeleteTenement) + router.Delete("/gm/tenement", security.SingularityAuthorize, DeleteTenement) } //用于将参数转化为切片 diff --git a/controller/statistics.go b/controller/statistics.go index 2d2c07e..fd0555c 100644 --- a/controller/statistics.go +++ b/controller/statistics.go @@ -15,7 +15,7 @@ var StatisticsWithdrawLog = logger.Named("Handler", "StatisticsWithdraw") func InitializeStatisticsController(router *fiber.App) { router.Get("/audits", security.OPSAuthorize, currentAuditAmount) - router.Get("/stat/reports", statReports) + router.Get("/stat/reports", security.OPSAuthorize, statReports) } From 8ab89bca3409279d6ed03638cb955bfacadff501 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 28 Jul 2023 16:25:49 +0800 Subject: [PATCH 128/141] =?UTF-8?q?[=E5=A4=A9=E7=A5=9E=E6=A8=A1=E5=BC=8F]?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8C=87=E5=AE=9A=E7=9A=84=E5=9B=AD=E5=8C=BA?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 21 +++ repository/god_mode.go | 328 ++++++++++++++++++++++++++++++++++++++++- service/god_mode.go | 41 ++++++ 3 files changed, 387 insertions(+), 3 deletions(-) diff --git a/controller/god_mode.go b/controller/god_mode.go index eb8fcfd..b401838 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -5,14 +5,17 @@ import ( "electricity_bill_calc/response" "electricity_bill_calc/security" "electricity_bill_calc/service" + "errors" "github.com/gofiber/fiber/v2" "go.uber.org/zap" + "net/http" ) var GmLog = logger.Named("Handler", "GM") func InitializeGmController(router *fiber.App) { router.Delete("/gm/tenement", security.SingularityAuthorize, DeleteTenement) + router.Delete("/gm/park", security.SingularityAuthorize, DeletePark) } //用于将参数转化为切片 @@ -39,3 +42,21 @@ func DeleteTenement(c *fiber.Ctx) error { return result.Success("指定商户已经删除。") } + +func DeletePark(c *fiber.Ctx) error { + parks := getQueryValues(c, "parks") + result := response.NewResult(c) + GmLog.Info("[天神模式]删除指定园区", zap.Strings("parks", parks)) + + if len(parks) < 0 { + GmLog.Info("[天神模式]用户未指派园区参数或者未指定需要删除的园区。") + return result.Error(http.StatusBadRequest, error.Error(errors.New("必须至少指定一个需要删除的园区!"))) + } + + err := service.GMService.DeleteParks(parks) + if err != nil { + GmLog.Error("[天神模式]删除指定园区失败",zap.Error(err)) + return result.Error(500,err.Error()) + } + return result.Success("指定园区已经删除。") +} diff --git a/repository/god_mode.go b/repository/god_mode.go index 38f5040..5537efb 100644 --- a/repository/god_mode.go +++ b/repository/god_mode.go @@ -36,7 +36,6 @@ func (gm _GMRepository) DeleteMeterBinding(ctx context.Context, tx pgx.Tx, pid s DeleteQuerySql, DeleteQueryArgs, _ := DeleteQuery.ToSQL() - _, err := tx.Exec(ctx, DeleteQuerySql, DeleteQueryArgs...) if err != nil { gm.log.Error("数据库在删除tenement_meter表数据中出错", zap.Error(err)) @@ -46,7 +45,7 @@ func (gm _GMRepository) DeleteMeterBinding(ctx context.Context, tx pgx.Tx, pid s return nil } -func (gm _GMRepository) DeleteTenements(ctx context.Context, tx pgx.Tx, pid string, tenements []string) error { +func (gm _GMRepository) DeleteTenements(ctx context.Context, tx pgx.Tx, pid string, tenements ...[]string) error { DeleteTenements := gm.ds. From("tenement"). Where(goqu.I("park_id").Eq(pid)). @@ -63,7 +62,330 @@ func (gm _GMRepository) DeleteTenements(ctx context.Context, tx pgx.Tx, pid stri _, err := tx.Exec(ctx, DeleteTenementsSql, DeleteTenementsArgs...) if err != nil { tx.Rollback(ctx) - gm.log.Error("删除商户信息出错",zap.Error(err)) + gm.log.Error("删除商户信息出错", zap.Error(err)) + return err + } + return nil +} + +func (gm _GMRepository) DeleteInvoices(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error { + if len(val) > 0 { + updateQuery, updateQueryArgs, _ := gm.ds. + Update(goqu.T("report_tenement")). + Set(goqu.Record{"invoice": nil}). + Where(goqu.I("invoice").In(val)). + Where( + goqu.I("report_id"). + Eq( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)), + ), + ).ToSQL() + _, err := tx.Exec(ctx, updateQuery, updateQueryArgs...) + if err != nil { + tx.Rollback(ctx) + gm.log.Error("更新发票记录出错", zap.Error(err)) + return err + } + } else { + updateQuery, updateQueryArgs, _ := gm.ds. + Update(goqu.T("report_tenement")). + Set(goqu.Record{"invoice": nil}). + Where( + goqu.I("report_id"). + Eq(gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)), + )).ToSQL() + _, err := tx.Exec(ctx, updateQuery, updateQueryArgs...) + if err != nil { + tx.Rollback(ctx) + gm.log.Error("更新发票记录出错", zap.Error(err)) + return err + } + } + + deleteQuery := gm.ds. + From(goqu.T("invoices")). + Where(goqu.I("park_id").Eq(parks)). + Delete() + if len(val) > 0 { + deleteQuery.Where(goqu.I("invoice_code").In(val)) + } + deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL() + + _, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...) + if err != nil { + tx.Rollback(ctx) + gm.log.Error("删除指定园区发票记录出错", zap.Error(err)) + return err + } + return nil + +} + +func (gm _GMRepository) DeleteMeterPookings(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error { + deleteQuery := gm.ds. + Delete(goqu.T("meter_relations")). + Where(goqu.I("park_id").Eq(parks)) + + if len(val) > 0 { + deleteQuery = deleteQuery. + Where( + goqu.I("master_meter_id").In(val), + goqu.Or(goqu.I("slave_meter_id").In(val)), + ) + } + deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL() + _, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...) + if err != nil { + tx.Rollback(ctx) + gm.log.Error("删除指定园区中的表计分摊关系失败", zap.Error(err)) + return err + } + return nil +} + +func (gm _GMRepository) DeleteMeters(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error { + deleteQuery := gm.ds. + Delete(goqu.T("meter_04kv")). + Where(goqu.I("park_id").Eq(parks)) + + if len(val) > 0 { + deleteQuery = deleteQuery.Where(goqu.I("code").In(val)) + } + + deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL() + + _, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...) + if err != nil { + tx.Rollback(ctx) + gm.log.Error("删除指定园区的符合条件的标记出错", zap.Error(err)) + return err + } + return nil +} + +func (gm _GMRepository) DeleteReports(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error { + var err error + + if len(val) > 0 { + deleteReportTenementQuerySql, deleteReportTenementQueryArgs, _ := gm.ds. + Delete(goqu.T("report_tenement")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("id").In(val)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportTenementQuerySql, deleteReportTenementQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs, _ := gm.ds. + Delete(goqu.T("report_pooled_consumption")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("id").In(val)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs, _ := gm.ds. + Delete(goqu.T("report_public_consumption")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("id").In(val)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs, _ := gm.ds. + Delete(goqu.T("report_summary")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("id").In(val)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportTaskQuerySql, deleteReportTaskQueryArgs, _ := gm.ds. + Delete(goqu.T("report_task")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("id").In(val)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportTaskQuerySql, deleteReportTaskQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportQuerySql, deleteReportQueryArgs, _ := gm.ds. + Delete(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("id").In(val)).ToSQL() + _, err = tx.Exec(ctx, deleteReportQuerySql, deleteReportQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + } else { + deleteReportTenementQuerySql, deleteReportTenementQueryArgs, _ := gm.ds. + Delete(goqu.T("report_tenement")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportTenementQuerySql, deleteReportTenementQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs, _ := gm.ds. + Delete(goqu.T("report_pooled_consumption")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportPooledConsumptionQuerySql, deleteReportPooledConsumptionQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs, _ := gm.ds. + Delete(goqu.T("report_public_consumption")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportPublicConsumptionQuerySql, deleteReportPublicConsumptionQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs, _ := gm.ds. + Delete(goqu.T("report_summary")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportSummaryQuerySql, deleteReportSummaryQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportTaskQuerySql, deleteReportTaskQueryArgs, _ := gm.ds. + Delete(goqu.T("report_task")). + Where(goqu.I("report_id").In( + gm.ds. + From(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)), + )).ToSQL() + _, err = tx.Exec(ctx, deleteReportTaskQuerySql, deleteReportTaskQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + + deleteReportQuerySql, deleteReportQueryArgs, _ := gm.ds. + Delete(goqu.T("report")). + Where(goqu.I("park_id").Eq(parks)).ToSQL() + _, err = tx.Exec(ctx, deleteReportQuerySql, deleteReportQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + } + + return nil +} + +func (gm _GMRepository) DeleteBuildings(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error { + if len(val) > 0 { + updateBulidingSql, updateBlidingArgs, _ := gm.ds. + Update(goqu.T("tenement")). + Set(goqu.Record{"building": nil}). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("building").In( + gm.ds. + From(goqu.I("park_building")). + Where(goqu.I("park_id").Eq(parks)). + Where(goqu.I("id").In(val)). + Select(goqu.I("id")), + )).ToSQL() + _, err := tx.Exec(ctx, updateBulidingSql, updateBlidingArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + } else { + updateBulidingSql, updateBlidingArgs, _ := gm.ds. + Update(goqu.T("tenement")). + Set(goqu.Record{"building": nil}). + Where(goqu.I("park_id").Eq(parks)).ToSQL() + _, err := tx.Exec(ctx, updateBulidingSql, updateBlidingArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + } + + deleteQuery := gm.ds. + Delete(goqu.I("park_building")). + Where(goqu.I("park_id").Eq(parks)) + + if len(val) > 0 { + deleteQuery = deleteQuery. + Where(goqu.I("id").In(val)) + } + + deleteQuerySql, deleteQueryArgs, _ := deleteQuery.ToSQL() + _, err := tx.Exec(ctx, deleteQuerySql, deleteQueryArgs...) + if err != nil { + tx.Rollback(ctx) + return err + } + return nil +} + +func (gm _GMRepository) DeleteParks(ctx context.Context, tx pgx.Tx, park []string) error { + deleteParksSql, deleteParksArgs, _ := gm.ds. + Delete(goqu.T("park")). + Where(goqu.I("id").In(park)).ToSQL() + + _, err := tx.Exec(ctx, deleteParksSql, deleteParksArgs...) + if err != nil { + tx.Rollback(ctx) return err } return nil diff --git a/service/god_mode.go b/service/god_mode.go index 462cf9c..7623465 100644 --- a/service/god_mode.go +++ b/service/god_mode.go @@ -44,3 +44,44 @@ func (gm _GMService) DeleteTenements(pid string, tenements []string) error { tx.Commit(ctx) return nil } + +func (gm _GMService) DeleteParks(parks []string) error { + + var err error + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + gm.l.Error("未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + for _, pid := range parks { + //删除invoices + err = repository.GMRepository.DeleteInvoices(ctx, tx, pid) + //删除meter_binding + err = repository.GMRepository.DeleteMeterBinding(ctx, tx, pid, []string{}) + //删除meter_pookings + err = repository.GMRepository.DeleteMeterPookings(ctx, tx, pid) + //删除tenements + err = repository.GMRepository.DeleteTenements(ctx, tx, pid, []string{}) + //删除meters + err = repository.GMRepository.DeleteMeters(ctx, tx, pid) + //删除reports + err = repository.GMRepository.DeleteReports(ctx, tx, pid) + //删除buildings + err = repository.GMRepository.DeleteBuildings(ctx, tx, pid) + if err != nil { + gm.l.Error("删除关联表出错。", zap.Error(err)) + break + return err + } + } + err = repository.GMRepository.DeleteParks(ctx, tx, parks) + if err != nil { + gm.l.Error("指定园区删除失败。", zap.Error(err)) + return err + } + tx.Commit(ctx) + return nil +} From 18d48c7fea0a85f0207155b053c6fceba97bc5a7 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 28 Jul 2023 16:46:52 +0800 Subject: [PATCH 129/141] =?UTF-8?q?[=E5=A4=A9=E7=A5=9E=E6=A8=A1=E5=BC=8F]?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=AC=A6=E5=90=88=E6=9D=A1=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 19 +++++++++++++++++-- service/god_mode.go | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/controller/god_mode.go b/controller/god_mode.go index b401838..26d5906 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -16,6 +16,7 @@ var GmLog = logger.Named("Handler", "GM") func InitializeGmController(router *fiber.App) { router.Delete("/gm/tenement", security.SingularityAuthorize, DeleteTenement) router.Delete("/gm/park", security.SingularityAuthorize, DeletePark) + router.Delete("/gm/report", security.SingularityAuthorize, DeleteReports) } //用于将参数转化为切片 @@ -55,8 +56,22 @@ func DeletePark(c *fiber.Ctx) error { err := service.GMService.DeleteParks(parks) if err != nil { - GmLog.Error("[天神模式]删除指定园区失败",zap.Error(err)) - return result.Error(500,err.Error()) + GmLog.Error("[天神模式]删除指定园区失败", zap.Error(err)) + return result.Error(500, err.Error()) } return result.Success("指定园区已经删除。") } + +func DeleteReports(c *fiber.Ctx) error { + pid := c.Query("park") + reports := getQueryValues(c, "reports") + result := response.NewResult(c) + GmLog.Info("[天神模式]删除符合条件的报表。", zap.Strings("reports", reports)) + + err := service.GMService.DeleteReports(pid, reports) + if err != nil { + GmLog.Error("[天神模式]删除指定园区中的报表失败。", zap.Error(err)) + return result.Error(500, err.Error()) + } + return result.Success("指定报表已经删除。") +} diff --git a/service/god_mode.go b/service/god_mode.go index 7623465..a5cd395 100644 --- a/service/god_mode.go +++ b/service/god_mode.go @@ -85,3 +85,22 @@ func (gm _GMService) DeleteParks(parks []string) error { tx.Commit(ctx) return nil } + +func (gm _GMService) DeleteReports(pid string, reports []string) error { + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + gm.l.Error("未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + err = repository.GMRepository.DeleteReports(ctx, tx, pid, reports) + if err != nil { + tx.Rollback(ctx) + return err + } + tx.Commit(ctx) + return nil +} From 1dd5f1049d438d55b3efcbe461beed3f636d721e Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 28 Jul 2023 17:29:41 +0800 Subject: [PATCH 130/141] =?UTF-8?q?[=E5=A4=A9=E7=A5=9E=E6=A8=A1=E5=BC=8F]?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=AC=A6=E5=90=88=E6=9D=A1=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E5=95=86=E6=88=B7=E7=BB=91=E5=AE=9A=E7=9A=84=E8=A1=A8=E8=AE=A1?= =?UTF-8?q?=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 32 ++++++++++++++++++++++++++------ service/god_mode.go | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/controller/god_mode.go b/controller/god_mode.go index 26d5906..9088b23 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -14,9 +14,10 @@ import ( var GmLog = logger.Named("Handler", "GM") func InitializeGmController(router *fiber.App) { - router.Delete("/gm/tenement", security.SingularityAuthorize, DeleteTenement) - router.Delete("/gm/park", security.SingularityAuthorize, DeletePark) - router.Delete("/gm/report", security.SingularityAuthorize, DeleteReports) + router.Delete("/gm/tenement", security.SingularityAuthorize, deleteTenement) + router.Delete("/gm/park", security.SingularityAuthorize, deletePark) + router.Delete("/gm/report", security.SingularityAuthorize, DeleteEnterprise) + router.Delete("/gm/tenement/meter",security.SingularityAuthorize, deleteTenementMeterRelations) } //用于将参数转化为切片 @@ -29,7 +30,7 @@ func getQueryValues(c *fiber.Ctx, paramName string) []string { return result } -func DeleteTenement(c *fiber.Ctx) error { +func deleteTenement(c *fiber.Ctx) error { park := c.Query("park", "") tenements := getQueryValues(c, "tenements") result := response.NewResult(c) @@ -44,7 +45,7 @@ func DeleteTenement(c *fiber.Ctx) error { return result.Success("指定商户已经删除。") } -func DeletePark(c *fiber.Ctx) error { +func deletePark(c *fiber.Ctx) error { parks := getQueryValues(c, "parks") result := response.NewResult(c) GmLog.Info("[天神模式]删除指定园区", zap.Strings("parks", parks)) @@ -62,7 +63,7 @@ func DeletePark(c *fiber.Ctx) error { return result.Success("指定园区已经删除。") } -func DeleteReports(c *fiber.Ctx) error { +func deleteReports(c *fiber.Ctx) error { pid := c.Query("park") reports := getQueryValues(c, "reports") result := response.NewResult(c) @@ -75,3 +76,22 @@ func DeleteReports(c *fiber.Ctx) error { } return result.Success("指定报表已经删除。") } + +func DeleteEnterprise(c *fiber.Ctx) error { + + return nil +} + + +func deleteTenementMeterRelations(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Query("park") + tId := getQueryValues(c,"tenements") + metersId := getQueryValues(c, "meters") + GmLog.Info("删除指定园区中的商户与表计的关联关系", zap.String("park id", parkId)) + if err := service.GMService.DeleteTenementMeterRelations(parkId, tId, metersId); err != nil { + meterLog.Error("无法删除指定园区中的商户与表计的关联关系", zap.Error(err)) + return result.NotAccept(err.Error()) + } + return result.Success("删除成功") +} \ No newline at end of file diff --git a/service/god_mode.go b/service/god_mode.go index a5cd395..7025c06 100644 --- a/service/god_mode.go +++ b/service/god_mode.go @@ -104,3 +104,27 @@ func (gm _GMService) DeleteReports(pid string, reports []string) error { tx.Commit(ctx) return nil } + +func (gm _GMService) DeleteTenementMeterRelations(pId string, tId []string, mId []string) error { + gm.l.Info("删除商户表记关系", zap.String("tenement", pId)) + ctx, cancel := global.TimeoutContext(10) + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + gm.l.Error("开启数据库事务失败。", zap.Error(err)) + return err + } + if err := repository.GMRepository.DeleteMeterBinding(ctx, tx, pId, tId, mId); err != nil { + gm.l.Error("无法删除商户与表记关系。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + err = tx.Commit(ctx) + if err != nil { + gm.l.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + return nil +} From b84c51b18efdcea1e380011cd3c563603de28561 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 28 Jul 2023 17:33:36 +0800 Subject: [PATCH 131/141] =?UTF-8?q?=E4=BF=AE=E6=AD=A3router=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/god_mode.go b/controller/god_mode.go index 9088b23..1a3537d 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -16,7 +16,7 @@ var GmLog = logger.Named("Handler", "GM") func InitializeGmController(router *fiber.App) { router.Delete("/gm/tenement", security.SingularityAuthorize, deleteTenement) router.Delete("/gm/park", security.SingularityAuthorize, deletePark) - router.Delete("/gm/report", security.SingularityAuthorize, DeleteEnterprise) + router.Delete("/gm/report", security.SingularityAuthorize, deleteReports) router.Delete("/gm/tenement/meter",security.SingularityAuthorize, deleteTenementMeterRelations) } @@ -77,7 +77,7 @@ func deleteReports(c *fiber.Ctx) error { return result.Success("指定报表已经删除。") } -func DeleteEnterprise(c *fiber.Ctx) error { +func deleteEnterprise(c *fiber.Ctx) error { return nil } From c36bfff05a2553a203c66ec553e2f526abc0c72b Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Mon, 31 Jul 2023 09:41:45 +0800 Subject: [PATCH 132/141] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E7=9A=84=E4=BC=81=E4=B8=9A=E7=94=A8=E6=88=B7=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 18 +++++++++---- repository/god_mode.go | 59 +++++++++++++++++++++++++++++++++++++++++- service/god_mode.go | 51 ++++++++++++++++++++++++++++++++++-- 3 files changed, 120 insertions(+), 8 deletions(-) diff --git a/controller/god_mode.go b/controller/god_mode.go index 1a3537d..b7b4242 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -17,7 +17,8 @@ func InitializeGmController(router *fiber.App) { router.Delete("/gm/tenement", security.SingularityAuthorize, deleteTenement) router.Delete("/gm/park", security.SingularityAuthorize, deletePark) router.Delete("/gm/report", security.SingularityAuthorize, deleteReports) - router.Delete("/gm/tenement/meter",security.SingularityAuthorize, deleteTenementMeterRelations) + router.Delete("/gm/tenement/meter", security.SingularityAuthorize, deleteTenementMeterRelations) + router.Delete("/gm/enterprise", security.SingularityAuthorize, deleteEnterprise) } //用于将参数转化为切片 @@ -78,15 +79,22 @@ func deleteReports(c *fiber.Ctx) error { } func deleteEnterprise(c *fiber.Ctx) error { + uid := c.Query("uid") + result := response.NewResult(c) + GmLog.Info("[天神模式]删除指定企业用户", zap.String("uid", uid)) - return nil + err := service.GMService.DeleteEnterprises(uid) + if err != nil { + GmLog.Error("[天神模式]删除指定企业用户失败", zap.Error(err)) + return result.Error(500, "删除指定企业用户失败。") + } + return result.Success("指定企业用户已经删除。") } - func deleteTenementMeterRelations(c *fiber.Ctx) error { result := response.NewResult(c) parkId := c.Query("park") - tId := getQueryValues(c,"tenements") + tId := getQueryValues(c, "tenements") metersId := getQueryValues(c, "meters") GmLog.Info("删除指定园区中的商户与表计的关联关系", zap.String("park id", parkId)) if err := service.GMService.DeleteTenementMeterRelations(parkId, tId, metersId); err != nil { @@ -94,4 +102,4 @@ func deleteTenementMeterRelations(c *fiber.Ctx) error { return result.NotAccept(err.Error()) } return result.Success("删除成功") -} \ No newline at end of file +} diff --git a/repository/god_mode.go b/repository/god_mode.go index 5537efb..fed5fc8 100644 --- a/repository/god_mode.go +++ b/repository/god_mode.go @@ -2,9 +2,11 @@ package repository import ( "context" + "electricity_bill_calc/global" "electricity_bill_calc/logger" "fmt" "github.com/doug-martin/goqu/v9" + "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "go.uber.org/zap" ) @@ -125,7 +127,7 @@ func (gm _GMRepository) DeleteInvoices(ctx context.Context, tx pgx.Tx, parks str } -func (gm _GMRepository) DeleteMeterPookings(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error { +func (gm _GMRepository) DeleteMeterPoolings(ctx context.Context, tx pgx.Tx, parks string, val ...[]string) error { deleteQuery := gm.ds. Delete(goqu.T("meter_relations")). Where(goqu.I("park_id").Eq(parks)) @@ -390,3 +392,58 @@ func (gm _GMRepository) DeleteParks(ctx context.Context, tx pgx.Tx, park []strin } return nil } + +func (gm _GMRepository) ListAllParkIdsInUser(ctx context.Context, tx pgx.Tx, uid string) ([]string, error) { + SearchParkIdsSql, SearchParkIdsArgs, _ := gm.ds. + From(goqu.T("park")). + Where(goqu.I("user_id").Eq(uid)). + Select(goqu.I("id")).ToSQL() + var pids []string + err := pgxscan.Select(ctx, global.DB, &pids, SearchParkIdsSql, SearchParkIdsArgs...) + if err != nil { + gm.log.Error("查询["+uid+"]用户下的所有园区失败", zap.Error(err)) + tx.Rollback(ctx) + return nil, err + } + + return pids, nil +} + +func (gm _GMRepository) DeleteUsers(ctx context.Context, tx pgx.Tx, uid string) error { + var err error + //删除用户关联 + DeleteUserChargeSql, DeleteUserChargeArgs, _ := gm.ds. + Delete(goqu.T("user_charge")). + Where(goqu.I("id").Eq(uid)).ToSQL() + + _, err = tx.Exec(ctx,DeleteUserChargeSql,DeleteUserChargeArgs...) + if err != nil { + gm.log.Error("user_charge表关联出错",zap.Error(err)) + tx.Rollback(ctx) + return err + } + + //删除用户详细信息 + DeleteUserDetailSql, DeleteUserDetailArgs,_ := gm.ds. + Delete(goqu.T("user_detail")). + Where(goqu.I("id").Eq(uid)).ToSQL() + _, err = tx.Exec(ctx,DeleteUserDetailSql,DeleteUserDetailArgs...) + if err != nil { + gm.log.Error("user_detail表详细信息出错",zap.Error(err)) + tx.Rollback(ctx) + return err + } + + //删除用户基础信息 + DeleteUserSql, DeleteUserArgs,_ := gm.ds. + Delete(goqu.T("users")). + Where(goqu.I("id").Eq(uid)).ToSQL() + _, err = tx.Exec(ctx,DeleteUserSql,DeleteUserArgs...) + if err != nil { + gm.log.Error("user表基础信息出错",zap.Error(err)) + tx.Rollback(ctx) + return err + } + + return nil +} diff --git a/service/god_mode.go b/service/god_mode.go index 7025c06..6c66439 100644 --- a/service/god_mode.go +++ b/service/god_mode.go @@ -61,8 +61,8 @@ func (gm _GMService) DeleteParks(parks []string) error { err = repository.GMRepository.DeleteInvoices(ctx, tx, pid) //删除meter_binding err = repository.GMRepository.DeleteMeterBinding(ctx, tx, pid, []string{}) - //删除meter_pookings - err = repository.GMRepository.DeleteMeterPookings(ctx, tx, pid) + //删除meter_poolings + err = repository.GMRepository.DeleteMeterPoolings(ctx, tx, pid) //删除tenements err = repository.GMRepository.DeleteTenements(ctx, tx, pid, []string{}) //删除meters @@ -128,3 +128,50 @@ func (gm _GMService) DeleteTenementMeterRelations(pId string, tId []string, mId } return nil } + +func (gm _GMService) DeleteEnterprises(uid string) error { + var err error + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + gm.l.Error("未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + parks, err := repository.GMRepository.ListAllParkIdsInUser(ctx, tx, uid) + if err != nil { + gm.l.Error("查询园区错误", zap.Error(err)) + tx.Rollback(ctx) + return err + } + + for _, pid := range parks { + err = repository.GMRepository.DeleteInvoices(ctx, tx, pid) + err = repository.GMRepository.DeleteMeterBinding(ctx, tx, pid, []string{}) + err = repository.GMRepository.DeleteMeterPookings(ctx, tx, pid) + err = repository.GMRepository.DeleteTenements(ctx, tx, pid) + err = repository.GMRepository.DeleteMeters(ctx, tx, pid) + err = repository.GMRepository.DeleteReports(ctx, tx, pid) + err = repository.GMRepository.DeleteBuildings(ctx, tx, pid) + + if err != nil { + gm.l.Error("删除用户下关联出错", zap.Error(err)) + return err + } + } + + err = repository.GMRepository.DeleteParks(ctx, tx, parks) + if err != nil { + gm.l.Error("删除用户关联园区错误", zap.Error(err)) + tx.Rollback(ctx) + return err + } + err = repository.GMRepository.DeleteUsers(ctx, tx, uid) + if err != nil { + gm.l.Error("删除用户信息出错", zap.Error(err)) + tx.Rollback(ctx) + return err + } + return nil +} From 9b899be33d199865c60c6864730435cd2ab7ed68 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Mon, 31 Jul 2023 09:50:41 +0800 Subject: [PATCH 133/141] =?UTF-8?q?[=E5=A4=A9=E7=A5=9E=E6=A8=A1=E5=BC=8F]?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=AC=A6=E5=90=88=E6=9D=A1=E4=BB=B6=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E5=85=AC=E6=91=8A=E5=85=B3=E7=B3=BB=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 14 ++++++++++++++ service/god_mode.go | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/controller/god_mode.go b/controller/god_mode.go index b7b4242..40099cf 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -19,6 +19,7 @@ func InitializeGmController(router *fiber.App) { router.Delete("/gm/report", security.SingularityAuthorize, deleteReports) router.Delete("/gm/tenement/meter", security.SingularityAuthorize, deleteTenementMeterRelations) router.Delete("/gm/enterprise", security.SingularityAuthorize, deleteEnterprise) + router.Delete("/gm/meter/pooling", security.SingularityAuthorize, deleteMeterPoolingRelations) } //用于将参数转化为切片 @@ -103,3 +104,16 @@ func deleteTenementMeterRelations(c *fiber.Ctx) error { } return result.Success("删除成功") } + +func deleteMeterPoolingRelations(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Query("park") + mId := getQueryValues(c, "meters") + GmLog.Info("[天神模式]删除指定园区中的表计公摊关系", zap.String("park id", parkId)) + if err := service.GMService.DeleteMeterPooling(parkId, mId); err != nil { + meterLog.Error("[天神模式]删除指定园区中的表计公摊关系失败", zap.Error(err)) + return result.Error(500, "删除指定园区中的表计公摊关系失败。") + } + return result.Success("指定表计公摊关系已经删除。") + +} diff --git a/service/god_mode.go b/service/god_mode.go index 6c66439..8001b08 100644 --- a/service/god_mode.go +++ b/service/god_mode.go @@ -175,3 +175,25 @@ func (gm _GMService) DeleteEnterprises(uid string) error { } return nil } + +func (gm _GMService) DeleteMeterPooling(pId string, mId []string) error { + ctx, cancel := global.TimeoutContext() + defer cancel() + tx, err := global.DB.Begin(ctx) + if err != nil { + gm.l.Error("开启数据库事务失败。", zap.Error(err)) + return err + } + if err := repository.GMRepository.DeleteMeterPoolings(ctx, tx, pId, mId); err != nil { + gm.l.Error("无法删除指定表记公摊关系。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + err = tx.Commit(ctx) + if err != nil { + gm.l.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + return nil +} \ No newline at end of file From f254ec1f3a703523e0f52257993c1e09e3425f63 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Mon, 31 Jul 2023 10:15:14 +0800 Subject: [PATCH 134/141] =?UTF-8?q?[=E5=A4=A9=E7=A5=9E=E6=A8=A1=E5=BC=8F]?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=AC=A6=E5=90=88=E6=9D=A1=E4=BB=B6=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/god_mode.go | 13 +++++++++++++ service/god_mode.go | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/controller/god_mode.go b/controller/god_mode.go index 40099cf..db1a426 100644 --- a/controller/god_mode.go +++ b/controller/god_mode.go @@ -20,6 +20,7 @@ func InitializeGmController(router *fiber.App) { router.Delete("/gm/tenement/meter", security.SingularityAuthorize, deleteTenementMeterRelations) router.Delete("/gm/enterprise", security.SingularityAuthorize, deleteEnterprise) router.Delete("/gm/meter/pooling", security.SingularityAuthorize, deleteMeterPoolingRelations) + router.Delete("gm/meter", security.SingularityAuthorize, deleteMeters) } //用于将参数转化为切片 @@ -117,3 +118,15 @@ func deleteMeterPoolingRelations(c *fiber.Ctx) error { return result.Success("指定表计公摊关系已经删除。") } + +func deleteMeters(c *fiber.Ctx) error { + result := response.NewResult(c) + parkId := c.Query("park") + mId := getQueryValues(c, "meters") + GmLog.Info("[天神模式]删除指定园区中的表计", zap.String("park id", parkId)) + if err := service.GMService.DeleteMeters(parkId, mId); err != nil { + meterLog.Error("[天神模式]删除指定园区中的表计失败", zap.Error(err)) + return result.Error(500, "删除指定园区中的表计失败。") + } + return result.Success("指定表计已经删除。") +} diff --git a/service/god_mode.go b/service/god_mode.go index 8001b08..a493def 100644 --- a/service/god_mode.go +++ b/service/god_mode.go @@ -149,7 +149,7 @@ func (gm _GMService) DeleteEnterprises(uid string) error { for _, pid := range parks { err = repository.GMRepository.DeleteInvoices(ctx, tx, pid) err = repository.GMRepository.DeleteMeterBinding(ctx, tx, pid, []string{}) - err = repository.GMRepository.DeleteMeterPookings(ctx, tx, pid) + err = repository.GMRepository.DeleteMeterPoolings(ctx, tx, pid) err = repository.GMRepository.DeleteTenements(ctx, tx, pid) err = repository.GMRepository.DeleteMeters(ctx, tx, pid) err = repository.GMRepository.DeleteReports(ctx, tx, pid) @@ -189,6 +189,39 @@ func (gm _GMService) DeleteMeterPooling(pId string, mId []string) error { tx.Rollback(ctx) return err } + err = tx.Commit(ctx) + if err != nil { + gm.l.Error("未能成功提交数据库事务。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + return nil +} + +func (gm _GMService) DeleteMeters(pId string, mId []string) error { + ctx, cancel := global.TimeoutContext() + defer cancel() + tx, err := global.DB.Begin(ctx) + if err != nil { + gm.l.Error("开启数据库事务失败。", zap.Error(err)) + return err + } + if err := repository.GMRepository.DeleteMeterBinding(ctx, tx, pId, []string{}, mId); err != nil { + gm.l.Error("删除指定园区中的表计和商户的绑定关系", zap.Error(err)) + tx.Rollback(ctx) + return err + } + if err := repository.GMRepository.DeleteMeterPoolings(ctx, tx, pId, mId); err != nil { + gm.l.Error("无法删除指定表记公摊关系。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + if err := repository.GMRepository.DeleteMeters(ctx, tx, pId, mId); err != nil { + gm.l.Error("删除指定园区中符合条件的表计。", zap.Error(err)) + tx.Rollback(ctx) + return err + } + err = tx.Commit(ctx) if err != nil { gm.l.Error("未能成功提交数据库事务。", zap.Error(err)) From f688f50ecbd7fb058e92bea2da997ebbf6e8a605 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Thu, 3 Aug 2023 16:59:58 +0800 Subject: [PATCH 135/141] =?UTF-8?q?[=E8=AE=A1=E7=AE=97=E7=9B=B8=E5=85=B3]?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=89=80=E6=9C=89=E7=9A=84=E7=89=A9=E4=B8=9A?= =?UTF-8?q?=E8=A1=A8=E8=AE=A1=EF=BC=8C=E7=84=B6=E5=90=8E=E5=AF=B9=E6=89=80?= =?UTF-8?q?=E6=9C=89=E7=9A=84=E7=89=A9=E4=B8=9A=E8=A1=A8=E8=AE=A1=E7=94=B5?= =?UTF-8?q?=E9=87=8F=E8=BF=9B=E8=A1=8C=E8=AE=A1=E7=AE=97=E3=80=82(?= =?UTF-8?q?=E5=AE=8C=E6=88=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/routerSetting.md | 13 -- model/calculate/calculate.go | 121 ++++++++++++++ model/tenement.go | 10 ++ repository/calculate.go | 171 ++++++++++++++++++++ repository/withdraw.go | 2 +- service/calculate/checking.go | 32 ++++ service/calculate/park.go | 37 +++++ service/calculate/pooled.go | 111 +++++++++++++ service/calculate/shared.go | 105 +++++++++++++ service/calculate/summary.go | 1 + service/calculate/tenement.go | 288 ++++++++++++++++++++++++++++++++++ service/calculate/utils.go | 141 +++++++++++++++++ service/calculate/wattCost.go | 66 ++++++++ tools/utils.go | 1 - 14 files changed, 1084 insertions(+), 15 deletions(-) delete mode 100644 doc/routerSetting.md create mode 100644 service/calculate/checking.go create mode 100644 service/calculate/park.go create mode 100644 service/calculate/pooled.go create mode 100644 service/calculate/shared.go create mode 100644 service/calculate/summary.go create mode 100644 service/calculate/tenement.go create mode 100644 service/calculate/utils.go create mode 100644 service/calculate/wattCost.go diff --git a/doc/routerSetting.md b/doc/routerSetting.md deleted file mode 100644 index 6e9c627..0000000 --- a/doc/routerSetting.md +++ /dev/null @@ -1,13 +0,0 @@ -## fiber -#### fiber实例 -- app(是fiber创建的实例通常用app表示,其中有可选配置选项) - - BodyLimit 设置请求正文允许的最大大小(默认为4 * 1024 * 1024) - - EnablePrintRoutes 不打印框架自带日志(默认false) - - EnableTrustedProxyCheck 禁用受信代理(默认false) - - Prefork 预处理配置(默认false) - - ErrorHandler 全局错误处理 (默认false) - - JSONEncoder json编码 (默认json.Marshal) - - JSONDecoder json解码 (默认json.Unmarshal) - - 。。。。。。。。(还有很多配置) -- Use(中间件设置,一个或者多个) -- Group(类似于gin框架中的路由分组) \ No newline at end of file diff --git a/model/calculate/calculate.go b/model/calculate/calculate.go index da5177c..ff5ebb7 100644 --- a/model/calculate/calculate.go +++ b/model/calculate/calculate.go @@ -3,6 +3,7 @@ package calculate import ( "electricity_bill_calc/model" "electricity_bill_calc/types" + "fmt" "github.com/shopspring/decimal" ) @@ -42,6 +43,11 @@ type Meter struct { Poolings []*Pooling } +type PrimaryTenementStatistics struct { + Tenement model.Tenement + Meters []Meter +} + type TenementCharge struct { Tenement string Overall model.ConsumptionUnit @@ -90,3 +96,118 @@ type PoolingSummary struct { OverallAmount decimal.Decimal PoolingProportion decimal.Decimal } + +func FromReportSummary(summary *model.ReportSummary, pricingMode *model.ReportIndex) Summary { + var parkPrice float64 + switch pricingMode.PricePolicy { + case model.PRICING_POLICY_CONSUMPTION: + parkPrice = summary.ConsumptionFee.Decimal.InexactFloat64() / summary.Overall.Amount.InexactFloat64() + case model.PRICING_POLICY_ALL: + parkPrice = summary.Overall.Fee.InexactFloat64() / summary.Overall.Amount.InexactFloat64() + default: + fmt.Println("无法识别类型") + } + + flatAmount := summary.Overall.Amount.InexactFloat64() - + summary.Critical.Amount.InexactFloat64() - + summary.Peak.Amount.InexactFloat64() - + summary.Valley.Amount.InexactFloat64() + + flatFee := summary.Overall.Amount.InexactFloat64() - + summary.Critical.Fee.InexactFloat64() - + summary.Peak.Fee.InexactFloat64() - + summary.Valley.Fee.InexactFloat64() + + var OverallPrice float64 + if summary.Overall.Amount.GreaterThan(decimal.Zero) { + OverallPrice = parkPrice + } else { + OverallPrice = decimal.Zero.InexactFloat64() + } + + var CriticalPrice float64 + if summary.Critical.Amount.GreaterThan(decimal.Zero) { + CriticalPrice = summary.Critical.Fee.InexactFloat64() / summary.Critical.Amount.InexactFloat64() + } else { + CriticalPrice = decimal.Zero.InexactFloat64() + } + + var PeakPrice float64 + if summary.Peak.Amount.GreaterThan(decimal.Zero) { + PeakPrice = summary.Peak.Fee.InexactFloat64() / summary.Peak.Amount.InexactFloat64() + } else { + PeakPrice = decimal.Zero.InexactFloat64() + } + + var FlatPrice float64 + if decimal.NewFromFloat(flatAmount).GreaterThan(decimal.Zero) { + FlatPrice = flatFee / flatAmount + } else { + FlatPrice = decimal.Zero.InexactFloat64() + } + + var ValleyPrice float64 + if summary.Valley.Amount.GreaterThan(decimal.Zero) { + ValleyPrice = summary.Valley.Fee.InexactFloat64() / summary.Valley.Amount.InexactFloat64() + } else { + ValleyPrice = decimal.Zero.InexactFloat64() + } + + var LossDilutedPrice float64 + if summary.Overall.Amount.GreaterThan(decimal.Zero) { + LossDilutedPrice = parkPrice + } else { + LossDilutedPrice = decimal.Zero.InexactFloat64() + } + + _ = parkPrice + + return Summary{ + ReportId: summary.ReportId, + OverallArea: decimal.Zero, + Overall: model.ConsumptionUnit{ + Amount: summary.Overall.Amount, + Fee: summary.Overall.Fee, + Price: decimal.NewFromFloat(OverallPrice), + Proportion: decimal.NewFromFloat(1.0), + }, + ConsumptionFee: summary.ConsumptionFee.Decimal, + Critical: model.ConsumptionUnit{ + Amount: summary.Critical.Amount, + Fee: summary.Critical.Fee, + Price: decimal.NewFromFloat(CriticalPrice), + Proportion: decimal.NewFromFloat(summary.Critical.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()), + }, + Peak: model.ConsumptionUnit{ + Amount: summary.Peak.Amount, + Fee: summary.Peak.Fee, + Price: decimal.NewFromFloat(PeakPrice), + Proportion: decimal.NewFromFloat(summary.Peak.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()), + }, + Flat: model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(flatAmount), + Fee: decimal.NewFromFloat(flatFee), + Price: decimal.NewFromFloat(FlatPrice), + Proportion: decimal.NewFromFloat(flatAmount / summary.Overall.Amount.InexactFloat64()), + }, + Valley: model.ConsumptionUnit{ + Amount: summary.Valley.Amount, + Fee: summary.Valley.Fee, + Price: decimal.NewFromFloat(ValleyPrice), + Proportion: decimal.NewFromFloat(summary.Valley.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()), + }, + Loss: decimal.Zero, + LossFee: decimal.Zero, + LossProportion: decimal.Zero, + AuthoizeLoss: model.ConsumptionUnit{}, + BasicFee: summary.BasicFee, + BasicPooledPriceConsumption: decimal.Zero, + BasicPooledPriceArea: decimal.Zero, + AdjustFee: summary.AdjustFee, + AdjustPooledPriceConsumption: decimal.Zero, + AdjustPooledPriceArea: decimal.Zero, + LossDilutedPrice: decimal.NewFromFloat(LossDilutedPrice), + TotalConsumption: decimal.Zero, + FinalDilutedOverall: decimal.Zero, + } +} diff --git a/model/tenement.go b/model/tenement.go index eda08f0..c5c0282 100644 --- a/model/tenement.go +++ b/model/tenement.go @@ -21,3 +21,13 @@ type Tenement struct { LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` DeletedAt *types.DateTime `json:"deletedAt" db:"deleted_at"` } + +type TenementMeter struct { + ParkId string `db:"park_id"` + TenementId string `db:"tenement_id"` + MeterId string `db:"meter_id"` + ForeignRelation bool `db:"foreign_relation"` + AssociatedAt types.DateTime `db:"associated_at"` + DisassociatedAt types.DateTime `db:"disassociated_at"` + SynchronizedAt types.DateTime `db:"synchronized_at"` +} diff --git a/repository/calculate.go b/repository/calculate.go index 440d9ef..5f04d4c 100644 --- a/repository/calculate.go +++ b/repository/calculate.go @@ -5,6 +5,7 @@ import ( "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/types" + "time" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" @@ -68,3 +69,173 @@ func (cr _CalculateRepository) UpdateReportTaskStatus(rid string, status int16, } return res.RowsAffected() > 0, nil } + +//获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的 +func (cr _CalculateRepository) GetAllPoolingMeterRelations(pid string, revokedAfter time.Time) ([]model.MeterRelation, error) { + cr.log.Info("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("revokedAfter", revokedAfter)) + + ctx, cancel := global.TimeoutContext() + defer cancel() + relationsSql, relationsArgs, _ := cr.ds. + From(goqu.T("meter_relations")). + Where(goqu.I("park_id").Eq(pid)). + Where(goqu.Or( + goqu.I("revoked_at").IsNull(), + goqu.I("revoked_at").Gte(revokedAfter), + )).ToSQL() + + var meterRelation []model.MeterRelation + + err := pgxscan.Select(ctx, global.DB, meterRelation, relationsSql, relationsArgs...) + if err != nil { + cr.log.Error("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的出错", zap.Error(err)) + return nil, err + } + return meterRelation, nil +} + +//获取当前园区中所有的商户与表计的关联关系,包括已经解除的 +func (cr _CalculateRepository) GetAllTenementMeterRelations(pid string, associatedBefore time.Time, disassociatedAfter time.Time) ([]model.TenementMeter, error) { + cr.log.Info("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("associatedBefore", associatedBefore), zap.Time("disassociatedAfter", disassociatedAfter)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + relationsQuerySql, relationsQueryArgs, _ := cr.ds. + From(goqu.T("tenement_meter")). + Where(goqu.I("park_id").Eq(pid)). + Where(goqu.And( + goqu.I("associated_at").IsNull(), + goqu.I("associated_at").Lte(associatedBefore), + )). + Where(goqu.And( + goqu.I("associated_at").IsNull(), + goqu.I("associated_at").Gte(disassociatedAfter), + )).ToSQL() + + var tenementMeter []model.TenementMeter + + err := pgxscan.Select(ctx, global.DB, tenementMeter, relationsQuerySql, relationsQueryArgs...) + if err != nil { + cr.log.Error("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.Error(err)) + return nil, err + } + return tenementMeter, nil + +} + +//获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据 +func (cr _CalculateRepository) GetMeterReadings(rid string, meterType int16) ([]model.MeterReading, error) { + cr.log.Info("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据", zap.String("rid", rid), zap.Int16("meterType", meterType)) + + ctx, cancel := global.TimeoutContext() + defer cancel() + + readingsQuerySql, readingsQueryArgs, _ := cr.ds. + From(goqu.T("meter_reading").As(goqu.I("mr"))). + Join( + goqu.T("report").As("r"), + goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))), + ). + Where( + goqu.I("r.id").Eq(rid), + goqu.I("mr.meter_type").Eq(meterType), + // TODO:2023.08.02 此方法出错优先查看是否这里出问题 + goqu.I("mr.read_at::date <@ r.period"), + ). + Order(goqu.I("mr.read_at").Asc()).Select(goqu.I("mr.*")).ToSQL() + + var readings []model.MeterReading + + err := pgxscan.Select(ctx, global.DB, readings, readingsQuerySql, readingsQueryArgs...) + if err != nil { + cr.log.Error("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据出错", zap.Error(err)) + return nil, err + } + return readings, nil +} + +// 获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数 +func (cr _CalculateRepository) GetLastPeriodReadings(rid string, meterType int16) ([]model.MeterReading, error) { + cr.log.Info("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数", zap.String("rid", rid), zap.Int16("meterType", meterType)) + + ctx, cancel := global.TimeoutContext() + defer cancel() + + readingsSql, readingsArgs, _ := cr.ds.From(goqu.T("meter_reading").As("mr")). + Select( + goqu.MAX("mr.read_at").As("read_at"), + goqu.I("mr.park_id"), + goqu.I("mr.meter_id"), + goqu.I("mr.meter_type"), + goqu.I("mr.ratio"), + goqu.I("mr.overall"), + goqu.I("mr.critical"), + goqu.I("mr.peak"), + goqu.I("mr.flat"), + goqu.I("mr.valley"), + ). + Join( + goqu.T("report").As("r"), + goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))), + ). + Where( + goqu.I("r.id").Eq(rid), + goqu.I("mr.meter_type").Eq(meterType), + goqu.I(" mr.read_at::date <= lower(r.period)"), + ). + GroupBy( + goqu.I("mr.park_id"), + goqu.I("mr.meter_id"), + goqu.I("mr.meter_type"), + goqu.I("mr.ratio"), + goqu.I("mr.overall"), + goqu.I("mr.critical"), + goqu.I("mr.peak"), + goqu.I("mr.flat"), + goqu.I("mr.valley"), + goqu.I("r.period"), + ).ToSQL() + + var readings []model.MeterReading + err := pgxscan.Select(ctx, global.DB, readings, readingsSql, readingsArgs...) + if err != nil { + cr.log.Error("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数出错", zap.Error(err)) + return nil, err + } + return readings, nil +} + +// 取得指定报表所涉及的所有商户信息 +func (cr _CalculateRepository) GetAllTenements(rid string) ([]model.Tenement, error) { + cr.log.Info("取得指定报表所涉及的所有商户信息", zap.String("rid", rid)) + + ctx, cancel := global.TimeoutContext() + defer cancel() + + tenementQuerySql, tenementQueryArgs, _ := cr.ds. + From(goqu.T("tenement").As("t")). + LeftJoin( + goqu.T("park_building").As("b"), + goqu.On(goqu.I("b.id").Eq(goqu.I("t.building"))), + ). + Join( + goqu.T("report").As("r"), + goqu.On(goqu.I("r.park_id").Eq(goqu.I("t.park_id"))), + ). + Select( + goqu.I("t.*"), + goqu.I("b.name").As("building_name"), + ). + Where( + goqu.I("r.id").Eq(rid), + goqu.I("t.moved_in_at <= upper(r.period)"), + ).ToSQL() + + var tenements []model.Tenement + err := pgxscan.Select(ctx, global.DB, tenements, tenementQuerySql, tenementQueryArgs...) + if err != nil { + cr.log.Error("取得指定报表所涉及的所有商户信息出错", zap.Error(err)) + return nil, err + } + return tenements, nil +} diff --git a/repository/withdraw.go b/repository/withdraw.go index a83b43a..c30bd19 100644 --- a/repository/withdraw.go +++ b/repository/withdraw.go @@ -132,7 +132,7 @@ func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model. PeriodBegin: Begin, PeriodEnd: End, Published: v.Published, - PublishedAt: nil, + PublishedAt: tools.TimeToStringPtr(v.LastWithdrawAuditAt), Status: 0., Withdraw: v.Withdraw, } diff --git a/service/calculate/checking.go b/service/calculate/checking.go new file mode 100644 index 0000000..7169626 --- /dev/null +++ b/service/calculate/checking.go @@ -0,0 +1,32 @@ +package calculate +import ( + "electricity_bill_calc/model" + "fmt" + "sync/atomic" +) + +func CheckMeterArea(report *model.ReportIndex, meters []*model.MeterDetail) (bool, error) { + anyAreaOptions := report.BasisPooled == model.POOLING_MODE_AREA || + report.AdjustPooled == model.POOLING_MODE_AREA || + report.PublicPooled == model.POOLING_MODE_AREA || + report.LossPooled == model.POOLING_MODE_AREA + + if anyAreaOptions { + var meterWithoutArea int32 + + for _, m := range meters { + if (m.MeterType == model.METER_INSTALLATION_TENEMENT || m.MeterType == model.METER_INSTALLATION_POOLING) && + m.Area == nil { + atomic.AddInt32(&meterWithoutArea, 1) + } + } + + if meterWithoutArea != 0 { + return false, fmt.Errorf("园区中有 %d 个表计没有设置面积,无法进行按面积摊薄。", meterWithoutArea) + } + + return true, nil + } + + return false, nil +} diff --git a/service/calculate/park.go b/service/calculate/park.go new file mode 100644 index 0000000..1af4ee2 --- /dev/null +++ b/service/calculate/park.go @@ -0,0 +1,37 @@ +package calculate + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/model/calculate" + "electricity_bill_calc/repository" + "time" +) + +func MetersParkCalculate(report model.ReportIndex, periodStart time.Time, + periodEnd time.Time, meterDetail []*model.MeterDetail, + summary calculate.Summary) ([]calculate.Meter, error) { + parkMeterReadings, err := repository.CalculateRepository.GetMeterReadings(report.Id, model.METER_INSTALLATION_PARK) + if err != nil { + return nil, err + } + + lastTermParkMeterReadings, err := repository.CalculateRepository.GetLastPeriodReadings(report.Id, model.METER_INSTALLATION_PARK) + if err != nil { + return nil, err + } + + parkMeterReadings = append(parkMeterReadings, lastTermParkMeterReadings...) + + var parkMetersReports []calculate.Meter + for _, meter := range meterDetail { + if meter.MeterType == model.METER_INSTALLATION_PARK { + parkMetersReport, err := determinePublicMeterConsumptions(meter.Code, periodStart, periodEnd, parkMeterReadings, *meter, summary) + if err != nil { + return nil, err + } + parkMetersReports = append(parkMetersReports, parkMetersReport) + } + + } + return parkMetersReports, nil +} diff --git a/service/calculate/pooled.go b/service/calculate/pooled.go new file mode 100644 index 0000000..12ddc91 --- /dev/null +++ b/service/calculate/pooled.go @@ -0,0 +1,111 @@ +package calculate + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/model/calculate" + "electricity_bill_calc/repository" + "github.com/shopspring/decimal" + "time" + "unsafe" +) + +//核算园区中的全部公摊表计的电量用量 +func PooledMetersCalculate(report *model.ReportIndex, periodStart time.Time, + periodEnd time.Time, meterDetails []*model.MeterDetail, + summary calculate.Summary) ([]calculate.Meter, error) { + poolingMeterReadings, err := repository.CalculateRepository.GetMeterReadings(report.Id, model.METER_INSTALLATION_POOLING) + if err != nil { + return nil, err + } + + lastTermPoolingMeterReadings, err := repository.CalculateRepository.GetLastPeriodReadings(report.Id, model.METER_INSTALLATION_POOLING) + if err != nil { + return nil, err + } + + poolingMeterReadings = append(poolingMeterReadings, lastTermPoolingMeterReadings...) + + var poolingMetersReports []calculate.Meter + for _, meter := range meterDetails { + poolingMetersReport, err := determinePublicMeterConsumptions(meter.Code, periodStart, periodEnd, poolingMeterReadings, *meter, summary) + if err != nil { + return nil, err + } + poolingMetersReports = append(poolingMetersReports, poolingMetersReport) + } + return poolingMetersReports, nil +} + +// 确定指定非商户表计在指定时间段内的全部电量 +func determinePublicMeterConsumptions(meterId string, periodStart time.Time, + periodEnd time.Time, readings []model.MeterReading, + meterDetail model.MeterDetail, summary calculate.Summary) (calculate.Meter, error) { + startReading, err := DeterminePublicMeterStartReading(meterId, periodStart, meterDetail.DetachedAt.Time, readings) + if err != nil { + return calculate.Meter{}, err + } + + endReading, err := DeterminePublicMeterEndReading(meterId, periodEnd, meterDetail.DetachedAt.Time, readings) + if err != nil { + return calculate.Meter{}, err + } + + overall, err := ComputeOverall(*startReading, *endReading, summary) + if err != nil { + return calculate.Meter{}, err + } + + critical, err := ComputeCritical(*startReading, *endReading, summary) + if err != nil { + return calculate.Meter{}, err + } + + peak, err := ComputePeak(*startReading, *endReading, summary) + if err != nil { + return calculate.Meter{}, err + } + + flat, err := ComputeFlat(*startReading, *endReading, summary) + if err != nil { + return calculate.Meter{}, err + } + + valley, err := ComputeValley(*startReading, *endReading, summary) + if err != nil { + return calculate.Meter{}, err + } + + return calculate.Meter{ + Code: meterId, + Detail: meterDetail, + CoveredArea: meterDetail.Area.Decimal, + LastTermReading: (*calculate.Reading)(unsafe.Pointer(&model.Reading{ + Ratio: startReading.Ratio, + Overall: startReading.Overall, + Critical: startReading.Critical, + Peak: startReading.Peak, + Flat: startReading.Flat, + Valley: startReading.Valley, + })), + CurrentTermReading: (*calculate.Reading)(unsafe.Pointer(&model.Reading{ + Ratio: endReading.Ratio, + Overall: endReading.Overall, + Critical: endReading.Critical, + Peak: endReading.Peak, + Flat: endReading.Flat, + Valley: endReading.Valley, + })), + Overall: overall, + Critical: critical, + Peak: peak, + Flat: flat, + Valley: valley, + AdjustLoss: model.ConsumptionUnit{}, + PooledBasic: model.ConsumptionUnit{}, + PooledAdjust: model.ConsumptionUnit{}, + PooledLoss: model.ConsumptionUnit{}, + PooledPublic: model.ConsumptionUnit{}, + SharedPoolingProportion: decimal.Decimal{}, + Poolings: nil, + }, nil +} diff --git a/service/calculate/shared.go b/service/calculate/shared.go new file mode 100644 index 0000000..528d1fc --- /dev/null +++ b/service/calculate/shared.go @@ -0,0 +1,105 @@ +package calculate + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/types" + "errors" + "fmt" + "time" +) + +// 确定指定非商户表计的起始读数 +func DeterminePublicMeterStartReading(meterId string, periodStart time.Time, + attachedAt time.Time, meterReadings []model.MeterReading) (*model.MeterReading, error) { + periodBeginning := types.Date{Time: periodStart}.ToBeginningOfDate() + + if len(meterReadings) <= 0 { + return nil, errors.New(fmt.Sprintf("表计的抄表记录数据不足%s", meterId)) + } + + var minReading types.DateTime + for _, reading := range meterReadings { + if reading.ReadAt.Before(minReading.Time) { + minReading = reading.ReadAt + } + } + startTimes := []time.Time{ + minReading.Time, + periodBeginning.Time, + ShiftToAsiaShanghai(attachedAt), + } + if len(startTimes) < 0 { + return nil, errors.New(fmt.Sprintf("无法确定表计 {%s} 的计量的起始时间", meterId)) + } + + var startReading []model.MeterReading + for _, reading := range meterReadings { + readingAt := ShiftToAsiaShanghai(reading.ReadAt.UTC()) + for _, startTime := range startTimes { + if reading.Meter == meterId && readingAt.After(startTime) || readingAt.Equal(startTime) { + startReading = append(startReading, reading) + break + } + } + } + + if len(startReading) <= 0 { + return nil, errors.New(fmt.Sprintf("无法确定表计 %s 的计量的起始读数", meterId)) + } + + var startReadings *model.MeterReading + for _, readings := range startReading { + if startReadings == nil || readings.ReadAt.Before(startReadings.ReadAt.Time) { + startReadings = &readings + } + } + return startReadings, nil +} + +// 确定指定非商户表计的结束读数 +func DeterminePublicMeterEndReading(meterId string, periodEnd time.Time, + detachedAt time.Time, meterReadings []model.MeterReading) (*model.MeterReading, error) { + periodEnding := types.Date{Time: periodEnd}.ToEndingOfDate() + + if len(meterReadings) <= 0 { + return nil, errors.New(fmt.Sprintf("表计的抄表记录数据不足%s", meterId)) + } + + var minReading types.DateTime + for _, reading := range meterReadings { + if reading.ReadAt.Before(minReading.Time) { + minReading = reading.ReadAt + } + } + startTimes := []time.Time{ + minReading.Time, + periodEnding.Time, + ShiftToAsiaShanghai(detachedAt), + } + if len(startTimes) < 0 { + return nil, errors.New(fmt.Sprintf("无法确定表计 {%s} 的计量的终止时间", meterId)) + } + + var startReading []model.MeterReading + for _, reading := range meterReadings { + readingAt := ShiftToAsiaShanghai(reading.ReadAt.UTC()) + for _, startTime := range startTimes { + if reading.Meter == meterId && readingAt.After(startTime) || readingAt.Equal(startTime) { + startReading = append(startReading, reading) + break + } + } + } + + if len(startReading) <= 0 { + return nil, errors.New(fmt.Sprintf("无法确定表计 %s 的计量的终止读数", meterId)) + } + + var startReadings *model.MeterReading + for _, readings := range startReading { + if startReadings == nil || readings.ReadAt.Before(startReadings.ReadAt.Time) { + startReadings = &readings + } + } + return startReadings, nil +} diff --git a/service/calculate/summary.go b/service/calculate/summary.go new file mode 100644 index 0000000..8b0bbf7 --- /dev/null +++ b/service/calculate/summary.go @@ -0,0 +1 @@ +package calculate diff --git a/service/calculate/tenement.go b/service/calculate/tenement.go new file mode 100644 index 0000000..ca82dad --- /dev/null +++ b/service/calculate/tenement.go @@ -0,0 +1,288 @@ +package calculate + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/model/calculate" + "electricity_bill_calc/repository" + "errors" + "fmt" + "github.com/shopspring/decimal" + "strings" + "time" + "unsafe" +) + +// 核算园区中的全部商户表计电量用电 +func TenementMetersCalculate(report *model.ReportIndex, PeriodStart time.Time, + PeriodEnd time.Time, meterDetails []*model.MeterDetail, + summary calculate.Summary) ([]model.TenementMeter, error) { + tenements, err := repository.CalculateRepository.GetAllTenements(report.Id) + if err != nil { + fmt.Println("tenement 0", err) + return nil, err + } + + tenementMeterRelations, err := repository.CalculateRepository.GetAllTenementMeterRelations(report.Park, PeriodEnd, PeriodStart) + if err != nil { + fmt.Println("tenement 1", err) + return nil, err + } + + tenementMeterReadings, err := repository.CalculateRepository.GetMeterReadings(report.Id, model.METER_INSTALLATION_TENEMENT) + if err != nil { + fmt.Println("tenement 2", err) + return nil, err + } + + lastPeriodReadings, err := repository.CalculateRepository.GetLastPeriodReadings(report.Id, model.METER_INSTALLATION_TENEMENT) + if err != nil { + fmt.Println("tenement 3", err) + return nil, err + } + + var tenementReports []model.Tenement + + for _, tenement := range tenements { + var meters []model.TenementMeter + + for _, relation := range tenementMeterRelations { + if strings.EqualFold(relation.TenementId, tenement.Id) { + meters = append(meters, relation) + } + } + + pt, err := determineTenementConsumptions( + tenement, + meters, + PeriodStart, + PeriodEnd, + tenementMeterReadings, + lastPeriodReadings, + meterDetails, + summary, + ) + if err != nil { + return nil, err + } + + report := model.Tenement{ + Id: pt.Tenement.Id, + Park: pt.Tenement.Park, + FullName: pt.Tenement.FullName, + ShortName: pt.Tenement.ShortName, + Abbr: pt.Tenement.Abbr, + Address: pt.Tenement.Address, + ContactName: pt.Tenement.ContactName, + ContactPhone: pt.Tenement.ContactPhone, + Building: pt.Tenement.Building, + BuildingName: pt.Tenement.BuildingName, + OnFloor: pt.Tenement.OnFloor, + InvoiceInfo: pt.Tenement.InvoiceInfo, + MovedInAt: pt.Tenement.MovedInAt, + MovedOutAt: pt.Tenement.MovedOutAt, + CreatedAt: pt.Tenement.CreatedAt, + LastModifiedAt: pt.Tenement.LastModifiedAt, + DeletedAt: pt.Tenement.DeletedAt, + } + + tenementReports = append(tenementReports, report) + } + + return tenementMeterRelations, nil +} + +//TODO: 2023.08.02 此方法未完成此方法主要用于。确定指定商户在指定时间段内的所有表计读数(完成) +func determineTenementConsumptions(tenement model.Tenement, + relatedMeters []model.TenementMeter, periodStart time.Time, + periodEnd time.Time, currentTermReadings []model.MeterReading, lastPeriodReadings []model.MeterReading, + meterDetails []*model.MeterDetail, summary calculate.Summary) (calculate.PrimaryTenementStatistics, error) { + var meters []calculate.Meter + for _, meter := range relatedMeters { + startReading, err := determineTenementMeterStartReading(meter.MeterId, periodStart, ShiftToAsiaShanghai(tenement.MovedInAt.Time), meter, currentTermReadings, lastPeriodReadings) + if err != nil { + fmt.Println(err) + return calculate.PrimaryTenementStatistics{}, err + } + + endReading, err := determineTenementMeterEndReading(meter.MeterId, periodEnd, ShiftToAsiaShanghai(tenement.MovedOutAt.Time), meter, currentTermReadings) + if err != nil { + fmt.Println(err) + return calculate.PrimaryTenementStatistics{}, err + } + + detail, err := getMeterDetail(meterDetails, meter.MeterId) + if err != nil { + return calculate.PrimaryTenementStatistics{}, err + } + + overall, err := ComputeOverall(*startReading, *endReading, summary) + if err != nil { + return calculate.PrimaryTenementStatistics{}, err + } + + critical, err := ComputeCritical(*startReading, *endReading, summary) + if err != nil { + return calculate.PrimaryTenementStatistics{}, err + } + + peak, err := ComputePeak(*startReading, *endReading, summary) + if err != nil { + return calculate.PrimaryTenementStatistics{}, err + } + + flat, err := ComputeFlat(*startReading, *endReading, summary) + if err != nil { + return calculate.PrimaryTenementStatistics{}, err + } + + valley, err := ComputeValley(*startReading, *endReading, summary) + if err != nil { + return calculate.PrimaryTenementStatistics{}, err + } + + lastTermReading := model.Reading{ + Ratio: startReading.Ratio, + Overall: startReading.Overall, + Critical: startReading.Critical, + Peak: startReading.Peak, + Flat: startReading.Flat, + Valley: startReading.Valley, + } + + lastTermReadingPtr := &lastTermReading + + currentTermReading := model.Reading{ + Ratio: endReading.Ratio, + Overall: endReading.Overall, + Critical: endReading.Critical, + Peak: endReading.Peak, + Flat: endReading.Flat, + Valley: endReading.Valley, + } + + currentTermReadingPtr := ¤tTermReading + meter := calculate.Meter{ + Code: meter.MeterId, + Detail: detail, + CoveredArea: decimal.NewFromFloat(detail.Area.Decimal.InexactFloat64()), + + LastTermReading: (*calculate.Reading)(unsafe.Pointer(lastTermReadingPtr)), + CurrentTermReading: (*calculate.Reading)(unsafe.Pointer(currentTermReadingPtr)), + + Overall: overall, + Critical: critical, + Peak: peak, + Flat: flat, + Valley: valley, + + AdjustLoss: model.ConsumptionUnit{}, + PooledBasic: model.ConsumptionUnit{}, + PooledAdjust: model.ConsumptionUnit{}, + PooledLoss: model.ConsumptionUnit{}, + PooledPublic: model.ConsumptionUnit{}, + SharedPoolingProportion: decimal.Decimal{}, + Poolings: nil, + } + + meters = append(meters, meter) + } + + return calculate.PrimaryTenementStatistics{ + Tenement: tenement, + Meters: meters, + }, nil +} + +func getMeterDetail(meterDetails []*model.MeterDetail, code string) (model.MeterDetail, error) { + for _, detail := range meterDetails { + if detail.Code == code { + return *detail, nil + } + } + return model.MeterDetail{}, errors.New(fmt.Sprintf("表计 %s 的详细信息不存在", code)) +} + +//确定指定表计的起始读数 +func determineTenementMeterStartReading(meterId string, periodStart time.Time, tenementMovedInAt time.Time, + meterRelation model.TenementMeter, currentTermReadings []model.MeterReading, + lastPeriodReadings []model.MeterReading) (*model.MeterReading, error) { + var startTime time.Time + timeList := []time.Time{ + periodStart, + tenementMovedInAt, + meterRelation.AssociatedAt.Time, + } + + for _, t := range timeList { + if t.After(startTime) { + startTime = t + } + } + if startTime.IsZero() { + return nil, fmt.Errorf("无法确定表计 %s 的计量的起始时间", meterId) + } + + var startReading *model.MeterReading + if startTime.Equal(periodStart) { + for _, reading := range lastPeriodReadings { + if reading.Meter == meterId { + if startReading == nil || reading.ReadAt.After(startReading.ReadAt.Time) { + startReading = &reading + } + } + } + } else { + for _, reading := range currentTermReadings { + readingAt := ShiftToAsiaShanghai(reading.ReadAt.Time) + if reading.Meter == meterId && readingAt.After(startTime) { + if startReading == nil || readingAt.Before(startReading.ReadAt.Time) { + startReading = &reading + } + } + } + } + if startReading == nil { + return nil, errors.New("无法确定表计 " + meterId + " 的计量的起始读数") + } + return startReading, nil +} + +// 确定指定表计的终止读书 +func determineTenementMeterEndReading(meterId string, periodEnd time.Time, + TenementMovedOutAt time.Time, meterRelation model.TenementMeter, + currentTermReadings []model.MeterReading) (*model.MeterReading, error) { + var endTime time.Time + timeList := []time.Time{ + periodEnd, + TenementMovedOutAt, + ShiftToAsiaShanghai(meterRelation.DisassociatedAt.Time), + } + for _, t := range timeList { + if t.After(endTime) { + endTime = t + } + } + if endTime.IsZero() { + return nil, fmt.Errorf("无法确定表计 %s 的计量的结束时间", meterId) + } + + var endReading *model.MeterReading + + for _, reading := range currentTermReadings { + readingAt := ShiftToAsiaShanghai(reading.ReadAt.Time) + if reading.Meter == meterId && readingAt.Before(endTime) { + if endReading == nil || readingAt.After(ShiftToAsiaShanghai(endReading.ReadAt.Time)) { + endReading = &reading + } + } + } + if endReading == nil { + return nil, errors.New(fmt.Sprintf("无法确定表计 %s 的计量的结束读数", meterId)) + } + return endReading, nil +} + +func ShiftToAsiaShanghai(t time.Time) time.Time { + location, _ := time.LoadLocation("Asia/Shanghai") + return t.In(location) +} diff --git a/service/calculate/utils.go b/service/calculate/utils.go new file mode 100644 index 0000000..801a5c7 --- /dev/null +++ b/service/calculate/utils.go @@ -0,0 +1,141 @@ +package calculate + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/model/calculate" + "errors" + "fmt" + "github.com/shopspring/decimal" +) + +// 计算两个读书之间的有功(总)电量 +func ComputeOverall(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) { + start := startReading.Overall.InexactFloat64() * startReading.Ratio.InexactFloat64() + end := endReading.Overall.InexactFloat64() * endReading.Ratio.InexactFloat64() + + if start > end { + return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("表计 {%s} 有功(总)开始读数 {%x} 大于结束读数 {%x}", startReading.Meter, start, end)) + } + + amount := end - start + + var summaryAmount float64 + if summary.Overall.Amount == decimal.Zero { + summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64() + } else { + summaryAmount = summary.Overall.Amount.InexactFloat64() + } + + return model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(amount), + Fee: decimal.NewFromFloat(amount * summary.Overall.Price.InexactFloat64()), + Price: decimal.NewFromFloat(summary.Overall.Price.InexactFloat64()), + Proportion: decimal.NewFromFloat(amount / summaryAmount), + }, nil +} + +//计算两个读书之间的尖峰电量 +func ComputeCritical(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) { + start := startReading.Critical.InexactFloat64() * startReading.Ratio.InexactFloat64() + end := endReading.Critical.InexactFloat64() * endReading.Ratio.InexactFloat64() + + if start > end { + return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("尖峰开始读数 {%x} 大于结束读数 {%x}", start, end)) + } + + amount := end - start + var summaryAmount float64 + + if summary.Critical.Amount.Equal(decimal.Zero) { + summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64() + } else { + summaryAmount = summary.Critical.Amount.InexactFloat64() + } + + return model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(amount), + Fee: decimal.NewFromFloat(amount * summary.Critical.Amount.InexactFloat64()), + Price: decimal.NewFromFloat(summary.Critical.Price.InexactFloat64()), + Proportion: decimal.NewFromFloat(amount / summaryAmount), + }, nil +} + +// 计算两个读数之间的峰电量 +func ComputePeak(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) { + start := startReading.Peak.InexactFloat64() * startReading.Ratio.InexactFloat64() + end := startReading.Peak.InexactFloat64() * endReading.Ratio.InexactFloat64() + + if start > end { + return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("峰开始读数 {%x} 大于结束读数 {%x}", start, end)) + } + + amount := end - start + var summaryAmount float64 + + if summary.Peak.Amount.Equal(decimal.Zero) { + summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64() + } else { + summaryAmount = summary.Peak.Amount.InexactFloat64() + } + + return model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(amount), + Fee: decimal.NewFromFloat(amount * summary.Peak.Price.InexactFloat64()), + Price: decimal.NewFromFloat(summary.Peak.Price.InexactFloat64()), + Proportion: decimal.NewFromFloat(amount / summaryAmount), + }, nil + +} + +//计算两个读数之间的平电量 +func ComputeFlat(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) { + start := startReading.Flat.InexactFloat64() * startReading.Ratio.InexactFloat64() + end := endReading.Flat.InexactFloat64() * endReading.Ratio.InexactFloat64() + + if start > end { + return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("平开始读数 {%x} 大于结束读数 {%x}", start, end)) + } + + amount := end - start + var summaryAmount float64 + + if summary.Flat.Amount.Equal(decimal.Zero) { + summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64() + } else { + summaryAmount = summary.Flat.Amount.InexactFloat64() + } + + return model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(amount), + Fee: decimal.NewFromFloat(amount * summary.Flat.Price.InexactFloat64()), + Price: decimal.NewFromFloat(summary.Flat.Price.InexactFloat64()), + Proportion: decimal.NewFromFloat(amount / summaryAmount), + }, nil +} + +//计算两个读数之间的谷电量 +func ComputeValley(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) { + start := startReading.Valley.InexactFloat64() * startReading.Ratio.InexactFloat64() + end := endReading.Valley.InexactFloat64() * endReading.Ratio.InexactFloat64() + + if start > end { + return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("谷开始读数 {%x} 大于结束读数 {%x}", start, end)) + } + + amount := end - start + + var summaryAmount float64 + + if summary.Valley.Amount.Equal(decimal.Zero) { + summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64() + } else { + summaryAmount = summary.Valley.Amount.InexactFloat64() + } + + return model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(amount), + Fee: decimal.NewFromFloat(amount * summary.Valley.Price.InexactFloat64()), + Price: decimal.NewFromFloat(summary.Valley.Price.InexactFloat64()), + Proportion: decimal.NewFromFloat(amount / summaryAmount), + }, nil +} diff --git a/service/calculate/wattCost.go b/service/calculate/wattCost.go new file mode 100644 index 0000000..5d2956e --- /dev/null +++ b/service/calculate/wattCost.go @@ -0,0 +1,66 @@ +package calculate + +import ( + "electricity_bill_calc/model/calculate" + "electricity_bill_calc/repository" + "fmt" +) + +func MainCalculateProcess(rid string) { + report, err := repository.ReportRepository.GetReportIndex(rid) + if err != nil { + fmt.Println("1", err.Error()+"指定报表不存在") + return + } + + reportSummary, err := repository.ReportRepository.RetrieveReportSummary(rid) + if err != nil { + fmt.Println("2", err.Error()+"指定报表的基本电量电费数据不存在") + return + } + + summary := calculate.FromReportSummary(reportSummary, report) + + periodStart := report.Period.SafeLower() + periodEnd := report.Period.SafeUpper() + + meterDetails, err := repository.MeterRepository.AllUsedMetersInReport(report.Id) + if err != nil { + fmt.Println("3", err) + return + } + + meterRelations, err := repository.CalculateRepository.GetAllPoolingMeterRelations(report.Park, periodStart.Time) + if err != nil { + fmt.Println("4", err) + return + } + _, err = CheckMeterArea(report, meterDetails) + if err != nil { + fmt.Println("5", err) + return + } + + // 寻找每一个商户的所有表计读数,然后对分配到各个商户的表计读数进行初步的计算. + tenementReports, err := TenementMetersCalculate(report, periodStart.Time, periodEnd.Time, meterDetails, summary) + if err != nil { + fmt.Println("6", err) + return + } + + //取得所有公摊表计的读数,以及公摊表计对应的分摊表计 + poolingMetersReports, err := PooledMetersCalculate(report, periodStart.Time, periodEnd.Time, meterDetails, summary) + if err != nil { + fmt.Println("7", err) + return + } + + parkMetersReports, err := MetersParkCalculate(*report, periodStart.Time, periodEnd.Time, meterDetails, summary) + if err != nil { + fmt.Println("8", err) + return + } + + fmt.Println(meterRelations, tenementReports, poolingMetersReports, parkMetersReports) + +} diff --git a/tools/utils.go b/tools/utils.go index af61a41..325453f 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -160,7 +160,6 @@ func NullTime2PointerString(nullTime sql.NullTime) *string { } } - //该方法用于将时间解析为字符串指针 func TimeToStringPtr(t *time.Time) *string { if t == nil { From 8fc463bd9d53487d314bb9f2a69eda18cbeb407f Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Thu, 3 Aug 2023 17:30:00 +0800 Subject: [PATCH 136/141] =?UTF-8?q?[=E8=AE=A1=E7=AE=97=E7=9B=B8=E5=85=B3]?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E6=89=80=E6=9C=89=E8=A1=A8=E8=AE=A1=E7=9A=84?= =?UTF-8?q?=E6=80=BB=E7=94=B5=E9=87=8F(=E5=AE=8C=E6=88=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/calculate/summary.go | 38 +++++++++++++++++++++++++++++++++++ service/calculate/tenement.go | 28 ++++---------------------- service/calculate/wattCost.go | 6 +++++- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/service/calculate/summary.go b/service/calculate/summary.go index 8b0bbf7..50bd708 100644 --- a/service/calculate/summary.go +++ b/service/calculate/summary.go @@ -1 +1,39 @@ package calculate + +import ( + "electricity_bill_calc/model/calculate" + "github.com/shopspring/decimal" +) + +// 计算已经启用的商铺面积和 +func TotalConsumptionCalculate(tenements []calculate.PrimaryTenementStatistics, + summary calculate.Summary) decimal.Decimal { + var areaMaters []calculate.Meter + for _, t := range tenements { + areaMaters = append(areaMaters, t.Meters...) + } + + areaMaters = removeDuplicates(areaMaters) + + var areaTotal float64 + for _, m := range areaMaters { + areaTotal += m.Detail.Area.Decimal.InexactFloat64() + } + + areaTotal += summary.OverallArea.InexactFloat64() + + return decimal.NewFromFloat(areaTotal) + +} + +func removeDuplicates(meters []calculate.Meter) []calculate.Meter { + result := make([]calculate.Meter, 0, len(meters)) + seen := make(map[string]bool) + for _, meter := range meters { + if !seen[meter.Code] { + seen[meter.Code] = true + result = append(result, meter) + } + } + return result +} diff --git a/service/calculate/tenement.go b/service/calculate/tenement.go index ca82dad..1749140 100644 --- a/service/calculate/tenement.go +++ b/service/calculate/tenement.go @@ -15,7 +15,7 @@ import ( // 核算园区中的全部商户表计电量用电 func TenementMetersCalculate(report *model.ReportIndex, PeriodStart time.Time, PeriodEnd time.Time, meterDetails []*model.MeterDetail, - summary calculate.Summary) ([]model.TenementMeter, error) { + summary calculate.Summary) ([]calculate.PrimaryTenementStatistics, error) { tenements, err := repository.CalculateRepository.GetAllTenements(report.Id) if err != nil { fmt.Println("tenement 0", err) @@ -40,7 +40,7 @@ func TenementMetersCalculate(report *model.ReportIndex, PeriodStart time.Time, return nil, err } - var tenementReports []model.Tenement + var tenementReports []calculate.PrimaryTenementStatistics for _, tenement := range tenements { var meters []model.TenementMeter @@ -65,30 +65,10 @@ func TenementMetersCalculate(report *model.ReportIndex, PeriodStart time.Time, return nil, err } - report := model.Tenement{ - Id: pt.Tenement.Id, - Park: pt.Tenement.Park, - FullName: pt.Tenement.FullName, - ShortName: pt.Tenement.ShortName, - Abbr: pt.Tenement.Abbr, - Address: pt.Tenement.Address, - ContactName: pt.Tenement.ContactName, - ContactPhone: pt.Tenement.ContactPhone, - Building: pt.Tenement.Building, - BuildingName: pt.Tenement.BuildingName, - OnFloor: pt.Tenement.OnFloor, - InvoiceInfo: pt.Tenement.InvoiceInfo, - MovedInAt: pt.Tenement.MovedInAt, - MovedOutAt: pt.Tenement.MovedOutAt, - CreatedAt: pt.Tenement.CreatedAt, - LastModifiedAt: pt.Tenement.LastModifiedAt, - DeletedAt: pt.Tenement.DeletedAt, - } - - tenementReports = append(tenementReports, report) + tenementReports = append(tenementReports, pt) } - return tenementMeterRelations, nil + return tenementReports, nil } //TODO: 2023.08.02 此方法未完成此方法主要用于。确定指定商户在指定时间段内的所有表计读数(完成) diff --git a/service/calculate/wattCost.go b/service/calculate/wattCost.go index 5d2956e..f9556f1 100644 --- a/service/calculate/wattCost.go +++ b/service/calculate/wattCost.go @@ -55,12 +55,16 @@ func MainCalculateProcess(rid string) { return } + // 获取所有的物业表计,然后对所有的物业表计电量进行计算。 parkMetersReports, err := MetersParkCalculate(*report, periodStart.Time, periodEnd.Time, meterDetails, summary) if err != nil { fmt.Println("8", err) return } - fmt.Println(meterRelations, tenementReports, poolingMetersReports, parkMetersReports) + //计算所有表计的总电量 + parkTotal := TotalConsumptionCalculate(tenementReports, summary) + + fmt.Println(meterRelations, poolingMetersReports, parkMetersReports, parkTotal) } From 6b3d3dd93c249d2dcf8eda57430d2301d05503fe Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 4 Aug 2023 09:39:59 +0800 Subject: [PATCH 137/141] =?UTF-8?q?[=E8=AE=A1=E7=AE=97=E7=9B=B8=E5=85=B3]?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E7=BA=BF=E6=8D=9F=E4=BB=A5=E5=8F=8A=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E7=BA=BF=E6=8D=9F=EF=BC=88=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/report.go | 40 +++++++++++++++------------- service/calculate/summary.go | 50 +++++++++++++++++++++++++++++++++++ service/calculate/wattCost.go | 6 +++++ 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/model/report.go b/model/report.go index 2972647..1d7d207 100644 --- a/model/report.go +++ b/model/report.go @@ -7,25 +7,27 @@ import ( ) type ReportIndex struct { - Id string `json:"id"` - Park string `json:"parkId" db:"park_id"` - Period types.DateRange `json:"period"` - Category int16 `json:"category"` - MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"` - PricePolicy int16 `json:"pricePolicy"` - BasisPooled int16 `json:"basisPooled"` - AdjustPooled int16 `json:"adjustPooled"` - LossPooled int16 `json:"lossPooled"` - PublicPooled int16 `json:"publicPooled"` - Published bool `json:"published"` - PublishedAt *types.DateTime `json:"publishedAt" db:"published_at"` - Withdraw int16 `json:"withdraw"` - LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt" db:"last_withdraw_applied_at"` - LastWithdrawAuditAt *types.DateTime `json:"lastWithdrawAuditAt" db:"last_withdraw_audit_at"` - Status *int16 `json:"status"` - Message *string `json:"message"` - CreatedAt types.DateTime `json:"createdAt" db:"created_at"` - LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` + Id string `json:"id"` + Park string `json:"parkId" db:"park_id"` + Period types.DateRange `json:"period"` + Category int16 `json:"category"` + MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"` + PricePolicy int16 `json:"pricePolicy"` + BasisPooled int16 `json:"basisPooled"` + AdjustPooled int16 `json:"adjustPooled"` + LossPooled int16 `json:"lossPooled"` + PublicPooled int16 `json:"publicPooled"` + Published bool `json:"published"` + PublishedAt *types.DateTime `json:"publishedAt" db:"published_at"` + Withdraw int16 `json:"withdraw"` + LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt" db:"last_withdraw_applied_at"` + LastWithdrawAuditAt *types.DateTime `json:"lastWithdrawAuditAt" db:"last_withdraw_audit_at"` + Status *int16 `json:"status"` + Message *string `json:"message"` + CreatedAt types.DateTime `json:"createdAt" db:"created_at"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` + AuthorizedLossRate float64 `json:"authorized_loss_rate" db:"authorized_loss_rate"` + AuthorizedLossRateIncrement float64 `json:"authorized_loss_rate_increment" db:"authorized_loss_rate_increment"` } type ReportSummary struct { diff --git a/service/calculate/summary.go b/service/calculate/summary.go index 50bd708..85f22ea 100644 --- a/service/calculate/summary.go +++ b/service/calculate/summary.go @@ -1,7 +1,10 @@ package calculate import ( + "electricity_bill_calc/model" "electricity_bill_calc/model/calculate" + "errors" + "fmt" "github.com/shopspring/decimal" ) @@ -37,3 +40,50 @@ func removeDuplicates(meters []calculate.Meter) []calculate.Meter { } return result } + +//计算线损以及调整线损 +func LossCalculate(report *model.ReportIndex, Public []calculate.Meter, + publicTotal decimal.Decimal, summary calculate.Summary) error { + summary.Loss = summary.Overall.Amount.Sub(summary.TotalConsumption) + + var summaryAmount decimal.Decimal + if summary.Overall.Amount == decimal.Zero { + summaryAmount = decimal.NewFromFloat(1.0) + } else { + summaryAmount = summary.Overall.Amount + } + + summary.LossProportion = summary.Loss.Div(summaryAmount) + + var authorizedLossRate decimal.Decimal + if summary.LossProportion.InexactFloat64() > report.AuthorizedLossRate { + authorizedLossRate = summary.LossProportion + } else { + return errors.New(fmt.Sprintf("经过核算园区的线损率为:{%.8f}, 核定线损率为:{%.8f}", summary.LossProportion.InexactFloat64(),authorizedLossRate.InexactFloat64())) + } + + summary.AuthoizeLoss = model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(summary.Overall.Amount.InexactFloat64() * authorizedLossRate.InexactFloat64()), + Fee: decimal.NewFromFloat((summary.Overall.Amount.InexactFloat64() * authorizedLossRate.InexactFloat64()) * summary.Overall.Price.InexactFloat64()), + Price: summary.Overall.Price, + Proportion: authorizedLossRate, + } + + differentialLoss := summary.LossDilutedPrice.Sub(summary.AuthoizeLoss.Amount) + + if publicTotal.InexactFloat64() <= decimal.Zero.InexactFloat64() { + return errors.New("园区公共表计的电量总和为非正值,或者园区未设置公共表计,无法计算核定线损") + } + + for _, meter := range Public { + amountProportion := meter.Overall.Amount.InexactFloat64() / publicTotal.InexactFloat64() + adjustAmount := differentialLoss.InexactFloat64() * decimal.NewFromFloat(-1.0).InexactFloat64() + meter.AdjustLoss = model.ConsumptionUnit{ + Amount: decimal.NewFromFloat(adjustAmount), + Fee: decimal.NewFromFloat(adjustAmount * summary.LossDilutedPrice.InexactFloat64()), + Price: summary.LossDilutedPrice, + Proportion: decimal.NewFromFloat(amountProportion), + } + } + return nil +} diff --git a/service/calculate/wattCost.go b/service/calculate/wattCost.go index f9556f1..f7ff373 100644 --- a/service/calculate/wattCost.go +++ b/service/calculate/wattCost.go @@ -65,6 +65,12 @@ func MainCalculateProcess(rid string) { //计算所有表计的总电量 parkTotal := TotalConsumptionCalculate(tenementReports, summary) + err = LossCalculate(report, parkMetersReports, parkTotal, summary) + if err != nil { + fmt.Println("9", err) + return + } + fmt.Println(meterRelations, poolingMetersReports, parkMetersReports, parkTotal) } From 5710a640e8bd0dd149134d96bd0e97401ae8d24c Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 4 Aug 2023 10:05:27 +0800 Subject: [PATCH 138/141] =?UTF-8?q?[=E8=AE=A1=E7=AE=97=E7=9B=B8=E5=85=B3]f?= =?UTF-8?q?ix=20=E8=AE=A1=E7=AE=97=E7=BA=BF=E6=8D=9F=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=BA=BF=E6=8D=9F=E5=8F=82=E6=95=B0=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E3=80=82new=20=E8=AE=A1=E7=AE=97=E6=89=80=E6=9C=89?= =?UTF-8?q?=E5=B7=B2=E7=BB=8F=E5=90=AF=E7=94=A8=E7=9A=84=E5=95=86=E9=93=BA?= =?UTF-8?q?=E9=9D=A2=E7=A7=AF=E6=80=BB=E5=92=8C=EF=BC=8C=E4=BB=85=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E6=89=80=E6=9C=89=E6=9C=AA=E8=BF=81=E5=87=BA=E7=9A=84?= =?UTF-8?q?=E5=95=86=E6=88=B7=E7=9A=84=E6=89=80=E6=9C=89=E8=A1=A8=E8=AE=A1?= =?UTF-8?q?=E5=AF=B9=E5=BA=94=E7=9A=84=E5=95=86=E9=93=BA=E9=9D=A2=E7=A7=AF?= =?UTF-8?q?=E3=80=82(=E5=AE=8C=E6=88=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/calculate/summary.go | 32 ++++++++++++++++++++++++++++---- service/calculate/wattCost.go | 9 ++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/service/calculate/summary.go b/service/calculate/summary.go index 85f22ea..26d5620 100644 --- a/service/calculate/summary.go +++ b/service/calculate/summary.go @@ -42,8 +42,8 @@ func removeDuplicates(meters []calculate.Meter) []calculate.Meter { } //计算线损以及调整线损 -func LossCalculate(report *model.ReportIndex, Public []calculate.Meter, - publicTotal decimal.Decimal, summary calculate.Summary) error { +func LossCalculate(report *model.ReportIndex, Public *[]calculate.Meter, + publicTotal *decimal.Decimal, summary *calculate.Summary) error { summary.Loss = summary.Overall.Amount.Sub(summary.TotalConsumption) var summaryAmount decimal.Decimal @@ -56,10 +56,11 @@ func LossCalculate(report *model.ReportIndex, Public []calculate.Meter, summary.LossProportion = summary.Loss.Div(summaryAmount) var authorizedLossRate decimal.Decimal + //TODO: 2023.08.04 在此发现reportIndex结构体与数据库中的report表字段不对应缺少两个相应字段,在此添加的,如在其他地方有错误优先查找这里 if summary.LossProportion.InexactFloat64() > report.AuthorizedLossRate { authorizedLossRate = summary.LossProportion } else { - return errors.New(fmt.Sprintf("经过核算园区的线损率为:{%.8f}, 核定线损率为:{%.8f}", summary.LossProportion.InexactFloat64(),authorizedLossRate.InexactFloat64())) + return errors.New(fmt.Sprintf("经过核算园区的线损率为:{%.8f}, 核定线损率为:{%.8f}", summary.LossProportion.InexactFloat64(), authorizedLossRate.InexactFloat64())) } summary.AuthoizeLoss = model.ConsumptionUnit{ @@ -75,7 +76,7 @@ func LossCalculate(report *model.ReportIndex, Public []calculate.Meter, return errors.New("园区公共表计的电量总和为非正值,或者园区未设置公共表计,无法计算核定线损") } - for _, meter := range Public { + for _, meter := range *Public { amountProportion := meter.Overall.Amount.InexactFloat64() / publicTotal.InexactFloat64() adjustAmount := differentialLoss.InexactFloat64() * decimal.NewFromFloat(-1.0).InexactFloat64() meter.AdjustLoss = model.ConsumptionUnit{ @@ -87,3 +88,26 @@ func LossCalculate(report *model.ReportIndex, Public []calculate.Meter, } return nil } + +// 计算已经启用的商铺面积和 +func EnabledAreaCalculate(tenements *[]calculate.PrimaryTenementStatistics, summary *calculate.Summary) error { + var areaMeters []calculate.Meter + for _, t := range *tenements { + areaMeters = append(areaMeters, t.Meters...) + } + // 去重 + uniqueAreaMeters := make(map[string]calculate.Meter) + for _, meter := range areaMeters { + uniqueAreaMeters[meter.Code] = meter + } + var areaTotal decimal.Decimal + for _, meter := range uniqueAreaMeters { + areaTotal = areaTotal.Add(meter.Detail.Area.Decimal) + } + if summary != nil { + summary.OverallArea = areaTotal + } else { + return errors.New("summary is nil") + } + return nil +} diff --git a/service/calculate/wattCost.go b/service/calculate/wattCost.go index f7ff373..6fe261b 100644 --- a/service/calculate/wattCost.go +++ b/service/calculate/wattCost.go @@ -65,12 +65,19 @@ func MainCalculateProcess(rid string) { //计算所有表计的总电量 parkTotal := TotalConsumptionCalculate(tenementReports, summary) - err = LossCalculate(report, parkMetersReports, parkTotal, summary) + // 计算线损以及调整线损 + err = LossCalculate(report, &parkMetersReports, &parkTotal, &summary) if err != nil { fmt.Println("9", err) return } + err = EnabledAreaCalculate(&tenementReports, &summary) + if err != nil{ + fmt.Println("10",err) + return + } + fmt.Println(meterRelations, poolingMetersReports, parkMetersReports, parkTotal) } From c916301f6bb7d6ffe0b002b064ce7c2c027bb7d0 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 4 Aug 2023 10:24:09 +0800 Subject: [PATCH 139/141] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/calculate/wattCost.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/service/calculate/wattCost.go b/service/calculate/wattCost.go index 6fe261b..fe14ba6 100644 --- a/service/calculate/wattCost.go +++ b/service/calculate/wattCost.go @@ -48,7 +48,7 @@ func MainCalculateProcess(rid string) { return } - //取得所有公摊表计的读数,以及公摊表计对应的分摊表计 + // 取得所有公摊表计的读数,以及公摊表计对应的分摊表计 poolingMetersReports, err := PooledMetersCalculate(report, periodStart.Time, periodEnd.Time, meterDetails, summary) if err != nil { fmt.Println("7", err) @@ -62,7 +62,7 @@ func MainCalculateProcess(rid string) { return } - //计算所有表计的总电量 + // 计算所有表计的总电量 parkTotal := TotalConsumptionCalculate(tenementReports, summary) // 计算线损以及调整线损 @@ -72,6 +72,7 @@ func MainCalculateProcess(rid string) { return } + // 计算所有已经启用的商铺面积总和,仅计算所有未迁出的商户的所有表计对应的商铺面积。 err = EnabledAreaCalculate(&tenementReports, &summary) if err != nil{ fmt.Println("10",err) From ce4c483bcb810cb38d90f01120759f43f0a50bcb Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 4 Aug 2023 11:09:04 +0800 Subject: [PATCH 140/141] =?UTF-8?q?[=E8=AE=A1=E7=AE=97=E7=9B=B8=E5=85=B3]?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E5=B7=B2=E7=BB=8F=E5=90=AF=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E5=95=86=E9=93=BA=E9=9D=A2=E7=A7=AF=E5=92=8C=EF=BC=88=E5=AE=8C?= =?UTF-8?q?=E6=88=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/calculate/meter.go | 10 ++++++++++ service/calculate/summary.go | 23 ++++++++++++++++++++++- service/calculate/tenement.go | 28 ++++++++++++++++++++++++++++ service/calculate/wattCost.go | 9 +++++++-- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 service/calculate/meter.go diff --git a/service/calculate/meter.go b/service/calculate/meter.go new file mode 100644 index 0000000..b30cb72 --- /dev/null +++ b/service/calculate/meter.go @@ -0,0 +1,10 @@ +package calculate + +import "electricity_bill_calc/model/calculate" + +// / 合并所有的表计 +type Key struct { + Code string + TenementID string +} +type MeterMap map[Key]calculate.Meter diff --git a/service/calculate/summary.go b/service/calculate/summary.go index 26d5620..13ba004 100644 --- a/service/calculate/summary.go +++ b/service/calculate/summary.go @@ -90,7 +90,8 @@ func LossCalculate(report *model.ReportIndex, Public *[]calculate.Meter, } // 计算已经启用的商铺面积和 -func EnabledAreaCalculate(tenements *[]calculate.PrimaryTenementStatistics, summary *calculate.Summary) error { +func EnabledAreaCalculate(tenements *[]calculate.PrimaryTenementStatistics, + summary *calculate.Summary) error { var areaMeters []calculate.Meter for _, t := range *tenements { areaMeters = append(areaMeters, t.Meters...) @@ -111,3 +112,23 @@ func EnabledAreaCalculate(tenements *[]calculate.PrimaryTenementStatistics, summ } return nil } + +// 计算基本电费分摊、调整电费分摊以及电费摊薄单价。 +func PricesCalculate(summary *calculate.Summary) error { + if summary.TotalConsumption.IsZero() { + return nil + } + summary.BasicPooledPriceConsumption = summary.BasicFee.Div(summary.TotalConsumption) + if summary.OverallArea.IsZero() { + summary.BasicPooledPriceArea = decimal.Zero + } else { + summary.BasicPooledPriceArea = summary.BasicFee.Div(summary.OverallArea) + } + summary.AdjustPooledPriceConsumption = summary.AdjustFee.Div(summary.TotalConsumption) + if summary.OverallArea.IsZero() { + summary.AdjustPooledPriceArea = decimal.Zero + } else { + summary.AdjustPooledPriceArea = summary.AdjustFee.Div(summary.OverallArea) + } + return nil +} \ No newline at end of file diff --git a/service/calculate/tenement.go b/service/calculate/tenement.go index 1749140..fd13741 100644 --- a/service/calculate/tenement.go +++ b/service/calculate/tenement.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "github.com/shopspring/decimal" + "sort" "strings" "time" "unsafe" @@ -266,3 +267,30 @@ func ShiftToAsiaShanghai(t time.Time) time.Time { location, _ := time.LoadLocation("Asia/Shanghai") return t.In(location) } + +// 计算各个商户的合计信息,并归总与商户关联的表计记录 +func TenementChargeCalculate(tenements []calculate.PrimaryTenementStatistics, + summary calculate.Summary, meters MeterMap, _relations []model.MeterRelation) { + result := make(map[string][]string) + for _, t := range tenements { + meterCodes := make([]string, 0) + for _, m := range t.Meters { + meterCodes = append(meterCodes, m.Code) + } + sort.Strings(meterCodes) + result[t.Tenement.Id] = meterCodes + } + var Key Key + for tCode, meterCodes := range result { + relatedMeters := make([]calculate.Meter, 0) + for _, code := range meterCodes { + Key.Code = code + "_" + tCode + meter, ok := meters[Key] + if ok { + relatedMeters = append(relatedMeters, meter) + } + } + // 计算商户的合计电费信息 + + } +} diff --git a/service/calculate/wattCost.go b/service/calculate/wattCost.go index fe14ba6..735c5f6 100644 --- a/service/calculate/wattCost.go +++ b/service/calculate/wattCost.go @@ -74,8 +74,13 @@ func MainCalculateProcess(rid string) { // 计算所有已经启用的商铺面积总和,仅计算所有未迁出的商户的所有表计对应的商铺面积。 err = EnabledAreaCalculate(&tenementReports, &summary) - if err != nil{ - fmt.Println("10",err) + if err != nil { + fmt.Println("10", err) + return + } + err = PricesCalculate(&summary) + if err != nil { + fmt.Println("11", err) return } From af359f4429b7b9ff99681b531d5f94dfa4202514 Mon Sep 17 00:00:00 2001 From: ZiHangQin <1420014281@qq.com> Date: Fri, 4 Aug 2023 14:38:03 +0800 Subject: [PATCH 141/141] =?UTF-8?q?enhance(calculate):=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=96=B9=E6=B3=95=EF=BC=9A=E8=AE=A1=E7=AE=97=E5=95=86?= =?UTF-8?q?=E6=88=B7=E7=9A=84=E5=90=88=E8=AE=A1=E7=94=B5=E8=B4=B9=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=EF=BC=8C=E5=B9=B6=E5=BD=92=E6=80=BB=E4=B8=8E=E5=95=86?= =?UTF-8?q?=E6=88=B7=E7=9B=B8=E5=85=B3=E5=85=B3=E8=81=94=E7=9A=84=E8=A1=A8?= =?UTF-8?q?=E8=AE=A1=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/calculate/tenement.go | 164 +++++++++++++++++++++++++++++++++- service/calculate/wattCost.go | 8 +- 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/service/calculate/tenement.go b/service/calculate/tenement.go index fd13741..9bc9b74 100644 --- a/service/calculate/tenement.go +++ b/service/calculate/tenement.go @@ -270,7 +270,7 @@ func ShiftToAsiaShanghai(t time.Time) time.Time { // 计算各个商户的合计信息,并归总与商户关联的表计记录 func TenementChargeCalculate(tenements []calculate.PrimaryTenementStatistics, - summary calculate.Summary, meters MeterMap, _relations []model.MeterRelation) { + summary calculate.Summary, meters MeterMap) []calculate.TenementCharge { result := make(map[string][]string) for _, t := range tenements { meterCodes := make([]string, 0) @@ -281,6 +281,7 @@ func TenementChargeCalculate(tenements []calculate.PrimaryTenementStatistics, result[t.Tenement.Id] = meterCodes } var Key Key + var tc []calculate.TenementCharge for tCode, meterCodes := range result { relatedMeters := make([]calculate.Meter, 0) for _, code := range meterCodes { @@ -291,6 +292,167 @@ func TenementChargeCalculate(tenements []calculate.PrimaryTenementStatistics, } } // 计算商户的合计电费信息 + var overall model.ConsumptionUnit + var critical model.ConsumptionUnit + var peak model.ConsumptionUnit + var flat model.ConsumptionUnit + var valley model.ConsumptionUnit + var basicPooled decimal.Decimal + var adjustPooled decimal.Decimal + var lossAmount decimal.Decimal + var lossPooled decimal.Decimal + var publicPooled decimal.Decimal + + for _, meter := range relatedMeters { + overall.Amount.Add(meter.Overall.Amount) + overall.Fee.Add(meter.Overall.Fee) + + critical.Amount.Add(meter.Critical.Amount) + critical.Fee.Add(meter.Critical.Fee) + + peak.Amount.Add(meter.Peak.Amount) + peak.Fee.Add(meter.Peak.Fee) + + flat.Amount.Add(meter.Flat.Amount) + flat.Fee.Add(meter.Flat.Fee) + + valley.Amount.Add(meter.Valley.Amount) + valley.Fee.Add(meter.Valley.Fee) + + basicPooled.Add(meter.PooledBasic.Fee) + adjustPooled.Add(meter.PooledAdjust.Fee) + lossAmount.Add(meter.PooledLoss.Amount) + lossPooled.Add(meter.PooledLoss.Fee) + publicPooled.Add(meter.PooledPublic.Fee) + + // 反写商户表计的统计数据 + meter.Overall.Proportion = func() decimal.Decimal { + if overall.Amount.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.Overall.Amount.Div(overall.Amount) + }() + meter.Critical.Proportion = func() decimal.Decimal { + if critical.Amount.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.Critical.Amount.Div(critical.Amount) + }() + meter.Peak.Proportion = func() decimal.Decimal { + if peak.Amount.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.Peak.Amount.Div(peak.Amount) + }() + meter.Flat.Proportion = func() decimal.Decimal { + if flat.Amount.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.Flat.Amount.Div(flat.Amount) + }() + meter.Valley.Proportion = func() decimal.Decimal { + if valley.Amount.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.Valley.Amount.Div(valley.Amount) + }() + meter.PooledBasic.Proportion = func() decimal.Decimal { + if basicPooled.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.PooledBasic.Fee.Div(basicPooled) + }() + meter.PooledAdjust.Proportion = func() decimal.Decimal { + if adjustPooled.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.PooledAdjust.Fee.Div(adjustPooled) + }() + meter.PooledLoss.Proportion = func() decimal.Decimal { + if lossPooled.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.PooledLoss.Fee.Div(lossPooled) + }() + meter.PooledPublic.Proportion = func() decimal.Decimal { + if publicPooled.Equal(decimal.Zero) { + return decimal.Zero + } + return meter.PooledPublic.Fee.Div(publicPooled) + }() + + var OverallProportion decimal.Decimal + if summary.Overall.Amount == decimal.Zero { + OverallProportion = decimal.Zero + } else { + OverallProportion = decimal.NewFromFloat(overall.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()) + } + + var CriticalProportion decimal.Decimal + if summary.Critical.Amount == decimal.Zero { + CriticalProportion = decimal.Zero + } else { + CriticalProportion = decimal.NewFromFloat(critical.Amount.InexactFloat64() / summary.Critical.Amount.InexactFloat64()) + } + + var PeakProportion decimal.Decimal + if summary.Peak.Amount == decimal.Zero { + PeakProportion = decimal.Zero + } else { + PeakProportion = decimal.NewFromFloat(peak.Amount.InexactFloat64() / summary.Peak.Amount.InexactFloat64()) + } + + var FlatProportion decimal.Decimal + if summary.Flat.Amount == decimal.Zero { + FlatProportion = decimal.Zero + } else { + FlatProportion = decimal.NewFromFloat(flat.Amount.InexactFloat64() / summary.Flat.Amount.InexactFloat64()) + } + + var ValleyProportion decimal.Decimal + if summary.Valley.Amount == decimal.Zero { + ValleyProportion = decimal.Zero + } else { + ValleyProportion = decimal.NewFromFloat(valley.Amount.InexactFloat64() / summary.Valley.Amount.InexactFloat64()) + } + + tenementCharge := calculate.TenementCharge{ + Tenement: tCode, + Overall: model.ConsumptionUnit{ + Price: summary.Overall.Price, + Proportion: OverallProportion, + }, + Critical: model.ConsumptionUnit{ + Price: summary.Critical.Price, + Proportion: CriticalProportion, + }, + Peak: model.ConsumptionUnit{ + Price: summary.Overall.Price, + Proportion: PeakProportion, + }, + Flat: model.ConsumptionUnit{ + Price: summary.Overall.Price, + Proportion: FlatProportion, + }, + Valley: model.ConsumptionUnit{ + Price: summary.Overall.Price, + Proportion: ValleyProportion, + }, + BasicFee: basicPooled, + AdjustFee: adjustPooled, + LossPooled: lossPooled, + PublicPooled: publicPooled, + FinalCharges: decimal.NewFromFloat( + overall.Fee.InexactFloat64() + basicPooled.InexactFloat64() + + adjustPooled.InexactFloat64() + lossPooled.InexactFloat64() + + publicPooled.InexactFloat64()), + Submeters: nil, + Poolings: nil, + } + + tc = append(tc, tenementCharge) + } } + return tc } diff --git a/service/calculate/wattCost.go b/service/calculate/wattCost.go index 735c5f6..113eaab 100644 --- a/service/calculate/wattCost.go +++ b/service/calculate/wattCost.go @@ -84,6 +84,12 @@ func MainCalculateProcess(rid string) { return } - fmt.Println(meterRelations, poolingMetersReports, parkMetersReports, parkTotal) + //为获取值初始化一个空的,合并分支时可忽略 + var meters MeterMap + + // 计算商户的合计电费信息,并归总与商户相关联的表计记录 + tenementCharges := TenementChargeCalculate(tenementReports, summary, meters) + + fmt.Println(meterRelations, poolingMetersReports, tenementCharges) }