[计算相关]获取所有的物业表计,然后对所有的物业表计电量进行计算。(完成)

This commit is contained in:
ZiHangQin 2023-08-03 16:59:58 +08:00
parent f254ec1f3a
commit f688f50ecb
14 changed files with 1084 additions and 15 deletions

View File

@ -1,13 +0,0 @@
## fiber
#### fiber实例
- app是fiber创建的实例通常用app表示其中有可选配置选项
- BodyLimit 设置请求正文允许的最大大小默认为4 * 1024 * 1024
- EnablePrintRoutes 不打印框架自带日志默认false)
- EnableTrustedProxyCheck 禁用受信代理默认false)
- Prefork 预处理配置默认false)
- ErrorHandler 全局错误处理 默认false)
- JSONEncoder json编码 默认json.Marshal)
- JSONDecoder json解码 (默认json.Unmarshal)
- 。。。。。。。。(还有很多配置)
- Use中间件设置一个或者多个
- Group类似于gin框架中的路由分组

View File

@ -3,6 +3,7 @@ package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"fmt"
"github.com/shopspring/decimal"
)
@ -42,6 +43,11 @@ type Meter struct {
Poolings []*Pooling
}
type PrimaryTenementStatistics struct {
Tenement model.Tenement
Meters []Meter
}
type TenementCharge struct {
Tenement string
Overall model.ConsumptionUnit
@ -90,3 +96,118 @@ type PoolingSummary struct {
OverallAmount decimal.Decimal
PoolingProportion decimal.Decimal
}
func FromReportSummary(summary *model.ReportSummary, pricingMode *model.ReportIndex) Summary {
var parkPrice float64
switch pricingMode.PricePolicy {
case model.PRICING_POLICY_CONSUMPTION:
parkPrice = summary.ConsumptionFee.Decimal.InexactFloat64() / summary.Overall.Amount.InexactFloat64()
case model.PRICING_POLICY_ALL:
parkPrice = summary.Overall.Fee.InexactFloat64() / summary.Overall.Amount.InexactFloat64()
default:
fmt.Println("无法识别类型")
}
flatAmount := summary.Overall.Amount.InexactFloat64() -
summary.Critical.Amount.InexactFloat64() -
summary.Peak.Amount.InexactFloat64() -
summary.Valley.Amount.InexactFloat64()
flatFee := summary.Overall.Amount.InexactFloat64() -
summary.Critical.Fee.InexactFloat64() -
summary.Peak.Fee.InexactFloat64() -
summary.Valley.Fee.InexactFloat64()
var OverallPrice float64
if summary.Overall.Amount.GreaterThan(decimal.Zero) {
OverallPrice = parkPrice
} else {
OverallPrice = decimal.Zero.InexactFloat64()
}
var CriticalPrice float64
if summary.Critical.Amount.GreaterThan(decimal.Zero) {
CriticalPrice = summary.Critical.Fee.InexactFloat64() / summary.Critical.Amount.InexactFloat64()
} else {
CriticalPrice = decimal.Zero.InexactFloat64()
}
var PeakPrice float64
if summary.Peak.Amount.GreaterThan(decimal.Zero) {
PeakPrice = summary.Peak.Fee.InexactFloat64() / summary.Peak.Amount.InexactFloat64()
} else {
PeakPrice = decimal.Zero.InexactFloat64()
}
var FlatPrice float64
if decimal.NewFromFloat(flatAmount).GreaterThan(decimal.Zero) {
FlatPrice = flatFee / flatAmount
} else {
FlatPrice = decimal.Zero.InexactFloat64()
}
var ValleyPrice float64
if summary.Valley.Amount.GreaterThan(decimal.Zero) {
ValleyPrice = summary.Valley.Fee.InexactFloat64() / summary.Valley.Amount.InexactFloat64()
} else {
ValleyPrice = decimal.Zero.InexactFloat64()
}
var LossDilutedPrice float64
if summary.Overall.Amount.GreaterThan(decimal.Zero) {
LossDilutedPrice = parkPrice
} else {
LossDilutedPrice = decimal.Zero.InexactFloat64()
}
_ = parkPrice
return Summary{
ReportId: summary.ReportId,
OverallArea: decimal.Zero,
Overall: model.ConsumptionUnit{
Amount: summary.Overall.Amount,
Fee: summary.Overall.Fee,
Price: decimal.NewFromFloat(OverallPrice),
Proportion: decimal.NewFromFloat(1.0),
},
ConsumptionFee: summary.ConsumptionFee.Decimal,
Critical: model.ConsumptionUnit{
Amount: summary.Critical.Amount,
Fee: summary.Critical.Fee,
Price: decimal.NewFromFloat(CriticalPrice),
Proportion: decimal.NewFromFloat(summary.Critical.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()),
},
Peak: model.ConsumptionUnit{
Amount: summary.Peak.Amount,
Fee: summary.Peak.Fee,
Price: decimal.NewFromFloat(PeakPrice),
Proportion: decimal.NewFromFloat(summary.Peak.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()),
},
Flat: model.ConsumptionUnit{
Amount: decimal.NewFromFloat(flatAmount),
Fee: decimal.NewFromFloat(flatFee),
Price: decimal.NewFromFloat(FlatPrice),
Proportion: decimal.NewFromFloat(flatAmount / summary.Overall.Amount.InexactFloat64()),
},
Valley: model.ConsumptionUnit{
Amount: summary.Valley.Amount,
Fee: summary.Valley.Fee,
Price: decimal.NewFromFloat(ValleyPrice),
Proportion: decimal.NewFromFloat(summary.Valley.Amount.InexactFloat64() / summary.Overall.Amount.InexactFloat64()),
},
Loss: decimal.Zero,
LossFee: decimal.Zero,
LossProportion: decimal.Zero,
AuthoizeLoss: model.ConsumptionUnit{},
BasicFee: summary.BasicFee,
BasicPooledPriceConsumption: decimal.Zero,
BasicPooledPriceArea: decimal.Zero,
AdjustFee: summary.AdjustFee,
AdjustPooledPriceConsumption: decimal.Zero,
AdjustPooledPriceArea: decimal.Zero,
LossDilutedPrice: decimal.NewFromFloat(LossDilutedPrice),
TotalConsumption: decimal.Zero,
FinalDilutedOverall: decimal.Zero,
}
}

View File

@ -21,3 +21,13 @@ type Tenement struct {
LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"`
DeletedAt *types.DateTime `json:"deletedAt" db:"deleted_at"`
}
type TenementMeter struct {
ParkId string `db:"park_id"`
TenementId string `db:"tenement_id"`
MeterId string `db:"meter_id"`
ForeignRelation bool `db:"foreign_relation"`
AssociatedAt types.DateTime `db:"associated_at"`
DisassociatedAt types.DateTime `db:"disassociated_at"`
SynchronizedAt types.DateTime `db:"synchronized_at"`
}

View File

@ -5,6 +5,7 @@ import (
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"time"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
@ -68,3 +69,173 @@ func (cr _CalculateRepository) UpdateReportTaskStatus(rid string, status int16,
}
return res.RowsAffected() > 0, nil
}
//获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的
func (cr _CalculateRepository) GetAllPoolingMeterRelations(pid string, revokedAfter time.Time) ([]model.MeterRelation, error) {
cr.log.Info("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("revokedAfter", revokedAfter))
ctx, cancel := global.TimeoutContext()
defer cancel()
relationsSql, relationsArgs, _ := cr.ds.
From(goqu.T("meter_relations")).
Where(goqu.I("park_id").Eq(pid)).
Where(goqu.Or(
goqu.I("revoked_at").IsNull(),
goqu.I("revoked_at").Gte(revokedAfter),
)).ToSQL()
var meterRelation []model.MeterRelation
err := pgxscan.Select(ctx, global.DB, meterRelation, relationsSql, relationsArgs...)
if err != nil {
cr.log.Error("获取当前园区中所有公摊表计与商户表计之间的关联关系,包括已经解除的出错", zap.Error(err))
return nil, err
}
return meterRelation, nil
}
//获取当前园区中所有的商户与表计的关联关系,包括已经解除的
func (cr _CalculateRepository) GetAllTenementMeterRelations(pid string, associatedBefore time.Time, disassociatedAfter time.Time) ([]model.TenementMeter, error) {
cr.log.Info("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.String("pid", pid), zap.Time("associatedBefore", associatedBefore), zap.Time("disassociatedAfter", disassociatedAfter))
ctx, cancel := global.TimeoutContext()
defer cancel()
relationsQuerySql, relationsQueryArgs, _ := cr.ds.
From(goqu.T("tenement_meter")).
Where(goqu.I("park_id").Eq(pid)).
Where(goqu.And(
goqu.I("associated_at").IsNull(),
goqu.I("associated_at").Lte(associatedBefore),
)).
Where(goqu.And(
goqu.I("associated_at").IsNull(),
goqu.I("associated_at").Gte(disassociatedAfter),
)).ToSQL()
var tenementMeter []model.TenementMeter
err := pgxscan.Select(ctx, global.DB, tenementMeter, relationsQuerySql, relationsQueryArgs...)
if err != nil {
cr.log.Error("获取当前园区中所有的商户与表计的关联关系,包括已经解除的", zap.Error(err))
return nil, err
}
return tenementMeter, nil
}
//获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据
func (cr _CalculateRepository) GetMeterReadings(rid string, meterType int16) ([]model.MeterReading, error) {
cr.log.Info("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据", zap.String("rid", rid), zap.Int16("meterType", meterType))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingsQuerySql, readingsQueryArgs, _ := cr.ds.
From(goqu.T("meter_reading").As(goqu.I("mr"))).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("mr.meter_type").Eq(meterType),
// TODO2023.08.02 此方法出错优先查看是否这里出问题
goqu.I("mr.read_at::date <@ r.period"),
).
Order(goqu.I("mr.read_at").Asc()).Select(goqu.I("mr.*")).ToSQL()
var readings []model.MeterReading
err := pgxscan.Select(ctx, global.DB, readings, readingsQuerySql, readingsQueryArgs...)
if err != nil {
cr.log.Error("获取指定报表中所有涉及到的指定类型表计在核算时间段内的所有读数数据出错", zap.Error(err))
return nil, err
}
return readings, nil
}
// 获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数
func (cr _CalculateRepository) GetLastPeriodReadings(rid string, meterType int16) ([]model.MeterReading, error) {
cr.log.Info("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数", zap.String("rid", rid), zap.Int16("meterType", meterType))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingsSql, readingsArgs, _ := cr.ds.From(goqu.T("meter_reading").As("mr")).
Select(
goqu.MAX("mr.read_at").As("read_at"),
goqu.I("mr.park_id"),
goqu.I("mr.meter_id"),
goqu.I("mr.meter_type"),
goqu.I("mr.ratio"),
goqu.I("mr.overall"),
goqu.I("mr.critical"),
goqu.I("mr.peak"),
goqu.I("mr.flat"),
goqu.I("mr.valley"),
).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("mr.park_id"))),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("mr.meter_type").Eq(meterType),
goqu.I(" mr.read_at::date <= lower(r.period)"),
).
GroupBy(
goqu.I("mr.park_id"),
goqu.I("mr.meter_id"),
goqu.I("mr.meter_type"),
goqu.I("mr.ratio"),
goqu.I("mr.overall"),
goqu.I("mr.critical"),
goqu.I("mr.peak"),
goqu.I("mr.flat"),
goqu.I("mr.valley"),
goqu.I("r.period"),
).ToSQL()
var readings []model.MeterReading
err := pgxscan.Select(ctx, global.DB, readings, readingsSql, readingsArgs...)
if err != nil {
cr.log.Error("获取指定报表中所有涉及到的表计在核算起始日期前的最后一次读数出错", zap.Error(err))
return nil, err
}
return readings, nil
}
// 取得指定报表所涉及的所有商户信息
func (cr _CalculateRepository) GetAllTenements(rid string) ([]model.Tenement, error) {
cr.log.Info("取得指定报表所涉及的所有商户信息", zap.String("rid", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementQuerySql, tenementQueryArgs, _ := cr.ds.
From(goqu.T("tenement").As("t")).
LeftJoin(
goqu.T("park_building").As("b"),
goqu.On(goqu.I("b.id").Eq(goqu.I("t.building"))),
).
Join(
goqu.T("report").As("r"),
goqu.On(goqu.I("r.park_id").Eq(goqu.I("t.park_id"))),
).
Select(
goqu.I("t.*"),
goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("t.moved_in_at <= upper(r.period)"),
).ToSQL()
var tenements []model.Tenement
err := pgxscan.Select(ctx, global.DB, tenements, tenementQuerySql, tenementQueryArgs...)
if err != nil {
cr.log.Error("取得指定报表所涉及的所有商户信息出错", zap.Error(err))
return nil, err
}
return tenements, nil
}

View File

@ -132,7 +132,7 @@ func (wd _WithdrawRepository) FindWithdraw(page uint, keyword *string) ([]model.
PeriodBegin: Begin,
PeriodEnd: End,
Published: v.Published,
PublishedAt: nil,
PublishedAt: tools.TimeToStringPtr(v.LastWithdrawAuditAt),
Status: 0.,
Withdraw: v.Withdraw,
}

View File

@ -0,0 +1,32 @@
package calculate
import (
"electricity_bill_calc/model"
"fmt"
"sync/atomic"
)
func CheckMeterArea(report *model.ReportIndex, meters []*model.MeterDetail) (bool, error) {
anyAreaOptions := report.BasisPooled == model.POOLING_MODE_AREA ||
report.AdjustPooled == model.POOLING_MODE_AREA ||
report.PublicPooled == model.POOLING_MODE_AREA ||
report.LossPooled == model.POOLING_MODE_AREA
if anyAreaOptions {
var meterWithoutArea int32
for _, m := range meters {
if (m.MeterType == model.METER_INSTALLATION_TENEMENT || m.MeterType == model.METER_INSTALLATION_POOLING) &&
m.Area == nil {
atomic.AddInt32(&meterWithoutArea, 1)
}
}
if meterWithoutArea != 0 {
return false, fmt.Errorf("园区中有 %d 个表计没有设置面积,无法进行按面积摊薄。", meterWithoutArea)
}
return true, nil
}
return false, nil
}

37
service/calculate/park.go Normal file
View File

@ -0,0 +1,37 @@
package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"electricity_bill_calc/repository"
"time"
)
func MetersParkCalculate(report model.ReportIndex, periodStart time.Time,
periodEnd time.Time, meterDetail []*model.MeterDetail,
summary calculate.Summary) ([]calculate.Meter, error) {
parkMeterReadings, err := repository.CalculateRepository.GetMeterReadings(report.Id, model.METER_INSTALLATION_PARK)
if err != nil {
return nil, err
}
lastTermParkMeterReadings, err := repository.CalculateRepository.GetLastPeriodReadings(report.Id, model.METER_INSTALLATION_PARK)
if err != nil {
return nil, err
}
parkMeterReadings = append(parkMeterReadings, lastTermParkMeterReadings...)
var parkMetersReports []calculate.Meter
for _, meter := range meterDetail {
if meter.MeterType == model.METER_INSTALLATION_PARK {
parkMetersReport, err := determinePublicMeterConsumptions(meter.Code, periodStart, periodEnd, parkMeterReadings, *meter, summary)
if err != nil {
return nil, err
}
parkMetersReports = append(parkMetersReports, parkMetersReport)
}
}
return parkMetersReports, nil
}

111
service/calculate/pooled.go Normal file
View File

@ -0,0 +1,111 @@
package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"electricity_bill_calc/repository"
"github.com/shopspring/decimal"
"time"
"unsafe"
)
//核算园区中的全部公摊表计的电量用量
func PooledMetersCalculate(report *model.ReportIndex, periodStart time.Time,
periodEnd time.Time, meterDetails []*model.MeterDetail,
summary calculate.Summary) ([]calculate.Meter, error) {
poolingMeterReadings, err := repository.CalculateRepository.GetMeterReadings(report.Id, model.METER_INSTALLATION_POOLING)
if err != nil {
return nil, err
}
lastTermPoolingMeterReadings, err := repository.CalculateRepository.GetLastPeriodReadings(report.Id, model.METER_INSTALLATION_POOLING)
if err != nil {
return nil, err
}
poolingMeterReadings = append(poolingMeterReadings, lastTermPoolingMeterReadings...)
var poolingMetersReports []calculate.Meter
for _, meter := range meterDetails {
poolingMetersReport, err := determinePublicMeterConsumptions(meter.Code, periodStart, periodEnd, poolingMeterReadings, *meter, summary)
if err != nil {
return nil, err
}
poolingMetersReports = append(poolingMetersReports, poolingMetersReport)
}
return poolingMetersReports, nil
}
// 确定指定非商户表计在指定时间段内的全部电量
func determinePublicMeterConsumptions(meterId string, periodStart time.Time,
periodEnd time.Time, readings []model.MeterReading,
meterDetail model.MeterDetail, summary calculate.Summary) (calculate.Meter, error) {
startReading, err := DeterminePublicMeterStartReading(meterId, periodStart, meterDetail.DetachedAt.Time, readings)
if err != nil {
return calculate.Meter{}, err
}
endReading, err := DeterminePublicMeterEndReading(meterId, periodEnd, meterDetail.DetachedAt.Time, readings)
if err != nil {
return calculate.Meter{}, err
}
overall, err := ComputeOverall(*startReading, *endReading, summary)
if err != nil {
return calculate.Meter{}, err
}
critical, err := ComputeCritical(*startReading, *endReading, summary)
if err != nil {
return calculate.Meter{}, err
}
peak, err := ComputePeak(*startReading, *endReading, summary)
if err != nil {
return calculate.Meter{}, err
}
flat, err := ComputeFlat(*startReading, *endReading, summary)
if err != nil {
return calculate.Meter{}, err
}
valley, err := ComputeValley(*startReading, *endReading, summary)
if err != nil {
return calculate.Meter{}, err
}
return calculate.Meter{
Code: meterId,
Detail: meterDetail,
CoveredArea: meterDetail.Area.Decimal,
LastTermReading: (*calculate.Reading)(unsafe.Pointer(&model.Reading{
Ratio: startReading.Ratio,
Overall: startReading.Overall,
Critical: startReading.Critical,
Peak: startReading.Peak,
Flat: startReading.Flat,
Valley: startReading.Valley,
})),
CurrentTermReading: (*calculate.Reading)(unsafe.Pointer(&model.Reading{
Ratio: endReading.Ratio,
Overall: endReading.Overall,
Critical: endReading.Critical,
Peak: endReading.Peak,
Flat: endReading.Flat,
Valley: endReading.Valley,
})),
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,
}, nil
}

