diff --git a/controller/maintenance_fee.go b/controller/maintenance_fee.go index db21e75..072bd3d 100644 --- a/controller/maintenance_fee.go +++ b/controller/maintenance_fee.go @@ -6,9 +6,11 @@ import ( "electricity_bill_calc/security" "electricity_bill_calc/service" "net/http" + "strconv" "github.com/gin-gonic/gin" "github.com/jinzhu/copier" + "github.com/samber/lo" "github.com/shopspring/decimal" ) @@ -18,6 +20,7 @@ func InitializeMaintenanceFeeController(router *gin.Engine) { router.PUT("/maintenance/fee/:mid", security.EnterpriseAuthorize, modifyMaintenanceFeeRecord) router.PUT("/maintenance/fee/:mid/enabled", security.EnterpriseAuthorize, changeMaintenanceFeeState) router.DELETE("/maintenance/fee/:mid", security.EnterpriseAuthorize, deleteMaintenanceFee) + router.GET("/additional/charges", security.MustAuthenticated, statAdditionalCharges) } func ensureMaintenanceFeeBelongs(c *gin.Context, result *response.Result, requestMaintenanceFeeId string) bool { @@ -46,34 +49,51 @@ func listMaintenanceFees(c *gin.Context) { return } requestPark := c.DefaultQuery("park", "") + requestPeriod := c.DefaultQuery("period", "") + requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1")) + if err != nil { + result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。") + return + } if len(requestPark) > 0 { if !ensureParkBelongs(c, result, requestPark) { return } - fees, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{requestPark}) + fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{requestPark}, requestPeriod, requestPage) if err != nil { result.Error(http.StatusInternalServerError, err.Error()) return } - result.Json(http.StatusOK, "已获取指定园区下的维护费记录", gin.H{"fees": fees}) + result.Json( + http.StatusOK, + "已获取指定园区下的维护费记录", + response.NewPagedResponse(requestPage, total).ToMap(), + gin.H{"fees": fees}, + ) } else { parkIds, err := service.ParkService.AllParkIds(userSession.Uid) if err != nil { result.Error(http.StatusInternalServerError, err.Error()) return } - fees, err := service.MaintenanceFeeService.ListMaintenanceFees(parkIds) + fees, total, err := service.MaintenanceFeeService.ListMaintenanceFees(parkIds, requestPeriod, requestPage) if err != nil { result.Error(http.StatusInternalServerError, err.Error()) return } - result.Json(http.StatusOK, "已获取指定用户下的所有维护费记录。", gin.H{"fees": fees}) + result.Json( + http.StatusOK, + "已获取指定用户下的所有维护费记录。", + response.NewPagedResponse(requestPage, total).ToMap(), + gin.H{"fees": fees}, + ) } } type _FeeCreationFormData struct { ParkId string `json:"parkId" form:"parkId"` Name string `json:"name" form:"name"` + Period string `json:"period" form:"period"` Fee decimal.Decimal `json:"fee" form:"fee"` Memo *string `json:"memo" form:"memo"` } @@ -152,3 +172,38 @@ func deleteMaintenanceFee(c *gin.Context) { } result.Deleted("指定维护费条目已删除。") } + +func statAdditionalCharges(c *gin.Context) { + result := response.NewResult(c) + session, err := _retreiveSession(c) + if err != nil { + result.Unauthorized(err.Error()) + return + } + requestUser := lo. + If(session.Type == model.USER_TYPE_ENT, session.Uid). + Else(c.DefaultQuery("user", "")) + requestPark := c.DefaultQuery("park", "") + if len(requestPark) > 0 && session.Type == model.USER_TYPE_ENT { + if !ensureParkBelongs(c, result, requestPark) { + return + } + } + period := c.DefaultQuery("period", "") + keyword := c.DefaultQuery("keyword", "") + requestPage, err := strconv.Atoi(c.DefaultQuery("page", "1")) + if err != nil { + result.Error(http.StatusInternalServerError, "不能解析给定的参数[page]。") + return + } + fees, total, err := service.MaintenanceFeeService.QueryAdditiionalCharges(requestUser, requestPark, period, keyword, requestPage) + if err != nil { + result.Error(http.StatusInternalServerError, err.Error()) + return + } + result.Success( + "已经成功获取到物业附加费的统计记录。", + response.NewPagedResponse(requestPage, total).ToMap(), + gin.H{"charges": fees}, + ) +} diff --git a/controller/report.go b/controller/report.go index 00622fb..63a91ca 100644 --- a/controller/report.go +++ b/controller/report.go @@ -11,7 +11,6 @@ import ( "strconv" "time" - "github.com/fufuok/utils" "github.com/gin-gonic/gin" "github.com/jinzhu/copier" "github.com/samber/lo" @@ -26,12 +25,6 @@ func InitializeReportController(router *gin.Engine) { router.PUT("/report/:rid/summary", security.EnterpriseAuthorize, fillReportSummary) router.GET("/report/:rid/summary/calculate", security.EnterpriseAuthorize, testCalculateReportSummary) router.POST("/report/:rid/summary/calculate", security.EnterpriseAuthorize, progressReportSummary) - router.GET("/report/:rid/maintenance", security.EnterpriseAuthorize, fetchWillDilutedFees) - router.POST("/report/:rid/maintenance", security.EnterpriseAuthorize, createTemporaryWillDilutedFee) - router.POST("/report/:rid/maintenance/import", security.EnterpriseAuthorize, importPredefinedMaintenanceFees) - router.PUT("/report/:rid/maintenance/:mid", security.EnterpriseAuthorize, modifyWillDilutedFee) - router.DELETE("/report/:rid/maintenance/:mid", security.EnterpriseAuthorize, deleteTemporaryWillDilutedFee) - router.PUT("/report/:rid/step/diluted/fees", security.EnterpriseAuthorize, progressReportWillDilutedFee) router.PUT("/report/:rid/step/meter/register", security.EnterpriseAuthorize, progressEndUserRegister) router.POST("/report/:rid/publish", security.EnterpriseAuthorize, publishReport) router.GET("/reports", security.MustAuthenticated, searchReports) @@ -208,161 +201,6 @@ func progressReportSummary(c *gin.Context) { result.Success("已经完成园区概况的计算,并可以进行到下一步骤。") } -func fetchWillDilutedFees(c *gin.Context) { - result := response.NewResult(c) - requestReportId := c.Param("rid") - if !ensureReportBelongs(c, result, requestReportId) { - return - } - fees, err := service.ReportService.FetchWillDulutedMaintenanceFees(requestReportId) - if err != nil { - result.NotFound(err.Error()) - return - } - result.Json(http.StatusOK, "待摊薄费用已经获取到。", gin.H{"fees": fees}) -} - -type DilutedFeeCreationFormData struct { - ParkId string `json:"parkId" form:"parkId"` - Name string `json:"name" form:"name"` - Fee decimal.Decimal `json:"fee" form:"fee"` - Memo *string `json:"memo" form:"memo"` -} - -func createTemporaryWillDilutedFee(c *gin.Context) { - result := response.NewResult(c) - requestReportId := c.Param("rid") - if !ensureReportBelongs(c, result, requestReportId) { - return - } - formData := new(DilutedFeeCreationFormData) - c.BindJSON(formData) - report, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - result.NotFound(err.Error()) - return - } - if formData.ParkId != report.ParkId { - result.NotAccept("选择的园区与公示报表所属的园区不一致。") - return - } - newWillDilutedFee := new(model.WillDilutedFee) - copier.Copy(newWillDilutedFee, formData) - newWillDilutedFee.ReportId = report.Id - err = service.ReportService.CreateTemporaryWillDilutedMaintenanceFee(*newWillDilutedFee) - if err != nil { - result.Error(http.StatusInternalServerError, err.Error()) - return - } - result.Created("公示报表中所要使用的临时待摊薄费用已添加。") -} - -func importPredefinedMaintenanceFees(c *gin.Context) { - result := response.NewResult(c) - requestReportId := c.Param("rid") - if !ensureReportBelongs(c, result, requestReportId) { - return - } - report, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - result.NotFound(err.Error()) - return - } - maintenanceFees, err := service.MaintenanceFeeService.ListMaintenanceFees([]string{report.ParkId}) - if err != nil { - result.Error(http.StatusInternalServerError, err.Error()) - return - } - enabledMaintenanceFees := lo.Filter( - maintenanceFees, - func(elem model.MaintenanceFee, index int) bool { - return elem.Enabled - }, - ) - if len(enabledMaintenanceFees) == 0 { - result.NotFound("没有找到可供导入的配电维护费记录。") - return - } - dilutedFees := lo.Map( - enabledMaintenanceFees, - func(elem model.MaintenanceFee, index int) model.WillDilutedFee { - fee := &model.WillDilutedFee{ - Id: utils.UUIDString(), - ReportId: report.Id, - SourceId: lo.ToPtr(elem.Id), - Name: elem.Name, - Fee: elem.Fee, - Memo: elem.Memo, - } - return *fee - }, - ) - err = service.ReportService.BatchSaveMaintenanceFee(report.Id, dilutedFees) - if err != nil { - result.Error(http.StatusInternalServerError, err.Error()) - return - } - result.Created("预定义的配电维护费已经导入。") -} - -type DilutedFeeModificationFormData struct { - Name *string `json:"name,omitempty" form:"name"` - Fee decimal.Decimal `json:"fee" form:"fee"` - Memo *string `json:"memo,omitempty" form:"memo"` -} - -func modifyWillDilutedFee(c *gin.Context) { - result := response.NewResult(c) - requestReportId := c.Param("rid") - if !ensureReportBelongs(c, result, requestReportId) { - return - } - requestFeeId := c.Param("mid") - formData := new(DilutedFeeModificationFormData) - c.BindJSON(formData) - updateValues := tools.ConvertStructToMap(formData) - err := service.ReportService.UpdateMaintenanceFee(requestFeeId, updateValues) - if err != nil { - result.Error(http.StatusInternalServerError, err.Error()) - return - } - result.Updated("指定待摊薄费用信息已经更新。") -} - -func deleteTemporaryWillDilutedFee(c *gin.Context) { - result := response.NewResult(c) - requestReportId := c.Param("rid") - if !ensureReportBelongs(c, result, requestReportId) { - return - } - requestFeeId := c.Param("mid") - err := service.ReportService.DeleteWillDilutedFee(requestFeeId) - if err != nil { - result.Error(http.StatusInternalServerError, err.Error()) - return - } - result.Deleted("指定待摊薄费用信息已经删除。") -} - -func progressReportWillDilutedFee(c *gin.Context) { - result := response.NewResult(c) - requestReportId := c.Param("rid") - if !ensureReportBelongs(c, result, requestReportId) { - return - } - report, err := service.ReportService.RetreiveReportIndex(requestReportId) - if err != nil { - result.NotFound(err.Error()) - return - } - err = service.ReportService.ProgressReportWillDilutedFee(*report) - if err != nil { - result.Error(http.StatusInternalServerError, err.Error()) - return - } - result.Success("待摊薄费用编辑步骤已经完成。") -} - func progressEndUserRegister(c *gin.Context) { result := response.NewResult(c) requestReportId := c.Param("rid") diff --git a/model/maintenance_fee.go b/model/maintenance_fee.go index d917047..47dcb8a 100644 --- a/model/maintenance_fee.go +++ b/model/maintenance_fee.go @@ -19,6 +19,7 @@ type MaintenanceFee struct { Fee decimal.Decimal `bun:"type:numeric,notnull" json:"fee"` Memo *string `bun:"type:text" json:"memo"` Enabled bool `bun:",notnull" json:"enabled"` + Park Park `bun:"rel:belongs-to,join:park_id=id"` } var _ bun.BeforeAppendModelHook = (*MaintenanceFee)(nil) @@ -34,3 +35,14 @@ func (f *MaintenanceFee) BeforeAppendModel(ctx context.Context, query bun.Query) } return nil } + +type AdditionalCharge struct { + ParkId string `json:"parkId"` + Period string `json:"period"` + Fee decimal.Decimal `json:"fee"` + Price decimal.Decimal `json:"price"` + QuarterPrice decimal.Decimal `json:"quarterPrice"` + SemiAnnualPrice decimal.Decimal `json:"semiAnnualPrice"` + Enterprise UserDetailSimplified `json:"user"` + Park Park `json:"park"` +} diff --git a/service/maintenance_fee.go b/service/maintenance_fee.go index 22ea876..2247542 100644 --- a/service/maintenance_fee.go +++ b/service/maintenance_fee.go @@ -2,14 +2,17 @@ package service import ( "electricity_bill_calc/cache" + "electricity_bill_calc/config" "electricity_bill_calc/exceptions" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" "fmt" + mapset "github.com/deckarep/golang-set/v2" "github.com/google/uuid" "github.com/samber/lo" + "github.com/shopspring/decimal" "github.com/uptrace/bun" "go.uber.org/zap" ) @@ -22,9 +25,14 @@ var MaintenanceFeeService = _MaintenanceFeeService{ l: logger.Named("Service", "maintenance"), } -func (_MaintenanceFeeService) ListMaintenanceFees(pid []string) ([]model.MaintenanceFee, error) { - if fees, _ := cache.RetreiveSearch[[]model.MaintenanceFee]("maintenance_fee", pid...); fees != nil { - return *fees, nil +func (_MaintenanceFeeService) ListMaintenanceFees(pid []string, period string, requestPage int) ([]model.MaintenanceFee, int64, error) { + conditions := []string{fmt.Sprintf("%d", requestPage)} + conditions = append(conditions, pid...) + conditions = append(conditions, period) + if cachedTotal, err := cache.RetreiveCount("maintenance_fee", conditions...); cachedTotal != -1 && err == nil { + if fees, _ := cache.RetreiveSearch[[]model.MaintenanceFee]("maintenance_fee", conditions...); fees != nil { + return *fees, cachedTotal, nil + } } var ( @@ -34,23 +42,30 @@ func (_MaintenanceFeeService) ListMaintenanceFees(pid []string) ([]model.Mainten if len(pid) > 0 { cond = cond.Where("park_id in (?)", bun.In(pid)) } else { - return make([]model.MaintenanceFee, 0), exceptions.NewIllegalArgumentsError("必须给定所要请求的至少一个园区", "park_id") + return make([]model.MaintenanceFee, 0), 0, exceptions.NewIllegalArgumentsError("必须给定所要请求的至少一个园区", "park_id") + } + if len(period) > 0 { + cond = cond.Where("period = ?", period) } ctx, cancel := global.TimeoutContext() defer cancel() - - err := cond.Order("created_at desc").Scan(ctx) + startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize + total, err := cond.Order("period desc", "created_at desc"). + Limit(config.ServiceSettings.ItemsPageSize). + Offset(startItem). + ScanAndCount(ctx) if err != nil { - return make([]model.MaintenanceFee, 0), err + return make([]model.MaintenanceFee, 0), 0, fmt.Errorf("附加费查询出现错误,%w", err) } relations := lo.Map(fees, func(f model.MaintenanceFee, _ int) string { return fmt.Sprintf("maintenance_fee:%s", f.Id) }) relations = append(relations, "maintenance_fee", "park") - cache.CacheSearch(fees, relations, "maintenance_fee", pid...) - return fees, nil + cache.CacheCount(relations, "maintenance_fee", int64(total), conditions...) + cache.CacheSearch(fees, relations, "maintenance_fee", conditions...) + return fees, int64(total), nil } func (_MaintenanceFeeService) CreateMaintenanceFeeRecord(fee model.MaintenanceFee) error { @@ -77,7 +92,7 @@ func (_MaintenanceFeeService) ModifyMaintenanceFee(fee model.MaintenanceFee) err Exec(ctx) if err != nil { if rows, _ := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到匹配的维护费记录。") + return exceptions.NewNotFoundError("未能找到匹配的附加费记录。") } else { return err } @@ -96,7 +111,7 @@ func (_MaintenanceFeeService) ChangeMaintenanceFeeState(fid string, state bool) Exec(ctx) if err != nil { if rows, err := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到匹配的维护费记录。") + return exceptions.NewNotFoundError("未能找到匹配的附加费记录。") } else { return err } @@ -114,7 +129,7 @@ func (_MaintenanceFeeService) DeleteMaintenanceFee(fid string) error { Exec(ctx) if err != nil { if rows, err := res.RowsAffected(); rows == 0 { - return exceptions.NewNotFoundError("未能找到匹配的维护费记录。") + return exceptions.NewNotFoundError("未能找到匹配的附加费记录。") } else { return err } @@ -148,8 +163,130 @@ func (_MaintenanceFeeService) EnsureFeeBelongs(uid, mid string) (bool, error) { return acc || false }, false) if !exists { - return false, exceptions.NewNotFoundError("指定维护费所属园区未找到。") + return false, exceptions.NewNotFoundError("指定附加费所属园区未找到。") } cache.CacheExists([]string{fmt.Sprintf("maintenance_fee:%s", mid), "maintenance_fee", "park"}, "maintenance_fee", mid, uid) return exists, nil } + +type _FeeStat struct { + ParkId string + Period string + Total decimal.Decimal +} + +func (f _MaintenanceFeeService) QueryAdditiionalCharges(uid, pid, period, keyword string, requestPage int) ([]model.AdditionalCharge, int64, error) { + var ( + conditions = []string{fmt.Sprintf("%d", requestPage)} + statFees = make([]_FeeStat, 0) + cond = global.DB.NewSelect(). + Model((*model.MaintenanceFee)(nil)). + Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.ExcludeColumn("*") + }). + Relation("Park.Enterprise", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.ExcludeColumn("*") + }). + Where("m.enabled = ?", true) + ) + if len(uid) > 0 { + cond = cond.Where("park__enterprise.id = ?", uid) + conditions = append(conditions, uid) + } else { + conditions = append(conditions, "_") + } + if len(pid) > 0 { + cond = cond.Where("park.id = ?", pid) + conditions = append(conditions, pid) + } else { + conditions = append(conditions, "_") + } + if len(period) > 0 { + cond = cond.Where("m.period = ?", period) + conditions = append(conditions, period) + } else { + conditions = append(conditions, "_") + } + if len(keyword) > 0 { + keywordCond := "%" + keyword + "%" + cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Where("park__enterprise.name like ?", keywordCond). + WhereOr("park__enterprise.abbr like ?", keywordCond). + WhereOr("park.name like ?", keywordCond). + WhereOr("park.abbr like ?", keywordCond). + WhereOr("park.address like ?", keywordCond) + }) + conditions = append(conditions, keyword) + } else { + conditions = append(conditions, "_") + } + + if cachedTotal, err := cache.RetreiveCount("additional_charge", conditions...); cachedTotal != -1 && err == nil { + if cachedData, _ := cache.RetreiveSearch[[]model.AdditionalCharge]("additional_charge", conditions...); cachedData != nil { + return *cachedData, cachedTotal, nil + } + } + ctx, cancel := global.TimeoutContext(24) + defer cancel() + + startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize + + total, err := cond.ColumnExpr("sum(?) as total", bun.Ident("fee")). + Column("park_id", "period"). + Group("park_id", "period"). + Order("period desc"). + Limit(config.ServiceSettings.ItemsPageSize). + Offset(startItem). + ScanAndCount(ctx, &statFees) + if err != nil { + return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取附加费统计信息出现错误,%w", err) + } + parkIds := lo.Reduce( + statFees, + func(acc mapset.Set[string], elem _FeeStat, _ int) mapset.Set[string] { + acc.Add(elem.ParkId) + return acc + }, + mapset.NewSet[string](), + ) + parks := make([]model.Park, 0) + err = global.DB.NewSelect().Model(&parks).Relation("Enterprise"). + Where("p.id in (?)", bun.In(parkIds.ToSlice())). + Scan(ctx) + if err != nil { + return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取园区信息出现错误,%w", err) + } + f.l.Debug("Park ids", zap.Any("ids", parkIds)) + f.l.Debug("Check fees", zap.Any("fees", statFees)) + assembledStat := lo.Reduce( + statFees, + func(acc []model.AdditionalCharge, elem _FeeStat, _ int) []model.AdditionalCharge { + park, has := lo.Find(parks, func(p model.Park) bool { + return p.Id == elem.ParkId + }) + f.l.Debug("Park detection.", zap.Bool("has", has), zap.Any("park", park)) + if has { + if !park.Area.Valid || park.Area.Decimal.Equal(decimal.Zero) { + return acc + } + price := elem.Total.Div(park.Area.Decimal).RoundBank(8) + return append(acc, model.AdditionalCharge{ + ParkId: elem.ParkId, + Period: elem.Period, + Fee: elem.Total, + Price: price, + QuarterPrice: price.Div(decimal.NewFromInt(4)), + SemiAnnualPrice: price.Div(decimal.NewFromInt(2)), + Enterprise: model.FromUserDetail(*park.Enterprise), + Park: park, + }) + } else { + return acc + } + }, + make([]model.AdditionalCharge, 0), + ) + cache.CacheCount([]string{"maintenance_fee"}, "additional_charge", int64(total), conditions...) + cache.CacheSearch(assembledStat, []string{"maintenance_fee"}, "additional_charge", conditions...) + return assembledStat, int64(total), nil +}