forked from free-lancers/electricity_bill_calc_service
		
	enhance(report):基本完成报表查询部分的综合服务部分。
This commit is contained in:
		
							
								
								
									
										70
									
								
								repository/calculate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								repository/calculate.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"electricity_bill_calc/global" | ||||
| 	"electricity_bill_calc/logger" | ||||
| 	"electricity_bill_calc/model" | ||||
| 	"electricity_bill_calc/types" | ||||
|  | ||||
| 	"github.com/doug-martin/goqu/v9" | ||||
| 	_ "github.com/doug-martin/goqu/v9/dialect/postgres" | ||||
| 	"github.com/georgysavva/scany/v2/pgxscan" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
|  | ||||
| type _CalculateRepository struct { | ||||
| 	log *zap.Logger | ||||
| 	ds  goqu.DialectWrapper | ||||
| } | ||||
|  | ||||
| var CalculateRepository = _CalculateRepository{ | ||||
| 	log: logger.Named("Repository", "Calculate"), | ||||
| 	ds:  goqu.Dialect("postgres"), | ||||
| } | ||||
|  | ||||
| // 获取当前正在等待计算的核算任务ID列表 | ||||
| func (cr _CalculateRepository) ListPendingTasks() ([]string, error) { | ||||
| 	cr.log.Info("获取当前正在等待计算的核算任务ID列表") | ||||
| 	ctx, cancel := global.TimeoutContext() | ||||
| 	defer cancel() | ||||
|  | ||||
| 	var ids []string | ||||
| 	querySql, queryArgs, _ := cr.ds. | ||||
| 		From("report_task"). | ||||
| 		Select("id"). | ||||
| 		Where(goqu.C("status").Eq(model.REPORT_CALCULATE_TASK_STATUS_PENDING)). | ||||
| 		Prepared(true).ToSQL() | ||||
| 	if err := pgxscan.Select(ctx, global.DB, &ids, querySql, queryArgs...); err != nil { | ||||
| 		cr.log.Error("未能获取到当前正在等待计算的核算任务ID列表", zap.Error(err)) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return ids, nil | ||||
| } | ||||
|  | ||||
| // 更新指定报表的核算状态 | ||||
| func (cr _CalculateRepository) UpdateReportTaskStatus(rid string, status int16, message *string) (bool, error) { | ||||
| 	cr.log.Info("更新指定报表的核算状态", zap.String("Report", rid), zap.Int16("Status", status)) | ||||
| 	ctx, cancel := global.TimeoutContext() | ||||
| 	defer cancel() | ||||
|  | ||||
| 	currentTime := types.Now() | ||||
| 	updateSql, updateArgs, _ := cr.ds. | ||||
| 		Update("report_task"). | ||||
| 		Set(goqu.Record{ | ||||
| 			"status":           status, | ||||
| 			"last_modified_at": currentTime, | ||||
| 			"message":          message, | ||||
| 		}). | ||||
| 		Where(goqu.C("id").Eq(rid)). | ||||
| 		Prepared(true).ToSQL() | ||||
| 	res, err := global.DB.Exec(ctx, updateSql, updateArgs...) | ||||
| 	if err != nil { | ||||
| 		cr.log.Error("未能更新指定报表的核算状态", zap.Error(err)) | ||||
| 		return false, err | ||||
| 	} | ||||
| 	if res.RowsAffected() == 0 { | ||||
| 		cr.log.Warn("未能保存指定报表的核算状态", zap.String("Report", rid)) | ||||
| 		return false, nil | ||||
| 	} | ||||
| 	return res.RowsAffected() > 0, nil | ||||
| } | ||||
							
								
								
									
										198
									
								
								service/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								service/report.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"electricity_bill_calc/exceptions" | ||||
| 	"electricity_bill_calc/logger" | ||||
| 	"electricity_bill_calc/model" | ||||
| 	"electricity_bill_calc/repository" | ||||
| 	"electricity_bill_calc/types" | ||||
| 	"electricity_bill_calc/vo" | ||||
|  | ||||
| 	"github.com/doug-martin/goqu/v9" | ||||
| 	_ "github.com/doug-martin/goqu/v9/dialect/postgres" | ||||
| 	"github.com/jinzhu/copier" | ||||
| 	"github.com/samber/lo" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
|  | ||||
| type _ReportService struct { | ||||
| 	log *zap.Logger | ||||
| 	ds  goqu.DialectWrapper | ||||
| } | ||||
|  | ||||
| var ReportService = _ReportService{ | ||||
| 	log: logger.Named("Service", "Report"), | ||||
| 	ds:  goqu.Dialect("postgres"), | ||||
| } | ||||
|  | ||||
| // 将指定报表列入计算任务 | ||||
| func (rs _ReportService) DispatchReportCalculate(rid string) error { | ||||
| 	rs.log.Info("将指定报表列入计算任务", zap.String("Report", rid)) | ||||
| 	_, err := repository.CalculateRepository.UpdateReportTaskStatus(rid, model.REPORT_CALCULATE_TASK_STATUS_PENDING, nil) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能将指定报表列入计算任务", zap.Error(err)) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 列出指定用户下的所有尚未发布的报表索引 | ||||
| func (rs _ReportService) ListDraftReportIndicies(uid string) ([]*vo.ReportIndexQueryResponse, error) { | ||||
| 	rs.log.Info("列出指定用户下的所有尚未发布的报表", zap.String("User", uid)) | ||||
| 	indicies, err := repository.ReportRepository.ListDraftReportIndicies(uid) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能获取指定用户下所有未发布报表的索引", zap.Error(err)) | ||||
| 		return make([]*vo.ReportIndexQueryResponse, 0), err | ||||
| 	} | ||||
| 	parkIds := lo.Map(indicies, func(elem *model.ReportIndex, _ int) string { | ||||
| 		return elem.Park | ||||
| 	}) | ||||
| 	parks, err := repository.ParkRepository.RetrieveParks(parkIds) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能获取到相应报表对应的园区详细信息", zap.Error(err)) | ||||
| 		return make([]*vo.ReportIndexQueryResponse, 0), err | ||||
| 	} | ||||
| 	assembled := lo.Reduce(indicies, func(acc []*vo.ReportIndexQueryResponse, elem *model.ReportIndex, _ int) []*vo.ReportIndexQueryResponse { | ||||
| 		park, _ := lo.Find(parks, func(park *model.Park) bool { | ||||
| 			return park.Id == elem.Park | ||||
| 		}) | ||||
| 		var ( | ||||
| 			simplifiedPark   vo.SimplifiedParkDetail | ||||
| 			simplifiedReport vo.SimplifiedReportIndex | ||||
| 		) | ||||
| 		copier.Copy(&simplifiedPark, park) | ||||
| 		copier.Copy(&simplifiedReport, elem) | ||||
| 		acc = append(acc, &vo.ReportIndexQueryResponse{ | ||||
| 			Park:   simplifiedPark, | ||||
| 			Report: lo.ToPtr(simplifiedReport), | ||||
| 		}) | ||||
| 		return acc | ||||
| 	}, make([]*vo.ReportIndexQueryResponse, 0)) | ||||
| 	return assembled, nil | ||||
| } | ||||
|  | ||||
| // 获取指定报表中的包含索引、园区以及用户信息的详细信息 | ||||
| func (rs _ReportService) RetrieveReportIndexDetail(rid string) (*model.UserDetail, *model.Park, *model.ReportIndex, error) { | ||||
| 	index, err := repository.ReportRepository.GetReportIndex(rid) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能获取到指定报表的索引", zap.Error(err)) | ||||
| 		return nil, nil, nil, exceptions.NewNotFoundErrorFromError("未能获取到指定报表的索引", err) | ||||
| 	} | ||||
| 	park, err := repository.ParkRepository.RetrieveParkDetail(index.Park) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能获取到指定报表对应的园区详细信息", zap.Error(err)) | ||||
| 		return nil, nil, nil, exceptions.NewNotFoundErrorFromError("未能获取到指定报表对应的园区详细信息", err) | ||||
| 	} | ||||
| 	user, err := repository.UserRepository.FindUserDetailById(park.UserId) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能获取到指定报表对应的用户详细信息", zap.Error(err)) | ||||
| 		return nil, nil, nil, exceptions.NewNotFoundErrorFromError("未能获取到指定报表对应的用户详细信息", err) | ||||
| 	} | ||||
| 	return user, park, index, nil | ||||
| } | ||||
|  | ||||
| // 根据给定的园区ID列表,查询园区以及用户的详细信息 | ||||
| func (rs _ReportService) queryParkAndUserDetails(pids []string) ([]*model.Park, []*model.UserDetail, error) { | ||||
| 	parks, err := repository.ParkRepository.RetrieveParks(pids) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能获取到相应报表对应的园区详细信息", zap.Error(err)) | ||||
| 		return make([]*model.Park, 0), make([]*model.UserDetail, 0), exceptions.NewNotFoundErrorFromError("未能获取到相应报表对应的园区详细信息", err) | ||||
| 	} | ||||
| 	userIds := lo.Map(parks, func(elem *model.Park, _ int) string { | ||||
| 		return elem.UserId | ||||
| 	}) | ||||
| 	users, err := repository.UserRepository.RetrieveUsersDetail(userIds) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能获取到相应报表对应的用户详细信息", zap.Error(err)) | ||||
| 		return make([]*model.Park, 0), make([]*model.UserDetail, 0), exceptions.NewNotFoundErrorFromError("未能获取到相应报表对应的用户详细信息", err) | ||||
| 	} | ||||
| 	return parks, users, nil | ||||
| } | ||||
|  | ||||
| // 查询指定的核算报表列表 | ||||
| func (rs _ReportService) QueryReports(uid, pid *string, page uint, keyword *string, periodBegin, periodEnd *types.Date) ([]*vo.ComprehensiveReportQueryResponse, int64, error) { | ||||
| 	rs.log.Info("查询指定的核算报表列表", zap.Stringp("User", uid), zap.Stringp("Park", pid), zap.Uint("Page", page), zap.Stringp("Keyword", keyword), logger.DateFieldp("PeriodBegin", periodBegin), logger.DateFieldp("PeriodEnd", periodEnd)) | ||||
| 	reports, total, err := repository.ReportRepository.ComprehensiveReportSearch(uid, pid, page, keyword, periodBegin, periodEnd) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能查询到指定的核算报表列表", zap.Error(err)) | ||||
| 		return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err | ||||
| 	} | ||||
| 	parkIds := lo.Map(reports, func(elem *model.ReportIndex, _ int) string { | ||||
| 		return elem.Park | ||||
| 	}) | ||||
| 	parks, users, err := rs.queryParkAndUserDetails(parkIds) | ||||
| 	if err != nil { | ||||
| 		return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err | ||||
| 	} | ||||
| 	assembled := lo.Reduce( | ||||
| 		reports, | ||||
| 		func(acc []*vo.ComprehensiveReportQueryResponse, elem *model.ReportIndex, _ int) []*vo.ComprehensiveReportQueryResponse { | ||||
| 			park, _ := lo.Find(parks, func(park *model.Park) bool { | ||||
| 				return park.Id == elem.Park | ||||
| 			}) | ||||
| 			user, _ := lo.Find(users, func(user *model.UserDetail) bool { | ||||
| 				return user.Id == park.UserId | ||||
| 			}) | ||||
| 			var ( | ||||
| 				simplifiedUser   vo.SimplifiedUserDetail | ||||
| 				simplifiedPark   vo.SimplifiedParkDetail | ||||
| 				simplifiedReport vo.SimplifiedReportIndex | ||||
| 			) | ||||
| 			copier.Copy(&simplifiedUser, user) | ||||
| 			copier.Copy(&simplifiedPark, park) | ||||
| 			copier.Copy(&simplifiedReport, elem) | ||||
| 			acc = append(acc, &vo.ComprehensiveReportQueryResponse{ | ||||
| 				User:   simplifiedUser, | ||||
| 				Park:   simplifiedPark, | ||||
| 				Report: simplifiedReport, | ||||
| 			}) | ||||
| 			return acc | ||||
| 		}, | ||||
| 		make([]*vo.ComprehensiveReportQueryResponse, 0), | ||||
| 	) | ||||
| 	return assembled, total, nil | ||||
| } | ||||
|  | ||||
| // 查询当前待审核的核算报表撤回申请列表 | ||||
| func (rs _ReportService) ListWithdrawalRequests(page uint, keyword *string) ([]*vo.ComprehensiveReportQueryResponse, int64, error) { | ||||
| 	rs.log.Info("查询当前待审核的核算报表撤回申请列表", zap.Uint("Page", page), zap.Stringp("Keyword", keyword)) | ||||
| 	reports, total, err := repository.ReportRepository.ListWithdrawAppliedReports(page, keyword) | ||||
| 	if err != nil { | ||||
| 		rs.log.Error("未能查询到当前待审核的核算报表撤回申请列表", zap.Error(err)) | ||||
| 		return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err | ||||
| 	} | ||||
| 	parkIds := lo.Map(reports, func(elem *model.ReportIndex, _ int) string { | ||||
| 		return elem.Park | ||||
| 	}) | ||||
| 	parks, users, err := rs.queryParkAndUserDetails(parkIds) | ||||
| 	if err != nil { | ||||
| 		return make([]*vo.ComprehensiveReportQueryResponse, 0), 0, err | ||||
| 	} | ||||
| 	assembled := lo.Reduce( | ||||
| 		reports, | ||||
| 		func(acc []*vo.ComprehensiveReportQueryResponse, elem *model.ReportIndex, _ int) []*vo.ComprehensiveReportQueryResponse { | ||||
| 			park, _ := lo.Find(parks, func(park *model.Park) bool { | ||||
| 				return park.Id == elem.Park | ||||
| 			}) | ||||
| 			user, _ := lo.Find(users, func(user *model.UserDetail) bool { | ||||
| 				return user.Id == park.UserId | ||||
| 			}) | ||||
| 			var ( | ||||
| 				simplifiedUser   vo.SimplifiedUserDetail | ||||
| 				simplifiedPark   vo.SimplifiedParkDetail | ||||
| 				simplifiedReport vo.SimplifiedReportIndex | ||||
| 			) | ||||
| 			copier.Copy(&simplifiedUser, user) | ||||
| 			copier.Copy(&simplifiedPark, park) | ||||
| 			copier.Copy(&simplifiedReport, elem) | ||||
| 			acc = append(acc, &vo.ComprehensiveReportQueryResponse{ | ||||
| 				User:   simplifiedUser, | ||||
| 				Park:   simplifiedPark, | ||||
| 				Report: simplifiedReport, | ||||
| 			}) | ||||
| 			return acc | ||||
| 		}, | ||||
| 		make([]*vo.ComprehensiveReportQueryResponse, 0), | ||||
| 	) | ||||
| 	return assembled, total, nil | ||||
| } | ||||
| @@ -135,3 +135,12 @@ func EmptyToNil(val string) *string { | ||||
| 	} | ||||
| 	return &val | ||||
| } | ||||
|  | ||||
| // 将一个`decimal.NullDecimal`类型的值转换为字符串指针,并且在转换的过程中设定其展示位数,默认使用银行进位法。 | ||||
| func NullDecimalToString(d decimal.NullDecimal, precision ...int32) *string { | ||||
| 	precision = append(precision, 2) | ||||
| 	if !d.Valid { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return lo.ToPtr(d.Decimal.StringFixedBank(precision[0])) | ||||
| } | ||||
|   | ||||
| @@ -112,3 +112,25 @@ func (dr *DateRange) SetUpperUnbounded() { | ||||
| 	dr.Range.Upper = MaxDate() | ||||
| 	dr.Range.UpperType = pgtype.Unbounded | ||||
| } | ||||
|  | ||||
| func (dr DateRange) SafeUpper() Date { | ||||
| 	switch dr.Range.UpperType { | ||||
| 	case pgtype.Inclusive: | ||||
| 		return dr.Range.Upper | ||||
| 	case pgtype.Exclusive: | ||||
| 		return Date{dr.Range.Upper.AddDate(0, 0, -1)} | ||||
| 	default: | ||||
| 		return MaxDate() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (dr DateRange) SafeLower() Date { | ||||
| 	switch dr.Range.LowerType { | ||||
| 	case pgtype.Inclusive: | ||||
| 		return dr.Range.Lower | ||||
| 	case pgtype.Exclusive: | ||||
| 		return Date{dr.Range.Lower.AddDate(0, 0, 1)} | ||||
| 	default: | ||||
| 		return MinDate() | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										29
									
								
								vo/park.go
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								vo/park.go
									
									
									
									
									
								
							| @@ -3,6 +3,8 @@ package vo | ||||
| import ( | ||||
| 	"electricity_bill_calc/model" | ||||
| 	"electricity_bill_calc/tools" | ||||
|  | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| type ParkInformationForm struct { | ||||
| @@ -65,3 +67,30 @@ type ParkBuildingInformationForm struct { | ||||
| 	Name   string `json:"name"` | ||||
| 	Floors string `json:"floors"` | ||||
| } | ||||
|  | ||||
| type SimplifiedParkDetail struct { | ||||
| 	Id          string  `json:"id"` | ||||
| 	UserId      string  `json:"user_id"` | ||||
| 	Name        string  `json:"name"` | ||||
| 	TenementStr *string `json:"tenement"` | ||||
| 	AreaStr     *string `json:"area"` | ||||
| 	CapacityStr *string `json:"capacity"` | ||||
| 	Category    int16   `json:"category"` | ||||
| 	MeterType   int16   `json:"meter04kvType"` | ||||
| 	Region      *string `json:"region"` | ||||
| 	Address     *string `json:"address"` | ||||
| 	Contact     *string `json:"contact"` | ||||
| 	Phone       *string `json:"phone"` | ||||
| } | ||||
|  | ||||
| func (spd *SimplifiedParkDetail) TenementQuantity(tq decimal.NullDecimal) { | ||||
| 	spd.TenementStr = tools.NullDecimalToString(tq) | ||||
| } | ||||
|  | ||||
| func (spd *SimplifiedParkDetail) Area(area decimal.NullDecimal) { | ||||
| 	spd.AreaStr = tools.NullDecimalToString(area) | ||||
| } | ||||
|  | ||||
| func (spd *SimplifiedParkDetail) Capacity(capacity decimal.NullDecimal) { | ||||
| 	spd.CapacityStr = tools.NullDecimalToString(capacity) | ||||
| } | ||||
|   | ||||
							
								
								
									
										30
									
								
								vo/report.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								vo/report.go
									
									
									
									
									
								
							| @@ -40,3 +40,33 @@ type ReportModifyForm struct { | ||||
| 	BasicFee    decimal.Decimal `json:"basicFee"` | ||||
| 	AdjustFee   decimal.Decimal `json:"adjustFee"` | ||||
| } | ||||
|  | ||||
| type SimplifiedReportIndex struct { | ||||
| 	Id                    string          `json:"id"` | ||||
| 	Park                  string          `json:"parkId"` | ||||
| 	PeriodBegin           types.Date      `json:"periodBegin"` | ||||
| 	PeriodEnd             types.Date      `json:"periodEnd"` | ||||
| 	Published             bool            `json:"published"` | ||||
| 	PublishedAt           *types.DateTime `json:"publishedAt"` | ||||
| 	Withdraw              int16           `json:"withdraw"` | ||||
| 	LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt"` | ||||
| 	LastWithdrawAuditAt   *types.DateTime `json:"lastWithdrawAuditAt"` | ||||
| 	Status                int16           `json:"status"` | ||||
| 	Message               *string         `json:"message"` | ||||
| } | ||||
|  | ||||
| func (sri *SimplifiedReportIndex) Period(p types.DateRange) { | ||||
| 	sri.PeriodBegin = p.SafeLower() | ||||
| 	sri.PeriodEnd = p.SafeUpper() | ||||
| } | ||||
|  | ||||
| type ReportIndexQueryResponse struct { | ||||
| 	Park   SimplifiedParkDetail   `json:"park"` | ||||
| 	Report *SimplifiedReportIndex `json:"report"` | ||||
| } | ||||
|  | ||||
| type ComprehensiveReportQueryResponse struct { | ||||
| 	Report SimplifiedReportIndex `json:"report"` | ||||
| 	Park   SimplifiedParkDetail  `json:"park"` | ||||
| 	User   SimplifiedUserDetail  `json:"user"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										14
									
								
								vo/user.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								vo/user.go
									
									
									
									
									
								
							| @@ -2,6 +2,7 @@ package vo | ||||
|  | ||||
| import ( | ||||
| 	"electricity_bill_calc/model" | ||||
| 	"electricity_bill_calc/tools" | ||||
| 	"electricity_bill_calc/types" | ||||
| 	"time" | ||||
|  | ||||
| @@ -125,3 +126,16 @@ type RepasswordForm struct { | ||||
| 	Username    string `json:"uname"` | ||||
| 	NewPassword string `json:"newPass"` | ||||
| } | ||||
|  | ||||
| type SimplifiedUserDetail struct { | ||||
| 	Id      string  `json:"id"` | ||||
| 	NameStr string  `json:"name"` | ||||
| 	Contact *string `json:"contact"` | ||||
| 	Phone   *string `json:"phone"` | ||||
| 	Region  *string `json:"region"` | ||||
| 	Address *string `json:"address"` | ||||
| } | ||||
|  | ||||
| func (sud *SimplifiedUserDetail) Name(n *string) { | ||||
| 	sud.NameStr = tools.DefaultTo(n, "") | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user