105
service/calculate/shared.go Normal file
View File

@ -0,0 +1,105 @@
package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"errors"
"fmt"
"time"
)
// 确定指定非商户表计的起始读数
func DeterminePublicMeterStartReading(meterId string, periodStart time.Time,
attachedAt time.Time, meterReadings []model.MeterReading) (*model.MeterReading, error) {
periodBeginning := types.Date{Time: periodStart}.ToBeginningOfDate()
if len(meterReadings) <= 0 {
return nil, errors.New(fmt.Sprintf("表计的抄表记录数据不足%s", meterId))
}
var minReading types.DateTime
for _, reading := range meterReadings {
if reading.ReadAt.Before(minReading.Time) {
minReading = reading.ReadAt
}
}
startTimes := []time.Time{
minReading.Time,
periodBeginning.Time,
ShiftToAsiaShanghai(attachedAt),
}
if len(startTimes) < 0 {
return nil, errors.New(fmt.Sprintf("无法确定表计 {%s} 的计量的起始时间", meterId))
}
var startReading []model.MeterReading
for _, reading := range meterReadings {
readingAt := ShiftToAsiaShanghai(reading.ReadAt.UTC())
for _, startTime := range startTimes {
if reading.Meter == meterId && readingAt.After(startTime) || readingAt.Equal(startTime) {
startReading = append(startReading, reading)
break
}
}
}
if len(startReading) <= 0 {
return nil, errors.New(fmt.Sprintf("无法确定表计 %s 的计量的起始读数", meterId))
}
var startReadings *model.MeterReading
for _, readings := range startReading {
if startReadings == nil || readings.ReadAt.Before(startReadings.ReadAt.Time) {
startReadings = &readings
}
}
return startReadings, nil
}
// 确定指定非商户表计的结束读数
func DeterminePublicMeterEndReading(meterId string, periodEnd time.Time,
detachedAt time.Time, meterReadings []model.MeterReading) (*model.MeterReading, error) {
periodEnding := types.Date{Time: periodEnd}.ToEndingOfDate()
if len(meterReadings) <= 0 {
return nil, errors.New(fmt.Sprintf("表计的抄表记录数据不足%s", meterId))
}
var minReading types.DateTime
for _, reading := range meterReadings {
if reading.ReadAt.Before(minReading.Time) {
minReading = reading.ReadAt
}
}
startTimes := []time.Time{
minReading.Time,
periodEnding.Time,
ShiftToAsiaShanghai(detachedAt),
}
if len(startTimes) < 0 {
return nil, errors.New(fmt.Sprintf("无法确定表计 {%s} 的计量的终止时间", meterId))
}
var startReading []model.MeterReading
for _, reading := range meterReadings {
readingAt := ShiftToAsiaShanghai(reading.ReadAt.UTC())
for _, startTime := range startTimes {
if reading.Meter == meterId && readingAt.After(startTime) || readingAt.Equal(startTime) {
startReading = append(startReading, reading)
break
}
}
}
if len(startReading) <= 0 {
return nil, errors.New(fmt.Sprintf("无法确定表计 %s 的计量的终止读数", meterId))
}
var startReadings *model.MeterReading
for _, readings := range startReading {
if startReadings == nil || readings.ReadAt.Before(startReadings.ReadAt.Time) {
startReadings = &readings
}
}
return startReadings, nil
}

