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/.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/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/controller/god_mode.go b/controller/god_mode.go new file mode 100644 index 0000000..db1a426 --- /dev/null +++ b/controller/god_mode.go @@ -0,0 +1,132 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "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) + 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) + router.Delete("gm/meter", security.SingularityAuthorize, deleteMeters) +} + +//用于将参数转化为切片 +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("指定商户已经删除。") +} + +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("指定园区已经删除。") +} + +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("指定报表已经删除。") +} + +func deleteEnterprise(c *fiber.Ctx) error { + uid := c.Query("uid") + result := response.NewResult(c) + GmLog.Info("[天神模式]删除指定企业用户", zap.String("uid", uid)) + + 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") + 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("删除成功") +} + +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("指定表计公摊关系已经删除。") + +} + +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/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/statistics.go b/controller/statistics.go new file mode 100644 index 0000000..fd0555c --- /dev/null +++ b/controller/statistics.go @@ -0,0 +1,85 @@ +package controller + +import ( + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "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) + router.Get("/stat/reports", security.OPSAuthorize, statReports) + +} + +//获取当前系统中待审核的内容数量 +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}) +} + +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/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/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..f9d6695 --- /dev/null +++ b/controller/withdraw.go @@ -0,0 +1,134 @@ +package controller + +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" +) + +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.Delete("/withdraw/:rid", security.EnterpriseAuthorize, recallReport) +} + +//用于分页检索用户的核算报表 +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)) + //中间数据库操作暂且省略。。。。 + //首先进行核算报表的分页查询 + withdraws, total, err := repository.WithdrawRepository.FindWithdraw(uint(page), &keyword) + if err != nil { + withdrawLog.Error("检索用户核算报表失败。", zap.Error(err)) + return result.Error(http.StatusInternalServerError, err.Error()) + } + //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("审核拒绝撤回报表成功!") + } + } + +} + +//用于撤回电费核算 +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/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/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/park.go b/model/park.go index 9dfd190..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" @@ -31,3 +32,14 @@ type Park struct { LastModifiedAt time.Time `json:"lastModifiedAt"` DeletedAt *time.Time `json:"deletedAt"` } + +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/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/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/model/withdraw.go b/model/withdraw.go new file mode 100644 index 0000000..b1da5f1 --- /dev/null +++ b/model/withdraw.go @@ -0,0 +1,84 @@ +package model + +import ( + "electricity_bill_calc/types" + "github.com/shopspring/decimal" + "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 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/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/god_mode.go b/repository/god_mode.go new file mode 100644 index 0000000..fed5fc8 --- /dev/null +++ b/repository/god_mode.go @@ -0,0 +1,449 @@ +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" +) + +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 +} + +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) 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)) + + 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 +} + +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/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 new file mode 100644 index 0000000..c30bd19 --- /dev/null +++ b/repository/withdraw.go @@ -0,0 +1,242 @@ +package repository + +import ( + "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" + "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"), +} + +//该方法用于分页查询核算报表 +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() + + /** + 如果访问数据库次数过多出现时间过长的话可以用这个尝试优化,未测试的sql语句 + + wd.ds.From(goqu.T("report")). + Where(goqu.I("withdraw").Eq(1)). + 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() + + 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")))). + 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"), + 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"), + ) + + 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 + } + + var withdrawReses []model.Withdraw + //TODO: 2023.07.24对查询到的数据进行拼接(完成) + for _, v := range reports { + Begin := v.Period.SafeLower().Format("2006-01-02") + End := v.Period.SafeUpper().Format("2006-01-02") + + var withdrawRes model.Withdraw + 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: tools.TimeToStringPtr(v.LastWithdrawAuditAt), + Status: 0., + Withdraw: v.Withdraw, + } + 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, + } + + withdrawRes.Report = report + withdrawRes.Park = park + withdrawRes.User = userInfo + withdrawReses = append(withdrawReses, withdrawRes) + } + + 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": model.REPORT_WITHDRAW_GRANTED, + "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": model.REPORT_WITHDRAW_DENIED, + "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/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..e09f7b7 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) @@ -54,6 +54,11 @@ func App() *fiber.App { controller.InitializeTopUpHandlers(app) controller.InitializeReportHandlers(app) + controller.InitializeWithdrawHandlers(app) // 公示撤回 + controller.InitializeFoundationHandlers(app) // 基础数据 + controller.InitializeStatisticsController(app) // 首页信息 + controller.InitializeGmController(app) // 天神模式 + return app } 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..26d5620 --- /dev/null +++ b/service/calculate/summary.go @@ -0,0 +1,113 @@ +package calculate + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/model/calculate" + "errors" + "fmt" + "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 +} + +//计算线损以及调整线损 +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 + //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())) + } + + 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 +} + +// 计算已经启用的商铺面积和 +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/tenement.go b/service/calculate/tenement.go new file mode 100644 index 0000000..1749140 --- /dev/null +++ b/service/calculate/tenement.go @@ -0,0 +1,268 @@ +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) ([]calculate.PrimaryTenementStatistics, 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 []calculate.PrimaryTenementStatistics + + 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 + } + + tenementReports = append(tenementReports, pt) + } + + return tenementReports, 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..fe14ba6 --- /dev/null +++ b/service/calculate/wattCost.go @@ -0,0 +1,84 @@ +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 + } + + // 计算所有表计的总电量 + parkTotal := TotalConsumptionCalculate(tenementReports, 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) + +} diff --git a/service/god_mode.go b/service/god_mode.go new file mode 100644 index 0000000..a493def --- /dev/null +++ b/service/god_mode.go @@ -0,0 +1,232 @@ +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 +} + +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_poolings + err = repository.GMRepository.DeleteMeterPoolings(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 +} + +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 +} + +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 +} + +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.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) + 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 +} + +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 +} + +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)) + tx.Rollback(ctx) + return err + } + return nil +} \ No newline at end of file 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 +} 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/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 +} diff --git a/settings.yaml b/settings.yaml index 4a0e548..9968ebe 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 @@ -21,3 +21,5 @@ Service: ItemsPageSize: 20 CacheLifeTime: 5m HostSerial: 5 +BaselineLineLossRatio: + Base: 基准线损率 \ No newline at end of file diff --git a/tools/utils.go b/tools/utils.go index 6312dd5..325453f 100644 --- a/tools/utils.go +++ b/tools/utils.go @@ -1,9 +1,11 @@ package tools import ( + "database/sql" "encoding/json" "fmt" "strings" + "time" "github.com/mozillazg/go-pinyin" "github.com/samber/lo" @@ -144,3 +146,27 @@ 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 + } +} + +//该方法用于将时间解析为字符串指针 +func TimeToStringPtr(t *time.Time) *string { + if t == nil { + return nil + } + + timeStr := t.Format("2006-01-02 15:04:05") + return &timeStr +} + 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"` +}