electricity_bill_calc_service/service/calculate/tenement.go

470 lines
14 KiB
Go

package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"electricity_bill_calc/repository"
"electricity_bill_calc/types"
"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 {
movedInAt := tenement.MovedInAt
if movedInAt == nil {
shiftedTime := ShiftToAsiaShanghai(time.Time{})
movedInAt = &types.DateTime{Time: shiftedTime}
}
startReading, err := determineTenementMeterStartReading(meter.MeterId, periodStart, ShiftToAsiaShanghai(movedInAt.Time), meter, currentTermReadings, lastPeriodReadings)
if err != nil {
fmt.Println(err)
return calculate.PrimaryTenementStatistics{}, err
}
moveOutAt := tenement.MovedOutAt
if moveOutAt == nil {
shiftedTime := ShiftToAsiaShanghai(time.Time{})
moveOutAt = &types.DateTime{Time: shiftedTime}// 使用 types.DateTime 的零值表示时间的最大值
}
endReading, err := determineTenementMeterEndReading(meter.MeterId, periodEnd, ShiftToAsiaShanghai(moveOutAt.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 := &currentTermReading
m := 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, m)
}
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
}