View File

@ -0,0 +1 @@
package calculate

View File

@ -0,0 +1,288 @@
package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"electricity_bill_calc/repository"
"errors"
"fmt"
"github.com/shopspring/decimal"
"strings"
"time"
"unsafe"
)
// 核算园区中的全部商户表计电量用电
func TenementMetersCalculate(report *model.ReportIndex, PeriodStart time.Time,
PeriodEnd time.Time, meterDetails []*model.MeterDetail,
summary calculate.Summary) ([]model.TenementMeter, 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 []model.Tenement
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
}
report := model.Tenement{
Id: pt.Tenement.Id,
Park: pt.Tenement.Park,
FullName: pt.Tenement.FullName,
ShortName: pt.Tenement.ShortName,
Abbr: pt.Tenement.Abbr,
Address: pt.Tenement.Address,
ContactName: pt.Tenement.ContactName,
ContactPhone: pt.Tenement.ContactPhone,
Building: pt.Tenement.Building,
BuildingName: pt.Tenement.BuildingName,
OnFloor: pt.Tenement.OnFloor,
InvoiceInfo: pt.Tenement.InvoiceInfo,
MovedInAt: pt.Tenement.MovedInAt,
MovedOutAt: pt.Tenement.MovedOutAt,
CreatedAt: pt.Tenement.CreatedAt,
LastModifiedAt: pt.Tenement.LastModifiedAt,
DeletedAt: pt.Tenement.DeletedAt,
}
tenementReports = append(tenementReports, report)
}
return tenementMeterRelations, 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 := &currentTermReading
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)
}

