package calculate import ( "electricity_bill_calc/model" "electricity_bill_calc/model/calculate" "electricity_bill_calc/repository" "errors" "fmt" "github.com/shopspring/decimal" "sort" "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) } // 计算各个商户的合计信息,并归总与商户关联的表计记录 func TenementChargeCalculate(tenements []calculate.PrimaryTenementStatistics, summary calculate.Summary, meters MeterMap) []calculate.TenementCharge { result := make(map[string][]string) for _, t := range tenements { meterCodes := make([]string, 0) for _, m := range t.Meters { meterCodes = append(meterCodes, m.Code) } sort.Strings(meterCodes) result[t.Tenement.Id] = meterCodes } var Key Key var tc []calculate.TenementCharge for tCode, meterCodes := range result { relatedMeters := make([]calculate.Meter, 0) for _, code := range meterCodes { Key.Code = code + "_" + tCode meter, ok := meters[Key] if ok { relatedMeters = append(relatedMeters, meter) } } // 计算商户的合计电费信息 var overall model.ConsumptionUnit var critical model.ConsumptionUnit var peak model.ConsumptionUnit var flat model.ConsumptionUnit var valley model.ConsumptionUnit var basicPooled decimal.Decimal var adjustPooled decimal.Decimal var lossAmount decimal.Decimal var lossPooled decimal.Decimal var publicPooled decimal.Decimal for _, meter := range relatedMeters { overall.Amount.Add(meter.Overall.Amount) overall.Fee.Add(meter.Overall.Fee) critical.Amount.Add(meter.Critical.Amount) critical.Fee.Add(meter.Critical.Fee) peak.Amount.Add(meter.Peak.Amount) peak.Fee.Add(meter.Peak.Fee) flat.Amount.Add(meter.Flat.Amount) flat.Fee.Add(meter.Flat.Fee) valley.Amount.Add(meter.Valley.Amount) valley.Fee.Add(meter.Valley.Fee) basicPooled.Add(meter.PooledBasic.Fee) adjustPooled.Add(meter.PooledAdjust.Fee) lossAmount.Add(meter.PooledLoss.Amount) lossPooled.Add(meter.PooledLoss.Fee) publicPooled.Add(meter.PooledPublic.Fee) // 反写商户表计的统计数据 meter.Overall.Proportion = func() decimal.Decimal { if overall.Amount.Equal(decimal.Zero) { return decimal.Zero } return meter.Overall.Amount.Div(overall.Amount) }() meter.Critical.Proportion = func() decimal.Decimal { if critical.Amount.Equal(decimal.Zero) { return decimal.Zero } return meter.Critical.Amount.Div(critical.Amount) }() meter.Peak.Proportion = func() decimal.Decimal { if peak.Amount.Equal(decimal.Zero) { return decimal.Zero } return meter.Peak.Amount.Div(peak.Amount) }() meter.Flat.Proportion = func() decimal.Decimal { if flat.Amount.Equal(decimal.Zero) { return decimal.Zero } return meter.Flat.Amount.Div(flat.Amount) }() meter.Valley.Proportion = func() decimal.Decimal { if valley.Amount.Equal(decimal.Zero) { return decimal.Zero } return meter.Valley.Amount.Div(valley.Amount) }() meter.PooledBasic.Proportion = func() decimal.Decimal { if basicPooled.Equal(decimal.Zero) { return decimal.Zero } return meter.PooledBasic.Fee.Div(basicPooled) }() meter.PooledAdjust.Proportion = func() decimal.Decimal { if adjustPooled.Equal(decimal.Zero) { return decimal.Zero } return meter.PooledAdjust.Fee.Div(adjustPooled) }() meter.PooledLoss.Proportion = func() decimal.Decimal { if lossPooled.Equal(decimal.Zero) { return decimal.Zero } return meter.PooledLoss.Fee.Div(lossPooled) }() meter.PooledPublic.Proportion = func() decimal.Decimal { if publicPooled.Equal(decimal.Zero) { return decimal.Zero } return meter.PooledPublic.Fee.Div(publicPooled) }() var OverallProportion decimal.Decimal if summary.Overall.Amount == decimal.Zero { OverallProportion = decimal.Zero } else { OverallProportion = decimal.NewFromFloat(overall.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()) } var CriticalProportion decimal.Decimal if summary.Critical.Amount == decimal.Zero { CriticalProportion = decimal.Zero } else { CriticalProportion = decimal.NewFromFloat(critical.Amount.InexactFloat64() / summary.Critical.Amount.InexactFloat64()) } var PeakProportion decimal.Decimal if summary.Peak.Amount == decimal.Zero { PeakProportion = decimal.Zero } else { PeakProportion = decimal.NewFromFloat(peak.Amount.InexactFloat64() / summary.Peak.Amount.InexactFloat64()) } var FlatProportion decimal.Decimal if summary.Flat.Amount == decimal.Zero { FlatProportion = decimal.Zero } else { FlatProportion = decimal.NewFromFloat(flat.Amount.InexactFloat64() / summary.Flat.Amount.InexactFloat64()) } var ValleyProportion decimal.Decimal if summary.Valley.Amount == decimal.Zero { ValleyProportion = decimal.Zero } else { ValleyProportion = decimal.NewFromFloat(valley.Amount.InexactFloat64() / summary.Valley.Amount.InexactFloat64()) } tenementCharge := calculate.TenementCharge{ Tenement: tCode, Overall: model.ConsumptionUnit{ Price: summary.Overall.Price, Proportion: OverallProportion, }, Critical: model.ConsumptionUnit{ Price: summary.Critical.Price, Proportion: CriticalProportion, }, Peak: model.ConsumptionUnit{ Price: summary.Overall.Price, Proportion: PeakProportion, }, Flat: model.ConsumptionUnit{ Price: summary.Overall.Price, Proportion: FlatProportion, }, Valley: model.ConsumptionUnit{ Price: summary.Overall.Price, Proportion: ValleyProportion, }, BasicFee: basicPooled, AdjustFee: adjustPooled, LossPooled: lossPooled, PublicPooled: publicPooled, FinalCharges: decimal.NewFromFloat( overall.Fee.InexactFloat64() + basicPooled.InexactFloat64() + adjustPooled.InexactFloat64() + lossPooled.InexactFloat64() + publicPooled.InexactFloat64()), Submeters: nil, Poolings: nil, } tc = append(tc, tenementCharge) } } return tc }