498 lines
15 KiB
Go
498 lines
15 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 := ¤tTermReading
|
|
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)
|
|
var tc []calculate.TenementCharge
|
|
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
|
|
for tCode, meterCodes := range result {
|
|
relatedMeters := make([]calculate.Meter, 0)
|
|
for _, code := range meterCodes {
|
|
Key.Code = code
|
|
meter, ok := meters[Key]
|
|
if ok {
|
|
relatedMeters = append(relatedMeters, meter)
|
|
}
|
|
}
|
|
fmt.Println("---", relatedMeters[0].Valley)
|
|
fmt.Println("---", relatedMeters[0].Flat)
|
|
fmt.Println("---", relatedMeters[0].Peak)
|
|
fmt.Println("---", relatedMeters[0].Overall)
|
|
fmt.Println("---", relatedMeters[0].Critical)
|
|
// 计算商户的合计电费信息
|
|
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.InexactFloat64() == decimal.Zero.InexactFloat64() {
|
|
CriticalProportion = decimal.Zero
|
|
} else {
|
|
CriticalProportion = decimal.NewFromFloat(critical.Amount.InexactFloat64() / summary.Critical.Amount.InexactFloat64())
|
|
}
|
|
|
|
var PeakProportion decimal.Decimal
|
|
if summary.Peak.Amount.InexactFloat64() == decimal.Zero.InexactFloat64() {
|
|
PeakProportion = decimal.Zero
|
|
} else {
|
|
PeakProportion = decimal.NewFromFloat(peak.Amount.InexactFloat64() / summary.Peak.Amount.InexactFloat64())
|
|
}
|
|
|
|
var FlatProportion decimal.Decimal
|
|
if summary.Flat.Amount.InexactFloat64() == decimal.Zero.InexactFloat64() {
|
|
FlatProportion = decimal.Zero
|
|
} else {
|
|
FlatProportion = decimal.NewFromFloat(flat.Amount.InexactFloat64() / summary.Flat.Amount.InexactFloat64())
|
|
}
|
|
|
|
var ValleyProportion decimal.Decimal
|
|
if summary.Valley.Amount.InexactFloat64() == decimal.Zero.InexactFloat64() {
|
|
ValleyProportion = decimal.Zero
|
|
} else {
|
|
ValleyProportion = decimal.NewFromFloat(valley.Amount.InexactFloat64() / summary.Valley.Amount.InexactFloat64())
|
|
}
|
|
|
|
var lossProportion decimal.Decimal
|
|
if summary.AuthoizeLoss.Amount == decimal.Zero {
|
|
lossProportion = decimal.Zero
|
|
} else {
|
|
lossProportion = decimal.NewFromFloat(lossAmount.InexactFloat64() / summary.AuthoizeLoss.Amount.InexactFloat64())
|
|
}
|
|
tenementCharge := calculate.TenementCharge{
|
|
Tenement: tCode,
|
|
Overall: model.ConsumptionUnit{
|
|
Amount: overall.Amount,
|
|
Fee: overall.Fee,
|
|
Price: summary.Overall.Price,
|
|
Proportion: OverallProportion,
|
|
},
|
|
Critical: model.ConsumptionUnit{
|
|
Amount: critical.Amount,
|
|
Fee: critical.Fee,
|
|
Price: summary.Critical.Price,
|
|
Proportion: CriticalProportion,
|
|
},
|
|
Peak: model.ConsumptionUnit{
|
|
Amount: peak.Amount,
|
|
Fee: peak.Fee,
|
|
Price: summary.Peak.Price,
|
|
Proportion: PeakProportion,
|
|
},
|
|
Flat: model.ConsumptionUnit{
|
|
Amount: flat.Amount,
|
|
Fee: flat.Fee,
|
|
Price: summary.Flat.Price,
|
|
Proportion: FlatProportion,
|
|
},
|
|
Valley: model.ConsumptionUnit{
|
|
Amount: valley.Amount,
|
|
Fee: valley.Fee,
|
|
Price: summary.Valley.Price,
|
|
Proportion: ValleyProportion,
|
|
},
|
|
Loss: model.ConsumptionUnit{
|
|
Amount: lossAmount,
|
|
Fee: lossPooled,
|
|
Price: summary.AuthoizeLoss.Price,
|
|
Proportion: lossProportion,
|
|
},
|
|
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,
|
|
}
|
|
fmt.Println("=====", tenementCharge)
|
|
tc = append(tc, tenementCharge)
|
|
}
|
|
}
|
|
//fmt.Println(len(tc))
|
|
return tc
|
|
}
|