141
service/calculate/utils.go Normal file
View File

@ -0,0 +1,141 @@
package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"errors"
"fmt"
"github.com/shopspring/decimal"
)
// 计算两个读书之间的有功(总)电量
func ComputeOverall(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) {
start := startReading.Overall.InexactFloat64() * startReading.Ratio.InexactFloat64()
end := endReading.Overall.InexactFloat64() * endReading.Ratio.InexactFloat64()
if start > end {
return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("表计 {%s} 有功(总)开始读数 {%x} 大于结束读数 {%x}", startReading.Meter, start, end))
}
amount := end - start
var summaryAmount float64
if summary.Overall.Amount == decimal.Zero {
summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64()
} else {
summaryAmount = summary.Overall.Amount.InexactFloat64()
}
return model.ConsumptionUnit{
Amount: decimal.NewFromFloat(amount),
Fee: decimal.NewFromFloat(amount * summary.Overall.Price.InexactFloat64()),
Price: decimal.NewFromFloat(summary.Overall.Price.InexactFloat64()),
Proportion: decimal.NewFromFloat(amount / summaryAmount),
}, nil
}
//计算两个读书之间的尖峰电量
func ComputeCritical(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) {
start := startReading.Critical.InexactFloat64() * startReading.Ratio.InexactFloat64()
end := endReading.Critical.InexactFloat64() * endReading.Ratio.InexactFloat64()
if start > end {
return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("尖峰开始读数 {%x} 大于结束读数 {%x}", start, end))
}
amount := end - start
var summaryAmount float64
if summary.Critical.Amount.Equal(decimal.Zero) {
summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64()
} else {
summaryAmount = summary.Critical.Amount.InexactFloat64()
}
return model.ConsumptionUnit{
Amount: decimal.NewFromFloat(amount),
Fee: decimal.NewFromFloat(amount * summary.Critical.Amount.InexactFloat64()),
Price: decimal.NewFromFloat(summary.Critical.Price.InexactFloat64()),
Proportion: decimal.NewFromFloat(amount / summaryAmount),
}, nil
}
// 计算两个读数之间的峰电量
func ComputePeak(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) {
start := startReading.Peak.InexactFloat64() * startReading.Ratio.InexactFloat64()
end := startReading.Peak.InexactFloat64() * endReading.Ratio.InexactFloat64()
if start > end {
return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("峰开始读数 {%x} 大于结束读数 {%x}", start, end))
}
amount := end - start
var summaryAmount float64
if summary.Peak.Amount.Equal(decimal.Zero) {
summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64()
} else {
summaryAmount = summary.Peak.Amount.InexactFloat64()
}
return model.ConsumptionUnit{
Amount: decimal.NewFromFloat(amount),
Fee: decimal.NewFromFloat(amount * summary.Peak.Price.InexactFloat64()),
Price: decimal.NewFromFloat(summary.Peak.Price.InexactFloat64()),
Proportion: decimal.NewFromFloat(amount / summaryAmount),
}, nil
}
//计算两个读数之间的平电量
func ComputeFlat(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) {
start := startReading.Flat.InexactFloat64() * startReading.Ratio.InexactFloat64()
end := endReading.Flat.InexactFloat64() * endReading.Ratio.InexactFloat64()
if start > end {
return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("平开始读数 {%x} 大于结束读数 {%x}", start, end))
}
amount := end - start
var summaryAmount float64
if summary.Flat.Amount.Equal(decimal.Zero) {
summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64()
} else {
summaryAmount = summary.Flat.Amount.InexactFloat64()
}
return model.ConsumptionUnit{
Amount: decimal.NewFromFloat(amount),
Fee: decimal.NewFromFloat(amount * summary.Flat.Price.InexactFloat64()),
Price: decimal.NewFromFloat(summary.Flat.Price.InexactFloat64()),
Proportion: decimal.NewFromFloat(amount / summaryAmount),
}, nil
}
//计算两个读数之间的谷电量
func ComputeValley(startReading model.MeterReading, endReading model.MeterReading, summary calculate.Summary) (model.ConsumptionUnit, error) {
start := startReading.Valley.InexactFloat64() * startReading.Ratio.InexactFloat64()
end := endReading.Valley.InexactFloat64() * endReading.Ratio.InexactFloat64()
if start > end {
return model.ConsumptionUnit{}, errors.New(fmt.Sprintf("谷开始读数 {%x} 大于结束读数 {%x}", start, end))
}
amount := end - start
var summaryAmount float64
if summary.Valley.Amount.Equal(decimal.Zero) {
summaryAmount = decimal.NewFromFloat(1.0).InexactFloat64()
} else {
summaryAmount = summary.Valley.Amount.InexactFloat64()
}
return model.ConsumptionUnit{
Amount: decimal.NewFromFloat(amount),
Fee: decimal.NewFromFloat(amount * summary.Valley.Price.InexactFloat64()),
Price: decimal.NewFromFloat(summary.Valley.Price.InexactFloat64()),
Proportion: decimal.NewFromFloat(amount / summaryAmount),
}, nil
}

View File

@ -0,0 +1,66 @@
package calculate
import (
"electricity_bill_calc/model/calculate"
"electricity_bill_calc/repository"
"fmt"
)
func MainCalculateProcess(rid string) {
report, err := repository.ReportRepository.GetReportIndex(rid)
if err != nil {
fmt.Println("1", err.Error()+"指定报表不存在")
return
}
reportSummary, err := repository.ReportRepository.RetrieveReportSummary(rid)
if err != nil {
fmt.Println("2", err.Error()+"指定报表的基本电量电费数据不存在")
return
}
summary := calculate.FromReportSummary(reportSummary, report)
periodStart := report.Period.SafeLower()
periodEnd := report.Period.SafeUpper()
meterDetails, err := repository.MeterRepository.AllUsedMetersInReport(report.Id)
if err != nil {
fmt.Println("3", err)
return
}
meterRelations, err := repository.CalculateRepository.GetAllPoolingMeterRelations(report.Park, periodStart.Time)
if err != nil {
fmt.Println("4", err)
return
}
_, err = CheckMeterArea(report, meterDetails)
if err != nil {
fmt.Println("5", err)
return
}
// 寻找每一个商户的所有表计读数,然后对分配到各个商户的表计读数进行初步的计算.
tenementReports, err := TenementMetersCalculate(report, periodStart.Time, periodEnd.Time, meterDetails, summary)
if err != nil {
fmt.Println("6", err)
return
}
//取得所有公摊表计的读数,以及公摊表计对应的分摊表计
poolingMetersReports, err := PooledMetersCalculate(report, periodStart.Time, periodEnd.Time, meterDetails, summary)
if err != nil {
fmt.Println("7", err)
return
}
parkMetersReports, err := MetersParkCalculate(*report, periodStart.Time, periodEnd.Time, meterDetails, summary)
if err != nil {
fmt.Println("8", err)
return
}
fmt.Println(meterRelations, tenementReports, poolingMetersReports, parkMetersReports)
}

View File

@ -160,7 +160,6 @@ func NullTime2PointerString(nullTime sql.NullTime) *string {
}
}
//该方法用于将时间解析为字符串指针
func TimeToStringPtr(t *time.Time) *string {
if t == nil {