enhance(calculate): 完善计算部分

This commit is contained in:
2023-08-07 15:15:11 +08:00
84 changed files with 3173 additions and 5398 deletions

View File

@@ -1,262 +0,0 @@
package service
import (
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/model"
"fmt"
"github.com/shopspring/decimal"
)
type _CalculateService struct{}
var CalculateService _CalculateService
func (_CalculateService) ComprehensivelyCalculateReport(reportId string) (err error) {
ctx, cancel := global.TimeoutContext(12)
defer cancel()
// 资料准备
var report = new(model.Report)
err = global.DB.NewSelect().Model(report).
Relation("Summary").
Relation("WillDilutedFees").
Relation("EndUsers").
Where("r.id = ?", reportId).
Scan(ctx)
if err != nil || report == nil {
return exceptions.NewNotFoundErrorFromError("未找到指定的公示报表", err)
}
// 综合计算
report.Summary.CalculatePrices()
// 计算维护费总计
maintenanceFeeTotal := decimal.NewFromInt(0)
for _, m := range report.WillDilutedFees {
maintenanceFeeTotal = maintenanceFeeTotal.Add(m.Fee)
}
// 计算终端用户信息与概览中的合计
report.Summary.Customers = model.NewConsumptions()
report.Summary.Publics = model.NewConsumptions()
for _, eu := range report.EndUsers {
eu.OverallFee = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
eu.CriticalFee = decimal.NewNullDecimal(
eu.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2),
)
eu.PeakFee = decimal.NewNullDecimal(
eu.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2),
)
eu.FlatFee = decimal.NewNullDecimal(
eu.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2),
)
eu.ValleyFee = decimal.NewNullDecimal(
eu.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2),
)
if eu.IsPublicMeter {
report.Summary.Publics.Consumption.Decimal = report.Summary.Publics.Consumption.Decimal.Add(eu.Overall.Decimal)
report.Summary.Publics.Critical.Decimal = report.Summary.Publics.Critical.Decimal.Add(eu.Critical.Decimal)
report.Summary.Publics.Peak.Decimal = report.Summary.Publics.Peak.Decimal.Add(eu.Peak.Decimal)
report.Summary.Publics.Flat.Decimal = report.Summary.Publics.Flat.Decimal.Add(eu.Flat.Decimal)
report.Summary.Publics.Valley.Decimal = report.Summary.Publics.Valley.Decimal.Add(eu.Valley.Decimal)
} else {
report.Summary.Customers.Consumption.Decimal = report.Summary.Customers.Consumption.Decimal.Add(eu.Overall.Decimal)
report.Summary.Customers.Critical.Decimal = report.Summary.Customers.Critical.Decimal.Add(eu.Critical.Decimal)
report.Summary.Customers.Peak.Decimal = report.Summary.Customers.Peak.Decimal.Add(eu.Peak.Decimal)
report.Summary.Customers.Flat.Decimal = report.Summary.Customers.Flat.Decimal.Add(eu.Flat.Decimal)
report.Summary.Customers.Valley.Decimal = report.Summary.Customers.Valley.Decimal.Add(eu.Valley.Decimal)
}
}
// 计算户表总电费和公共总电费以及相应的摊薄
if report.SubmeterType == model.CUSTOMER_METER_NON_PV {
// 计算终端用户部分
report.Summary.Customers.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Customers.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.CriticalFee = decimal.NewNullDecimal(
report.Summary.Customers.Critical.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.PeakFee = decimal.NewNullDecimal(
report.Summary.Customers.Peak.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.FlatFee = decimal.NewNullDecimal(
report.Summary.Customers.Flat.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.ValleyFee = decimal.NewNullDecimal(
report.Summary.Customers.Valley.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
// 计算公共区域部分
report.Summary.Publics.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Publics.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.CriticalFee = decimal.NewNullDecimal(
report.Summary.Publics.Critical.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.PeakFee = decimal.NewNullDecimal(
report.Summary.Publics.Peak.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.FlatFee = decimal.NewNullDecimal(
report.Summary.Publics.Flat.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.ValleyFee = decimal.NewNullDecimal(
report.Summary.Publics.Valley.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
}
if report.SubmeterType == model.CUSTOMER_METER_PV {
// 计算终端用户部分
report.Summary.Customers.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Customers.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.CriticalFee = decimal.NewNullDecimal(
report.Summary.Customers.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.PeakFee = decimal.NewNullDecimal(
report.Summary.Customers.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.FlatFee = decimal.NewNullDecimal(
report.Summary.Customers.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2),
)
report.Summary.Customers.ValleyFee = decimal.NewNullDecimal(
report.Summary.Customers.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2),
)
// 计算公共区域部分
report.Summary.Publics.ConsumptionFee = decimal.NewNullDecimal(
report.Summary.Publics.Consumption.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.CriticalFee = decimal.NewNullDecimal(
report.Summary.Publics.Critical.Decimal.Mul(report.Summary.CriticalPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.PeakFee = decimal.NewNullDecimal(
report.Summary.Publics.Peak.Decimal.Mul(report.Summary.PeakPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.FlatFee = decimal.NewNullDecimal(
report.Summary.Publics.Flat.Decimal.Mul(report.Summary.FlatPrice.Decimal).RoundBank(2),
)
report.Summary.Publics.ValleyFee = decimal.NewNullDecimal(
report.Summary.Publics.Valley.Decimal.Mul(report.Summary.ValleyPrice.Decimal).RoundBank(2),
)
}
if report.Summary.Overall.Abs().GreaterThan(decimal.Zero) {
report.Summary.Customers.Proportion = decimal.NewNullDecimal(
report.Summary.Customers.Consumption.Decimal.Div(report.Summary.Overall).RoundBank(15),
)
report.Summary.Publics.Proportion = decimal.NewNullDecimal(
report.Summary.Publics.Consumption.Decimal.Div(report.Summary.Overall).RoundBank(15),
)
}
// 计算线损
report.Summary.Loss = decimal.NewNullDecimal(
report.Summary.Overall.Sub(report.Summary.Publics.Consumption.Decimal).Sub(report.Summary.Customers.Consumption.Decimal),
)
report.Summary.LossFee = decimal.NewNullDecimal(
report.Summary.Loss.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(8),
)
if report.Summary.Overall.Abs().GreaterThan(decimal.Zero) {
report.Summary.LossProportion = decimal.NewNullDecimal(
report.Summary.Loss.Decimal.Div(report.Summary.Overall).RoundBank(15),
)
if report.Summary.LossProportion.Decimal.GreaterThan(decimal.NewFromFloat(0.1)) {
report.Summary.AuthorizeLoss = decimal.NewNullDecimal(
report.Summary.Overall.Mul(decimal.NewFromFloat(0.1)).RoundBank(8),
)
report.Summary.AuthorizeLossFee = decimal.NewNullDecimal(
report.Summary.AuthorizeLoss.Decimal.Mul(report.Summary.OverallPrice.Decimal).RoundBank(8),
)
} else {
report.Summary.AuthorizeLoss = report.Summary.Loss
report.Summary.AuthorizeLossFee = report.Summary.LossFee
}
}
if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) {
report.Summary.LossDilutedPrice = decimal.NewNullDecimal(
report.Summary.AuthorizeLossFee.Decimal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
}
// 计算基本电费和调整电费等的摊薄
if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) {
report.Summary.BasicDilutedPrice = decimal.NewNullDecimal(
report.Summary.BasicFee.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
report.Summary.AdjustDilutedPrice = decimal.NewNullDecimal(
report.Summary.AdjustFee.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
report.Summary.MaintenanceDilutedPrice = decimal.NewNullDecimal(
maintenanceFeeTotal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(8),
)
}
// 计算摊薄总计
report.Summary.MaintenanceOverall = decimal.NewNullDecimal(maintenanceFeeTotal)
report.Summary.FinalDilutedOverall = decimal.NewNullDecimal(
report.Summary.BasicFee.
Add(report.Summary.AdjustFee).
Add(report.Summary.AuthorizeLossFee.Decimal),
)
// 计算终端用户的全部摊薄内容
for _, eu := range report.EndUsers {
// 计算户表表计的摊薄内容
if report.Summary.Customers.Consumption.Decimal.Abs().GreaterThan(decimal.Zero) {
eu.OverallProportion = eu.Overall.Decimal.Div(report.Summary.Customers.Consumption.Decimal).RoundBank(15)
} else {
eu.OverallProportion = decimal.Zero
}
eu.BasicFeeDiluted = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.BasicDilutedPrice.Decimal).RoundBank(2),
)
eu.AdjustFeeDiluted = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.AdjustDilutedPrice.Decimal).RoundBank(2),
)
eu.LossDiluted = decimal.NewNullDecimal(
report.Summary.AuthorizeLoss.Decimal.Mul(eu.OverallProportion).RoundBank(8),
)
eu.LossFeeDiluted = decimal.NewNullDecimal(
eu.Overall.Decimal.Mul(report.Summary.LossDilutedPrice.Decimal).RoundBank(8),
)
eu.FinalDiluted = decimal.NewNullDecimal(
eu.BasicFeeDiluted.Decimal.
Add(eu.AdjustFeeDiluted.Decimal).
Add(eu.LossFeeDiluted.Decimal).
RoundBank(2),
)
eu.FinalCharge = decimal.NewNullDecimal(
eu.OverallFee.Decimal.Add(eu.FinalDiluted.Decimal).RoundBank(2),
)
}
// 向数据库保存报表概况以及终端用户摊薄结果
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return
}
_, err = tx.NewUpdate().Model(report.Summary).WherePK().Exec(ctx)
if err != nil {
tx.Rollback()
return
}
for _, eu := range report.EndUsers {
_, err = tx.NewUpdate().Model(eu).WherePK().Exec(ctx)
if err != nil {
tx.Rollback()
return
}
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return
}
cache.AbolishRelation(fmt.Sprintf("publicity:%s", reportId))
return
}

View File

@@ -0,0 +1,33 @@
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
}

View File

@@ -0,0 +1,10 @@
package calculate
import "electricity_bill_calc/model/calculate"
// / 合并所有的表计
type Key struct {
Code string
TenementID string
}
type MeterMap map[Key]calculate.Meter

View File

@@ -9,13 +9,6 @@ import (
"github.com/shopspring/decimal"
)
// / 合并所有的表计
type Key struct {
Code string
TenementID string
}
type MeterMap map[Key]calculate.Meter
func CollectMeters(tenements []calculate.PrimaryTenementStatistics, poolings []calculate.Meter, publics []calculate.Meter) (MeterMap, error) {
meters := make(MeterMap)
// Collect tenement meters

View File

@@ -1,72 +0,0 @@
package calculate
import (
"electricity_bill_calc/global"
"electricity_bill_calc/repository"
"github.com/doug-martin/goqu/v9"
)
type _ModService struct {
ds goqu.DialectWrapper
}
var ModService = _ModService{
ds: goqu.Dialect("postgres"),
}
func mainCalculateProcess(rid string) error {
// 计算所有已经启用的商铺面积总和,仅计算所有未迁出的商户的所有表计对应的商铺面积。
err := CalculateEnabledArea(tenementreports, &summary)
// 计算基本电费分摊、调整电费分摊、电费摊薄单价。
err = CalculatePrices(&summary)
// 收集目前所有已经处理的表计,统一对其进行摊薄计算。
collectMeters, err := CollectMeters(tenementreports, poolingmetersreports, parkmetersreports)
meters, err := collectMeters, err
if err != nil {
return err
}
// 根据核算报表中设置的摊薄内容,逐个表计进行计算
CalculateBasicPooling(report, summary, meters)
CalculateAdjustPooling(report, summary, meters)
CalculateLossPooling(report, summary, meters)
// 计算所有商户类型表计的全周期电量,并根据全周期电量计算共用过同一表计的商户的二次分摊比例。
CalculateTenementConsumptions(meters)
CalculateTenementPoolings(report, summary, meters, metersrelations)
// 计算商户的合计电费信息,并归总与商户相关联的表计记录
tenementCharges, err := CalculateTenementCharge(tenementReports, summary, meters, meterRelations)
if err != nil {
// 处理错误
}
// 从此处开始向数据库保存全部计算结果。
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, _ := global.DB.Begin(ctx)
err = repository.CalculateRepository.ClearReportContent(tx, report.Id)
if err != nil {
tx.Rollback(ctx)
return err
}
err = SaveSummary(tx, summary)
if err != nil {
tx.Rollback(ctx)
return err
}
err = SavePublics(tx, report, meters)
if err != nil {
tx.Rollback(ctx)
return err
}
err = SavePoolings(tx)
if err != nil {
tx.Rollback(ctx)
return err
}
err = SaveTenements(tx)
if err != nil {
tx.Rollback(ctx)
return err
}
tx.Commit(ctx)
}

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
}

View File

@@ -60,9 +60,13 @@ func SavePoolings(tx pgx.Tx, report model.ReportIndex, meters MeterMap, relation
if err != nil {
return err
}
tx.Commit(ctx)
return nil
}
func SaveTenements(tx pgx.Tx, report model.ReportIndex, tenement []calculate.PrimaryTenementStatistics, tc []*calculate.TenementCharge) error {
func SaveTenements(tx pgx.Tx, report model.ReportIndex, tenement []calculate.PrimaryTenementStatistics, tc []calculate.TenementCharge) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
var ts []model.Tenement
for _, r := range tenement {
ts = append(ts, r.Tenement)
@@ -71,5 +75,7 @@ func SaveTenements(tx pgx.Tx, report model.ReportIndex, tenement []calculate.Pri
if err != nil {
return err
}
tx.Commit(ctx)
return 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

@@ -1,18 +1,99 @@
package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"errors"
"github.com/shopspring/decimal"
"fmt"
)
// / 计算已经启用的商铺面积和
// /
// / - `tenements`:所有商户的电量信息
// / - `summary`:核算报表的摘要信息
func CalculateEnabledArea(tenements []calculate.PrimaryTenementStatistics, summary *calculate.Summary) error {
var areaMeters []calculate.Meter
// 计算已经启用的商铺面积和
func TotalConsumptionCalculate(tenements []calculate.PrimaryTenementStatistics, summary calculate.Summary) decimal.Decimal {
var areaMaters []calculate.Meter
for _, t := range tenements {
areaMaters = append(areaMaters, t.Meters...)
}
areaMaters = removeDuplicates(areaMaters)
var areaTotal float64
for _, m := range areaMaters {
areaTotal += m.Detail.Area.Decimal.InexactFloat64()
}
areaTotal += summary.OverallArea.InexactFloat64()
return decimal.NewFromFloat(areaTotal)
}
func removeDuplicates(meters []calculate.Meter) []calculate.Meter {
result := make([]calculate.Meter, 0, len(meters))
seen := make(map[string]bool)
for _, meter := range meters {
if !seen[meter.Code] {
seen[meter.Code] = true
result = append(result, meter)
}
}
return result
}
// 计算线损以及调整线损
func LossCalculate(report *model.ReportIndex, Public *[]calculate.Meter,
publicTotal *decimal.Decimal, summary *calculate.Summary) error {
summary.Loss = summary.Overall.Amount.Sub(summary.TotalConsumption)
var summaryAmount decimal.Decimal
if summary.Overall.Amount == decimal.Zero {
summaryAmount = decimal.NewFromFloat(1.0)
} else {
summaryAmount = summary.Overall.Amount
}
summary.LossProportion = summary.Loss.Div(summaryAmount)
var authorizedLossRate decimal.Decimal
//TODO: 2023.08.04 在此发现reportIndex结构体与数据库中的report表字段不对应缺少两个相应字段在此添加的如在其他地方有错误优先查找这里
if summary.LossProportion.InexactFloat64() > report.AuthorizedLossRate {
authorizedLossRate = summary.LossProportion
} else {
return errors.New(fmt.Sprintf("经过核算园区的线损率为:{%.8f} 核定线损率为:{%.8f}", summary.LossProportion.InexactFloat64(), authorizedLossRate.InexactFloat64()))
}
summary.AuthoizeLoss = model.ConsumptionUnit{
Amount: decimal.NewFromFloat(summary.Overall.Amount.InexactFloat64() * authorizedLossRate.InexactFloat64()),
Fee: decimal.NewFromFloat((summary.Overall.Amount.InexactFloat64() * authorizedLossRate.InexactFloat64()) * summary.Overall.Price.InexactFloat64()),
Price: summary.Overall.Price,
Proportion: authorizedLossRate,
}
differentialLoss := summary.LossDilutedPrice.Sub(summary.AuthoizeLoss.Amount)
if publicTotal.InexactFloat64() <= decimal.Zero.InexactFloat64() {
return errors.New("园区公共表计的电量总和为非正值,或者园区未设置公共表计,无法计算核定线损")
}
for _, meter := range *Public {
amountProportion := meter.Overall.Amount.InexactFloat64() / publicTotal.InexactFloat64()
adjustAmount := differentialLoss.InexactFloat64() * decimal.NewFromFloat(-1.0).InexactFloat64()
meter.AdjustLoss = model.ConsumptionUnit{
Amount: decimal.NewFromFloat(adjustAmount),
Fee: decimal.NewFromFloat(adjustAmount * summary.LossDilutedPrice.InexactFloat64()),
Price: summary.LossDilutedPrice,
Proportion: decimal.NewFromFloat(amountProportion),
}
}
return nil
}
// 计算已经启用的商铺面积和
func EnabledAreaCalculate(tenements *[]calculate.PrimaryTenementStatistics,
summary *calculate.Summary) (*decimal.Decimal, error) {
var areaMeters []calculate.Meter
for _, t := range *tenements {
areaMeters = append(areaMeters, t.Meters...)
}
// 去重
@@ -32,6 +113,7 @@ func CalculateEnabledArea(tenements []calculate.PrimaryTenementStatistics, summa
return &areaTotal, nil
}
// =================================================================================
// / 计算基本电费分摊、调整电费分摊以及电费摊薄单价。
// /
// / - `summary`:核算报表的摘要信息
@@ -52,4 +134,5 @@ func CalculatePrices(summary *calculate.Summary) error {
summary.AdjustPooledPriceArea = summary.AdjustFee.Div(summary.OverallArea)
}
return nil
}

View File

@@ -3,219 +3,456 @@ package calculate
import (
"electricity_bill_calc/model"
"electricity_bill_calc/model/calculate"
"electricity_bill_calc/repository"
"errors"
"fmt"
"github.com/shopspring/decimal"
"math/big"
"sort"
"strings"
"time"
"unsafe"
)
// / 计算各个商户的合计信息,并归总与商户关联的表计记录
func CalculateTenementCharge(tenements []calculate.PrimaryTenementStatistics, summary calculate.Summary, meters MeterMap, relations []model.MeterRelation) ([]calculate.TenementCharge, error) {
tenementCharges := make([]calculate.TenementCharge, 0)
// 核算园区中的全部商户表计电量用电
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 {
relatedMeters := make([]calculate.Meter, 0)
for _, meter := range tenement.Meters {
code := meter.Code
if meter.Detail.MeterType == model.METER_INSTALLATION_TENEMENT {
code = meter.Detail.Code
var meters []model.TenementMeter
for _, relation := range tenementMeterRelations {
if strings.EqualFold(relation.TenementId, tenement.Id) {
meters = append(meters, relation)
}
relatedMeter, ok := meters[Key{Code: code}]
if !ok {
return nil, errors.New("related meter not found")
}
relatedMeters = append(relatedMeters, relatedMeter)
}
// Calculate overall, critical, peak, flat, valley, etc.
//var overall, critical, peak, flat, valley model.ConsumptionUnit
basicPooled, adjustPooled, lossPooled, publicPooled := new(big.Rat), new(big.Rat), new(big.Rat), new(big.Rat)
lossAmount := new(big.Rat)
for _, meter := range relatedMeters {
overall.Add(overall, meter.Overall)
critical.Add(critical, meter.Critical)
peak.Add(peak, meter.Peak)
flat.Add(flat, meter.Flat)
valley.Add(valley, meter.Valley)
basicPooled.Add(basicPooled, meter.PooledBasic.Fee)
adjustPooled.Add(adjustPooled, meter.PooledAdjust.Fee)
lossAmount.Add(lossAmount, meter.AdjustLoss.Amount)
lossPooled.Add(lossPooled, meter.PooledLoss.Fee)
publicPooled.Add(publicPooled, meter.PooledPublic.Fee)
pt, err := determineTenementConsumptions(
tenement,
meters,
PeriodStart,
PeriodEnd,
tenementMeterReadings,
lastPeriodReadings,
meterDetails,
summary,
)
if err != nil {
return nil, err
}
// Update proportions and other data for related meters
for _, meter := range relatedMeters {
meter.Overall.Proportion = new(big.Rat).Quo(meter.Overall.Amount, overall.Amount)
meter.Critical.Proportion = new(big.Rat).Quo(meter.Critical.Amount, critical.Amount)
meter.Peak.Proportion = new(big.Rat).Quo(meter.Peak.Amount, peak.Amount)
meter.Flat.Proportion = new(big.Rat).Quo(meter.Flat.Amount, flat.Amount)
meter.Valley.Proportion = new(big.Rat).Quo(meter.Valley.Amount, valley.Amount)
meter.PooledBasic.Proportion = new(big.Rat).Quo(meter.PooledBasic.Fee, basicPooled)
meter.PooledAdjust.Proportion = new(big.Rat).Quo(meter.PooledAdjust.Fee, adjustPooled)
meter.PooledLoss.Proportion = new(big.Rat).Quo(meter.PooledLoss.Fee, lossPooled)
meter.PooledPublic.Proportion = new(big.Rat).Quo(meter.PooledPublic.Fee, publicPooled)
}
tenementCharges = append(tenementCharges, TenementCharges{
Tenement: tenement.Tenement.ID,
Overall: ConsumptionUnit{
Amount: overall.Amount,
Fee: new(big.Rat).Mul(overall.Amount, summary.Overall.Price),
Price: summary.Overall.Price,
Proportion: new(big.Rat).Quo(overall.Amount, summary.Overall.Amount),
},
Critical: ConsumptionUnit{
Amount: critical.Amount,
Fee: new(big.Rat).Mul(critical.Amount, summary.Critical.Price),
Price: summary.Critical.Price,
Proportion: new(big.Rat).Quo(critical.Amount, summary.Critical.Amount),
},
Peak: ConsumptionUnit{
Amount: peak.Amount,
Fee: new(big.Rat).Mul(peak.Amount, summary.Peak.Price),
Price: summary.Peak.Price,
Proportion: new(big.Rat).Quo(peak.Amount, summary.Peak.Amount),
},
Flat: ConsumptionUnit{
Amount: flat.Amount,
Fee: new(big.Rat).Mul(flat.Amount, summary.Flat.Price),
Price: summary.Flat.Price,
Proportion: new(big.Rat).Quo(flat.Amount, summary.Flat.Amount),
},
Valley: ConsumptionUnit{
Amount: valley.Amount,
Fee: new(big.Rat).Mul(valley.Amount, summary.Valley.Price),
Price: summary.Valley.Price,
Proportion: new(big.Rat).Quo(valley.Amount, summary.Valley.Amount),
},
Loss: ConsumptionUnit{
Amount: lossAmount,
Fee: new(big.Rat).Mul(lossPooled, summary.AuthorizeLoss.Price),
Price: summary.AuthorizeLoss.Price,
Proportion: new(big.Rat).Quo(lossAmount, summary.AuthorizeLoss.Amount),
},
BasicFee: basicPooled,
AdjustFee: adjustPooled,
LossPooled: lossPooled,
PublicPooled: publicPooled,
// ... 其他字段的初始化
})
tenementReports = append(tenementReports, pt)
}
return tenementCharges, nil
return tenementReports, nil
}
func calculateTenementCharge(
tenements []*PrimaryTenementStatistics,
summary *Summary,
meters MeterMap,
_relations []MeterRelation,
) ([]TenementCharges, error) {
var tenementCharges []TenementCharges
// 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)
}
// 计算各个商户的合计信息,并归总与商户关联的表计记录
func TenementChargeCalculate(tenements []calculate.PrimaryTenementStatistics,
summary calculate.Summary, meters MeterMap) []calculate.TenementCharge {
result := make(map[string][]string)
for _, t := range tenements {
meterCodes := make([]string, len(t.Meters))
for i, m := range t.Meters {
meterCodes[i] = m.Code
meterCodes := make([]string, 0)
for _, m := range t.Meters {
meterCodes = append(meterCodes, m.Code)
}
relatedMeters := make([]*Meter, len(meterCodes))
for i, code := range meterCodes {
relatedMeter, ok := meters[Key{Code: code, TenementID: t.Tenement.ID}]
if !ok {
// 处理未找到相关表计的情况
continue
}
relatedMeters[i] = relatedMeter
}
var overall, critical, peak, flat, valley ConsumptionUnit
var basicPooled, adjustPooled, lossAmount, lossPooled, publicPooled decimal.Decimal
for _, meter := range relatedMeters {
overall.Amount = overall.Amount.Add(meter.Overall.Amount)
overall.Fee = overall.Fee.Add(meter.Overall.Fee)
critical.Amount = critical.Amount.Add(meter.Critical.Amount)
critical.Fee = critical.Fee.Add(meter.Critical.Fee)
peak.Amount = peak.Amount.Add(meter.Peak.Amount)
peak.Fee = peak.Fee.Add(meter.Peak.Fee)
flat.Amount = flat.Amount.Add(meter.Flat.Amount)
flat.Fee = flat.Fee.Add(meter.Flat.Fee)
valley.Amount = valley.Amount.Add(meter.Valley.Amount)
valley.Fee = valley.Fee.Add(meter.Valley.Fee)
basicPooled = basicPooled.Add(meter.PooledBasic.Fee)
adjustPooled = adjustPooled.Add(meter.PooledAdjust.Fee)
lossAmount = lossAmount.Add(meter.AdjustLoss.Amount)
lossPooled = lossPooled.Add(meter.PooledLoss.Fee)
publicPooled = publicPooled.Add(meter.PooledPublic.Fee)
}
// 反写商户表计的统计数据
for _, meter := range relatedMeters {
meter.Overall.Proportion = meter.Overall.Amount.Div(overall.Amount)
meter.Critical.Proportion = meter.Critical.Amount.Div(critical.Amount)
meter.Peak.Proportion = meter.Peak.Amount.Div(peak.Amount)
meter.Flat.Proportion = meter.Flat.Amount.Div(flat.Amount)
meter.Valley.Proportion = meter.Valley.Amount.Div(valley.Amount)
meter.PooledBasic.Proportion = meter.PooledBasic.Fee.Div(basicPooled)
meter.PooledAdjust.Proportion = meter.PooledAdjust.Fee.Div(adjustPooled)
meter.PooledLoss.Proportion = meter.PooledLoss.Fee.Div(lossPooled)
meter.PooledPublic.Proportion = meter.PooledPublic.Fee.Div(publicPooled)
}
// 构造并添加商户的合计信息
tenementCharges = append(tenementCharges, TenementCharges{
Tenement: t.Tenement.ID,
Overall: ConsumptionUnit{
Price: summary.Overall.Price,
Proportion: overall.Amount.Div(summary.Overall.Amount),
Amount: overall.Amount,
Fee: overall.Fee,
},
Critical: ConsumptionUnit{
Price: summary.Critical.Price,
Proportion: critical.Amount.Div(summary.Critical.Amount),
Amount: critical.Amount,
Fee: critical.Fee,
},
Peak: ConsumptionUnit{
Price: summary.Peak.Price,
Proportion: peak.Amount.Div(summary.Peak.Amount),
Amount: peak.Amount,
Fee: peak.Fee,
},
Flat: ConsumptionUnit{
Price: summary.Flat.Price,
Proportion: flat.Amount.Div(summary.Flat.Amount),
Amount: flat.Amount,
Fee: flat.Fee,
},
Valley: ConsumptionUnit{
Price: summary.Valley.Price,
Proportion: valley.Amount.Div(summary.Valley.Amount),
Amount: valley.Amount,
Fee: valley.Fee,
},
Loss: ConsumptionUnit{
Price: summary.AuthorizeLoss.Price,
Proportion: lossAmount.Div(summary.AuthorizeLoss.Amount),
Amount: lossAmount,
Fee: lossPooled,
},
BasicFee: basicPooled,
AdjustFee: adjustPooled,
LossPooled: lossPooled,
PublicPooled: publicPooled,
FinalCharges: overall.Fee.Add(basicPooled).Add(adjustPooled).Add(lossPooled).Add(publicPooled),
Submeters: relatedMeters,
Poolings: make([]Meter, 0), // TODO: Add pooling logic here
})
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
return tenementCharges, nil
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
}

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,168 @@
package calculate
import (
"electricity_bill_calc/global"
"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
}
// 计算所有表计的总电量
parkTotal := TotalConsumptionCalculate(tenementReports, summary)
// 计算线损以及调整线损
err = LossCalculate(report, &parkMetersReports, &parkTotal, &summary)
if err != nil {
fmt.Println("9", err)
return
}
// 计算所有已经启用的商铺面积总和,仅计算所有未迁出的商户的所有表计对应的商铺面积。
_, err = EnabledAreaCalculate(&tenementReports, &summary)
if err != nil {
fmt.Println("10", err)
return
}
err = CalculatePrices(&summary)
if err != nil {
fmt.Println("11", err)
return
}
//===========================================================================
// 计算基本电费分摊、调整电费分摊、电费摊薄单价。
err = CalculatePrices(&summary)
// 收集目前所有已经处理的表计,统一对其进行摊薄计算。
meters, err := CollectMeters(tenementReports, poolingMetersReports, parkMetersReports)
if err != nil {
fmt.Println("12", err)
return
}
// 计算商户的合计电费信息,并归总与商户相关联的表计记录
tenementCharges := TenementChargeCalculate(tenementReports, summary, meters)
// 根据核算报表中设置的摊薄内容,逐个表计进行计算
err = CalculateBasicPooling(report, &summary, &meters)
if err != nil {
fmt.Println("13", err)
return
}
err = CalculateAdjustPooling(*report, summary, meters)
if err != nil {
fmt.Println("14", err)
return
}
err = CalculateLossPooling(*report, summary, meters)
if err != nil {
fmt.Println("15", err)
return
}
// 计算所有商户类型表计的全周期电量,并根据全周期电量计算共用过同一表计的商户的二次分摊比例。
_, err = CalculateTenementConsumptions(meters)
if err != nil {
fmt.Println("16", err)
return
}
err = CalculateTenementPoolings(*report, summary, meters, meterRelations)
if err != nil {
fmt.Println("17", err)
return
}
// 计算商户的合计电费信息,并归总与商户相关联的表计记录
tenementCharges = TenementChargeCalculate(tenementReports, summary, meters)
// 从此处开始向数据库保存全部计算结果。
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, _ := global.DB.Begin(ctx)
err = repository.CalculateRepository.ClearReportContent(tx, report.Id)
if err != nil {
tx.Rollback(ctx)
fmt.Println("18", err)
}
err = SaveSummary(tx, summary)
if err != nil {
tx.Rollback(ctx)
fmt.Println("19", err)
}
err = SavePublics(tx, *report, meters)
if err != nil {
tx.Rollback(ctx)
fmt.Println("20", err)
}
err = SavePoolings(tx, *report, meters, meterRelations)
if err != nil {
tx.Rollback(ctx)
fmt.Println("21", err)
}
err = SaveTenements(tx, *report, tenementReports, tenementCharges)
if err != nil {
tx.Rollback(ctx)
fmt.Println("22", err)
}
tx.Commit(ctx)
}

View File

@@ -1,479 +0,0 @@
package service
import (
"context"
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/excel"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"io"
"strconv"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _EndUserService struct {
l *zap.Logger
}
type MeterAppears struct {
Meter string
Appears int64
}
var EndUserService = _EndUserService{
l: logger.Named("Service", "EndUser"),
}
func (_EndUserService) SearchEndUserRecord(reportId, keyword string, page int) ([]model.EndUserDetail, int64, error) {
var (
conditions = make([]string, 0)
endUsers = make([]model.EndUserDetail, 0)
cond = global.DB.NewSelect().Model(&endUsers)
)
conditions = append(conditions, reportId, strconv.Itoa(page))
cond = cond.Where("report_id = ?", reportId)
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("customer_name like ?", keywordCond).
WhereOr("contact_name like ?", keywordCond).
WhereOr("contact_phone like ?", keywordCond).
WhereOr("meter_04kv_id like ?", keywordCond)
})
conditions = append(conditions, keyword)
}
if cachedTotal, err := cache.RetreiveCount("end_user_detail", conditions...); cachedTotal != -1 && err == nil {
if cachedEndUsers, _ := cache.RetreiveSearch[[]model.EndUserDetail]("end_user_detail", conditions...); cachedEndUsers != nil {
return *cachedEndUsers, cachedTotal, nil
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
Order("seq asc", "meter_04kv_id asc").
ScanAndCount(ctx)
relations := []string{"end_user", "report", "park"}
for _, eu := range endUsers {
relations = append(relations, fmt.Sprintf("end_user:%s:%s", eu.ReportId, eu.MeterId))
}
cache.CacheCount(relations, "end_user_detail", int64(total), conditions...)
cache.CacheSearch(endUsers, relations, "end_user_detail", conditions...)
return endUsers, int64(total), err
}
func (_EndUserService) AllEndUserRecord(reportId string) ([]model.EndUserDetail, error) {
if cachedEndUsers, _ := cache.RetreiveSearch[[]model.EndUserDetail]("end_user_detail", "report", reportId); cachedEndUsers != nil {
return *cachedEndUsers, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
users := make([]model.EndUserDetail, 0)
err := global.DB.NewSelect().Model(&users).
Where("report_id = ?", reportId).
Order("seq asc", "meter_04kv_id asc").
Scan(ctx)
relations := lo.Map(users, func(eu model.EndUserDetail, _ int) string {
return fmt.Sprintf("end_user:%s:%s", eu.ReportId, eu.MeterId)
})
relations = append(relations, "report", "park")
cache.CacheSearch(users, relations, "end_user_detail", "report", reportId)
return users, err
}
func (_EndUserService) FetchSpecificEndUserRecord(reportId, parkId, meterId string) (*model.EndUserDetail, error) {
if cachedEndUser, _ := cache.RetreiveEntity[model.EndUserDetail]("end_user_detail", fmt.Sprintf("%s_%s_%s", reportId, parkId, meterId)); cachedEndUser != nil {
return cachedEndUser, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
record := new(model.EndUserDetail)
err := global.DB.NewSelect().Model(record).
Where("report_id = ?", reportId).
Where("park_id = ?", parkId).
Where("meter_04kv_id = ?", meterId).
Scan(ctx)
cache.CacheEntity(record, []string{fmt.Sprintf("end_user:%s:%s", reportId, meterId), "report", "park"}, "end_user_detail", fmt.Sprintf("%s_%s_%s", reportId, parkId, meterId))
return record, err
}
func (_EndUserService) UpdateEndUserRegisterRecord(tx *bun.Tx, ctx *context.Context, record model.EndUserDetail) (err error) {
record.CalculatePeriod()
updateColumns := []string{
"current_period_overall",
"adjust_overall",
"current_period_critical",
"current_period_peak",
"current_period_flat",
"current_period_valley",
"adjust_critical",
"adjust_peak",
"adjust_flat",
"adjust_valley",
"overall",
"critical",
"peak",
"flat",
"valley",
}
if record.Initialize {
updateColumns = append(updateColumns,
"last_period_overall",
"last_period_critical",
"last_period_peak",
"last_period_flat",
"last_period_valley",
)
}
_, err = tx.NewUpdate().Model(&record).
WherePK().
Column(updateColumns...).
Exec(*ctx)
cache.AbolishRelation(fmt.Sprintf("end_user:%s:%s", record.ReportId, record.MeterId))
return
}
func (_EndUserService) newVirtualExcelAnalysisError(err error) *excel.ExcelAnalysisError {
return &excel.ExcelAnalysisError{Col: -1, Row: -1, Err: excel.AnalysisError{Err: err}}
}
func (es _EndUserService) BatchImportNonPVRegister(reportId string, file io.Reader) *exceptions.BatchError {
ctx, cancel := global.TimeoutContext(120)
defer cancel()
errs := exceptions.NewBatchError()
users, err := es.AllEndUserRecord(reportId)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
var reportDetail = new(model.Report)
err = global.DB.NewSelect().Model(reportDetail).
Where("id = ?", reportId).
Scan(ctx)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(fmt.Errorf("未能找到相应的报表。%w", err)))
return errs
}
meterAppers := make([]MeterAppears, 0)
err = global.DB.NewSelect().Model((*model.EndUserDetail)(nil)).
ColumnExpr("meter_04kv_id as meter").
ColumnExpr("count(*) as appears").
Where("park_id = ?", reportDetail.ParkId).
Group("meter_04kv_id").
Scan(ctx, &meterAppers)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
indexedUsers := lo.Reduce(
users,
func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail {
acc[elem.MeterId] = elem
return acc
},
make(map[string]model.EndUserDetail, 0),
)
analyzer, err := excel.NewEndUserNonPVExcelAnalyzer(file)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
imports, excelErrs := analyzer.Analysis(*new(model.EndUserImport))
if len(excelErrs) > 0 {
for _, e := range excelErrs {
errs.AddError(e)
}
return errs
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
for _, im := range imports {
if elem, ok := indexedUsers[im.MeterId]; ok {
if appears, has := lo.Find(meterAppers, func(m MeterAppears) bool {
return m.Meter == elem.MeterId
}); has {
if appears.Appears <= 1 {
elem.LastPeriodOverall = im.LastPeriodOverall
elem.LastPeriodCritical = decimal.Zero
elem.LastPeriodPeak = decimal.Zero
elem.LastPeriodValley = decimal.Zero
elem.LastPeriodFlat = elem.LastPeriodOverall.Sub(elem.LastPeriodCritical).Sub(elem.LastPeriodPeak).Sub(elem.LastPeriodValley)
elem.Initialize = true
}
}
elem.CurrentPeriodOverall = im.CurrentPeriodOverall
elem.AdjustOverall = im.AdjustOverall
elem.CurrentPeriodCritical = decimal.Zero
elem.CurrentPeriodPeak = decimal.Zero
elem.CurrentPeriodValley = decimal.Zero
elem.CurrentPeriodFlat = elem.CurrentPeriodOverall.Sub(elem.CurrentPeriodCritical).Sub(elem.CurrentPeriodPeak).Sub(elem.CurrentPeriodValley)
elem.AdjustCritical = decimal.Zero
elem.AdjustPeak = decimal.Zero
elem.AdjustValley = decimal.Zero
elem.AdjustFlat = elem.AdjustOverall.Sub(elem.AdjustCritical).Sub(elem.AdjustPeak).Sub(elem.AdjustValley)
err := es.UpdateEndUserRegisterRecord(&tx, &ctx, elem)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
} else {
errs.AddError(exceptions.NewNotFoundError(fmt.Sprintf("表计 %s 未找到", im.MeterId)))
}
}
if errs.Len() > 0 {
tx.Rollback()
return errs
}
err = tx.Commit()
if err != nil {
tx.Rollback()
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
cache.AbolishRelation("end_user_detail")
return errs
}
func (es _EndUserService) BatchImportPVRegister(reportId string, file io.Reader) *exceptions.BatchError {
ctx, cancel := global.TimeoutContext(120)
defer cancel()
errs := exceptions.NewBatchError()
users, err := es.AllEndUserRecord(reportId)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
var reportDetail = new(model.Report)
err = global.DB.NewSelect().Model(reportDetail).Where("id = ?", reportId).Scan(ctx)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(fmt.Errorf("未能找到相应的报表。%w", err)))
return errs
}
meterAppers := make([]MeterAppears, 0)
err = global.DB.NewSelect().Model((*model.EndUserDetail)(nil)).
ColumnExpr("meter_04kv_id as meter").
ColumnExpr("count(*) as appears").
Where("park_id = ?", reportDetail.Id).
Group("meter_04kv_id").
Scan(ctx, &meterAppers)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
indexedUsers := lo.Reduce(
users,
func(acc map[string]model.EndUserDetail, elem model.EndUserDetail, index int) map[string]model.EndUserDetail {
acc[elem.MeterId] = elem
return acc
},
make(map[string]model.EndUserDetail, 0),
)
analyzer, err := excel.NewEndUserPVExcelAnalyzer(file)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
imports, excelErrs := analyzer.Analysis(*new(model.EndUserImport))
if len(excelErrs) > 0 {
for _, e := range excelErrs {
errs.AddError(e)
}
return errs
}
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
return errs
}
for _, im := range imports {
if elem, ok := indexedUsers[im.MeterId]; ok {
if appears, has := lo.Find(meterAppers, func(m MeterAppears) bool {
return m.Meter == elem.MeterId
}); has {
if appears.Appears <= 1 {
elem.LastPeriodOverall = im.LastPeriodOverall
elem.LastPeriodCritical = im.LastPeriodCritical.Decimal
elem.LastPeriodPeak = im.LastPeriodPeak.Decimal
elem.LastPeriodValley = im.LastPeriodValley.Decimal
elem.LastPeriodFlat = elem.LastPeriodOverall.Sub(elem.LastPeriodCritical).Sub(elem.LastPeriodPeak).Sub(elem.LastPeriodValley)
elem.Initialize = true
}
}
elem.CurrentPeriodOverall = im.CurrentPeriodOverall
elem.AdjustOverall = im.AdjustOverall
elem.CurrentPeriodCritical = im.CurrentPeriodCritical.Decimal
elem.CurrentPeriodPeak = im.CurrentPeriodPeak.Decimal
elem.CurrentPeriodValley = im.CurrentPeriodValley.Decimal
elem.CurrentPeriodFlat = elem.CurrentPeriodOverall.Sub(elem.CurrentPeriodCritical).Sub(elem.CurrentPeriodPeak).Sub(elem.CurrentPeriodValley)
elem.AdjustCritical = im.AdjustCritical.Decimal
elem.AdjustPeak = im.AdjustPeak.Decimal
elem.AdjustValley = im.AdjustValley.Decimal
elem.AdjustFlat = elem.AdjustOverall.Sub(elem.AdjustCritical).Sub(elem.AdjustPeak).Sub(elem.AdjustValley)
err := es.UpdateEndUserRegisterRecord(&tx, &ctx, elem)
if err != nil {
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
} else {
errs.AddError(es.newVirtualExcelAnalysisError(exceptions.NewNotFoundError(fmt.Sprintf("表计 %s 未找到", im.MeterId))))
}
}
if errs.Len() > 0 {
tx.Rollback()
return errs
}
err = tx.Commit()
if err != nil {
tx.Rollback()
errs.AddError(es.newVirtualExcelAnalysisError(err))
}
cache.AbolishRelation("end_user_detail")
return errs
}
func (es _EndUserService) StatEndUserRecordInPeriod(requestUser, requestPark, startDate, endDate string) ([]model.EndUserPeriodStat, error) {
var (
conditions = make([]string, 0)
relations = []string{
fmt.Sprintf("park:%s", requestPark),
"end_user_detail",
}
cond = global.DB.NewSelect().
Model((*model.EndUserDetail)(nil)).
Relation("Report", func(sq *bun.SelectQuery) *bun.SelectQuery {
return sq.ExcludeColumn("*")
}).
Relation("Park", func(sq *bun.SelectQuery) *bun.SelectQuery {
return sq.ExcludeColumn("*")
})
)
if len(requestUser) > 0 {
cond = cond.Where("park.user_id = ?", requestUser)
conditions = append(conditions, requestUser)
} else {
conditions = append(conditions, "_")
}
if len(requestPark) > 0 {
cond = cond.Where("eud.park_id = ?", requestPark)
conditions = append(conditions, requestPark)
} else {
conditions = append(conditions, "_")
}
if len(startDate) > 0 {
parseTime, err := time.Parse("2006-01", startDate)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("不能解析给定的参数[startDate]%w", err)
}
start := model.NewDate(parseTime)
cond = cond.Where("report.period >= ?::date", start.ToString())
conditions = append(conditions, startDate)
} else {
conditions = append(conditions, "_")
}
if len(endDate) > 0 {
parseTime, err := time.Parse("2006-01", endDate)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("不能解析给定的参数[endDate]%w", err)
}
end := model.NewDate(parseTime)
cond = cond.Where("report.period <= ?::date", end.ToString())
conditions = append(conditions, endDate)
}
if cached, err := cache.RetreiveSearch[[]model.EndUserPeriodStat]("end_user_stat", conditions...); cached != nil && err == nil {
return *cached, nil
}
ctx, cancel := global.TimeoutContext(120)
defer cancel()
var endUserSums []model.EndUserPeriodStat
err := cond.Column("eud.meter_04kv_id", "eud.park_id").
ColumnExpr("sum(?) as overall", bun.Ident("eud.overall")).
ColumnExpr("sum(?) as overall_fee", bun.Ident("eud.overall_fee")).
ColumnExpr("sum(?) as critical", bun.Ident("eud.critical")).
ColumnExpr("sum(?) as critical_fee", bun.Ident("eud.critical_fee")).
ColumnExpr("sum(?) as peak", bun.Ident("eud.peak")).
ColumnExpr("sum(?) as peak_fee", bun.Ident("eud.peak_fee")).
ColumnExpr("sum(?) as valley", bun.Ident("eud.valley")).
ColumnExpr("sum(?) as valley_fee", bun.Ident("eud.valley_fee")).
ColumnExpr("sum(?) as final_diluted", bun.Ident("eud.final_diluted")).
Where("report.published = ?", true).
Group("eud.meter_04kv_id", "eud.park_id").
Scan(ctx, &endUserSums)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("未能完成终端用户在指定期限内的统计,%w", err)
}
parkIds := lo.Reduce(
endUserSums,
func(acc mapset.Set[string], elem model.EndUserPeriodStat, _ int) mapset.Set[string] {
acc.Add(elem.ParkId)
return acc
},
mapset.NewSet[string](),
)
meterArchives := make([]model.Meter04KV, 0)
if len(parkIds.ToSlice()) > 0 {
err = global.DB.NewSelect().
Model(&meterArchives).Relation("ParkDetail").
Where("park_id in (?)", bun.In(parkIds.ToSlice())).
Scan(ctx)
if err != nil {
return make([]model.EndUserPeriodStat, 0), fmt.Errorf("未能获取到终端表计的最新基础档案,%w", err)
}
}
filledStats := lo.Map(
endUserSums,
func(elem model.EndUserPeriodStat, _ int) model.EndUserPeriodStat {
archive, has := lo.Find(meterArchives, func(meter model.Meter04KV) bool {
return meter.Code == elem.MeterId
})
if has {
if archive.Address != nil {
elem.Address = *archive.Address
} else {
elem.Address = ""
}
if archive.CustomerName != nil {
elem.CustomerName = *archive.CustomerName
} else {
elem.CustomerName = ""
}
elem.IsPublicMeter = archive.IsPublicMeter
elem.Kind = archive.ParkDetail.SubmeterType
}
if elem.OverallFee.Valid && elem.AdjustFee.Valid && !elem.OverallFee.Decimal.IsZero() {
elem.AdjustProportion = decimal.NewNullDecimal(
elem.AdjustFee.Decimal.Div(elem.OverallFee.Decimal).RoundBank(8),
)
} else {
elem.AdjustProportion = decimal.NullDecimal{}
}
return elem
},
)
cache.CacheSearch(filledStats, relations, "end_user_stat", conditions...)
return filledStats, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,295 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
mapset "github.com/deckarep/golang-set/v2"
"github.com/google/uuid"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _MaintenanceFeeService struct {
l *zap.Logger
}
var MaintenanceFeeService = _MaintenanceFeeService{
l: logger.Named("Service", "maintenance"),
}
func (_MaintenanceFeeService) ListMaintenanceFees(pid []string, period string, requestPage int) ([]model.MaintenanceFee, int64, error) {
conditions := []string{fmt.Sprintf("%d", requestPage)}
conditions = append(conditions, pid...)
conditions = append(conditions, period)
if cachedTotal, err := cache.RetreiveCount("maintenance_fee", conditions...); cachedTotal != -1 && err == nil {
if fees, _ := cache.RetreiveSearch[[]model.MaintenanceFee]("maintenance_fee", conditions...); fees != nil {
return *fees, cachedTotal, nil
}
}
var (
fees = make([]model.MaintenanceFee, 0)
cond = global.DB.NewSelect().Model(&fees)
)
if len(pid) > 0 {
cond = cond.Where("park_id in (?)", bun.In(pid))
} else {
return make([]model.MaintenanceFee, 0), 0, exceptions.NewIllegalArgumentsError("必须给定所要请求的至少一个园区", "park_id")
}
if len(period) > 0 {
cond = cond.Where("period = ?", period)
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.Order("period desc", "created_at desc").
Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx)
if err != nil {
return make([]model.MaintenanceFee, 0), 0, fmt.Errorf("附加费查询出现错误,%w", err)
}
relations := lo.Map(fees, func(f model.MaintenanceFee, _ int) string {
return fmt.Sprintf("maintenance_fee:%s", f.Id)
})
relations = append(relations, "maintenance_fee", "park")
cache.CacheCount(relations, "maintenance_fee", int64(total), conditions...)
cache.CacheSearch(fees, relations, "maintenance_fee", conditions...)
return fees, int64(total), nil
}
func (_MaintenanceFeeService) CreateMaintenanceFeeRecord(fee model.MaintenanceFee) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
fee.Id = uuid.New().String()
fee.Enabled = true
_, err := global.DB.NewInsert().Model(&fee).Exec(ctx)
if err != nil {
return err
}
cache.AbolishRelation("maintenance_fee")
return nil
}
func (_MaintenanceFeeService) ModifyMaintenanceFee(fee model.MaintenanceFee) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model(&fee).
WherePK().
Column("fee", "memo").
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到匹配的附加费记录。")
} else {
return err
}
}
cache.AbolishRelation("maintenance_fee")
cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fee.Id))
return nil
}
func (_MaintenanceFeeService) ChangeMaintenanceFeeState(fid string, state bool) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model((*model.MaintenanceFee)(nil)).
Where("id = ?", fid).
Set("enabled = ?", state).
Exec(ctx)
if err != nil {
if rows, err := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到匹配的附加费记录。")
} else {
return err
}
}
cache.AbolishRelation("maintenance_fee")
cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fid))
return nil
}
func (_MaintenanceFeeService) DeleteMaintenanceFee(fid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewDelete().Model((*model.MaintenanceFee)(nil)).
Where("id = ?", fid).
Exec(ctx)
if err != nil {
if rows, err := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到匹配的附加费记录。")
} else {
return err
}
}
cache.AbolishRelation("maintenance_fee")
cache.AbolishRelation(fmt.Sprintf("maintenance_fee:%s", fid))
return nil
}
func (_MaintenanceFeeService) EnsureFeeBelongs(uid, mid string) (bool, error) {
if has, _ := cache.CheckExists("maintenance_fee", mid, uid); has {
return true, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
parks := make([]model.Park, 0)
err := global.DB.NewSelect().Model(&parks).
Relation("MaintenanceFees").
Where("p.user_id = ?", uid).
Scan(ctx)
if err != nil {
return false, err
}
exists := lo.Reduce(parks, func(acc bool, elem model.Park, _ int) bool {
for _, e := range elem.MaintenanceFees {
if e.Id == mid {
return acc || true
}
}
return acc || false
}, false)
if !exists {
return false, exceptions.NewNotFoundError("指定附加费所属园区未找到。")
}
cache.CacheExists([]string{fmt.Sprintf("maintenance_fee:%s", mid), "maintenance_fee", "park"}, "maintenance_fee", mid, uid)
return exists, nil
}
type _FeeStat struct {
ParkId string
Period string
Total decimal.Decimal
}
func (f _MaintenanceFeeService) QueryAdditionalCharges(uid, pid, period, keyword string, requestPage int) ([]model.AdditionalCharge, int64, error) {
var (
conditions = []string{fmt.Sprintf("%d", requestPage)}
statFees = make([]_FeeStat, 0)
cond = global.DB.NewSelect().
Model((*model.MaintenanceFee)(nil)).
Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.ExcludeColumn("*")
}).
Relation("Park.Enterprise", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.ExcludeColumn("*")
}).
Where("m.enabled = ?", true)
)
if len(uid) > 0 {
cond = cond.Where("park__enterprise.id = ?", uid)
conditions = append(conditions, uid)
} else {
conditions = append(conditions, "_")
}
if len(pid) > 0 {
cond = cond.Where("park.id = ?", pid)
conditions = append(conditions, pid)
} else {
conditions = append(conditions, "_")
}
if len(period) > 0 {
cond = cond.Where("m.period = ?", period)
conditions = append(conditions, period)
} else {
conditions = append(conditions, "_")
}
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("park__enterprise.name like ?", keywordCond).
WhereOr("park__enterprise.abbr like ?", keywordCond).
WhereOr("park.name like ?", keywordCond).
WhereOr("park.abbr like ?", keywordCond).
WhereOr("park.address like ?", keywordCond)
})
conditions = append(conditions, keyword)
} else {
conditions = append(conditions, "_")
}
if cachedTotal, err := cache.RetreiveCount("additional_charge", conditions...); cachedTotal != -1 && err == nil {
if cachedData, _ := cache.RetreiveSearch[[]model.AdditionalCharge]("additional_charge", conditions...); cachedData != nil {
return *cachedData, cachedTotal, nil
}
}
ctx, cancel := global.TimeoutContext(24)
defer cancel()
startItem := (requestPage - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.ColumnExpr("sum(?) as total", bun.Ident("fee")).
Column("park_id", "period").
Group("park_id", "period").
Order("period desc").
Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx, &statFees)
if err != nil {
return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取附加费统计信息出现错误,%w", err)
}
parkIds := lo.Reduce(
statFees,
func(acc mapset.Set[string], elem _FeeStat, _ int) mapset.Set[string] {
acc.Add(elem.ParkId)
return acc
},
mapset.NewSet[string](),
)
parks := make([]model.Park, 0)
if len(parkIds.ToSlice()) > 0 {
err = global.DB.NewSelect().Model(&parks).Relation("Enterprise").
Where("p.id in (?)", bun.In(parkIds.ToSlice())).
Scan(ctx)
if err != nil {
return make([]model.AdditionalCharge, 0), 0, fmt.Errorf("获取园区信息出现错误,%w", err)
}
}
assembledStat := lo.Reduce(
statFees,
func(acc []model.AdditionalCharge, elem _FeeStat, _ int) []model.AdditionalCharge {
park, has := lo.Find(parks, func(p model.Park) bool {
return p.Id == elem.ParkId
})
f.l.Debug("Park detection.", zap.Bool("has", has), zap.Any("park", park))
if has {
if !park.Area.Valid || park.Area.Decimal.Equal(decimal.Zero) {
return acc
}
price := elem.Total.Div(park.Area.Decimal).RoundBank(8)
return append(acc, model.AdditionalCharge{
ParkId: elem.ParkId,
Period: elem.Period,
Fee: elem.Total,
Price: price,
QuarterPrice: price.Div(decimal.NewFromInt(4)),
SemiAnnualPrice: price.Div(decimal.NewFromInt(2)),
Enterprise: model.FromUserDetail(*park.Enterprise),
Park: park,
})
} else {
return acc
}
},
make([]model.AdditionalCharge, 0),
)
cache.CacheCount([]string{"maintenance_fee"}, "additional_charge", int64(total), conditions...)
cache.CacheSearch(assembledStat, []string{"maintenance_fee"}, "additional_charge", conditions...)
return assembledStat, int64(total), nil
}

View File

@@ -1,231 +0,0 @@
package service
import (
"context"
"database/sql"
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/excel"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"strconv"
mapset "github.com/deckarep/golang-set/v2"
"github.com/samber/lo"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _Meter04kVService struct {
l *zap.Logger
}
var Meter04kVService = _Meter04kVService{
l: logger.Named("Service", "Meter04KV"),
}
func (_Meter04kVService) ListMeterDetail(park, keyword string, page int) ([]model.Meter04KV, int64, error) {
var (
condition = make([]string, 0)
meters = make([]model.Meter04KV, 0)
)
cond := global.DB.NewSelect().Model(&meters).
Where("park_id = ?", park)
condition = append(condition, park, strconv.Itoa(page))
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("address like ?", keywordCond).
WhereOr("code like ?", keywordCond).
WhereOr("customer_name like ?", keywordCond).
WhereOr("contact_name like ?", keywordCond).
WhereOr("contact_phone like ?", keywordCond)
})
condition = append(condition, keyword)
}
if cachedTotal, err := cache.RetreiveCount("meter_04kv", condition...); cachedTotal != -1 && err == nil {
if cachedMeters, _ := cache.RetreiveSearch[[]model.Meter04KV]("meter_04kv", condition...); cachedMeters != nil {
return *cachedMeters, cachedTotal, nil
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.
Order("seq asc", "code asc").
Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx)
relations := lo.Map(meters, func(m model.Meter04KV, _ int) string {
return fmt.Sprintf("meter_04kv:%s:%s", m.ParkId, m.Code)
})
relations = append(relations, "meter_04kv", "park")
cache.CacheCount(relations, "meter_04kv", int64(total), condition...)
cache.CacheSearch(meters, relations, "meter_04kv", condition...)
return meters, int64(total), err
}
func (_Meter04kVService) Get04kVMeterDetail(park, code string) (*model.Meter04KV, error) {
if cachedMeter, _ := cache.RetreiveEntity[model.Meter04KV]("meter_04kv", fmt.Sprintf("%s_%s", park, code)); cachedMeter != nil {
return cachedMeter, nil
}
var meter = new(model.Meter04KV)
ctx, cancel := global.TimeoutContext()
defer cancel()
err := global.DB.NewSelect().Model(meter).
Where("code = ?", code).
Where("park_id = ?", park).
Scan(ctx)
if err != nil {
return nil, err
}
cache.CacheEntity(meter, []string{fmt.Sprintf("meter_04kv:%s:%s", park, code), "park"}, "meter_04kv", fmt.Sprintf("%s_%s", park, code))
return meter, nil
}
func (_Meter04kVService) insertNewMeter(tx *bun.Tx, ctx *context.Context, meter model.Meter04KV) error {
_, err := tx.NewInsert().Model(&meter).Exec(*ctx)
if err != nil {
tx.Rollback()
}
cache.AbolishRelation("meter_04kv")
return err
}
func (_Meter04kVService) updateMeter(tx *bun.Tx, ctx *context.Context, meter model.Meter04KV) error {
_, err := tx.NewUpdate().Model(&meter).
Where("code = ?", meter.Code).
Where("park_id = ?", meter.ParkId).
Column("address", "customer_name", "contact_name", "contact_phone", "ratio", "seq", "public_meter", "enabled").
Exec(*ctx)
if err != nil {
tx.Rollback()
}
cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code))
return err
}
func (m _Meter04kVService) CreateSingleMeter(meter model.Meter04KV) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
err = m.insertNewMeter(&tx, &ctx, meter)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation("meter_04kv")
cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code))
return nil
}
func (m _Meter04kVService) UpdateSingleMeter(meter *model.Meter04KV) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
err = m.updateMeter(&tx, &ctx, *meter)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation(fmt.Sprintf("meter_04kv:%s:%s", meter.ParkId, meter.Code))
return nil
}
func (_Meter04kVService) DuplicateMeterCodeValidate(meters []model.Meter04KV) []excel.ExcelAnalysisError {
errs := make([]excel.ExcelAnalysisError, 0)
for i := 0; i < len(meters); i++ {
for j := i + 1; j < len(meters); j++ {
if meters[j].Code == meters[i].Code {
errs = append(errs, excel.ExcelAnalysisError{Row: j + 1, Col: 0, Err: excel.AnalysisError{Err: fmt.Errorf("第 %d 行表计表号与第 %d 行表计表号重复!", j+1, i+1)}})
}
}
}
return errs
}
func (m _Meter04kVService) BatchCreateMeter(meters []model.Meter04KV) error {
parkIds := lo.Reduce(meters, func(acc mapset.Set[string], elem model.Meter04KV, index int) mapset.Set[string] {
acc.Add(elem.ParkId)
return acc
}, mapset.NewSet[string]())
if parkIds.Cardinality() > 1 {
return fmt.Errorf("一次只能向同一个园区中添加0.4kV表计。")
}
parkId, _ := parkIds.Pop()
ctx, cancel := global.TimeoutContext(120)
defer cancel()
allMeterCodes := make([]string, 0)
err := global.DB.NewSelect().Model((*model.Meter04KV)(nil)).
Where("park_id = ?", parkId).
Column("code").
Scan(ctx, &allMeterCodes)
if err != nil {
return err
}
meterCodes := mapset.NewSet(allMeterCodes...)
tx, err := global.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
var (
updates = make([]model.Meter04KV, 0)
inserts = make([]model.Meter04KV, 0)
)
for _, meter := range meters {
if meterCodes.Contains(meter.Code) {
updates = append(updates, meter)
} else {
inserts = append(inserts, meter)
}
}
if len(updates) > 0 {
_, err = tx.NewUpdate().Model(&updates).
Column("address", "customer_name", "contact_name", "contact_phone", "ratio", "seq", "public_meter", "enabled").
Bulk().
Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
}
if len(inserts) > 0 {
_, err = tx.NewInsert().Model(&inserts).Exec(ctx)
if err != nil {
tx.Rollback()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Rollback()
return err
}
cache.AbolishRelation("meter_04kv")
return nil
}

View File

@@ -1,171 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type _ParkService struct {
l *zap.Logger
}
var ParkService = _ParkService{
l: logger.Named("Service", "Park"),
}
func (_ParkService) SaveNewPark(park model.Park) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
_, err := global.DB.NewInsert().Model(&park).Exec(ctx)
if err != nil {
return err
}
cache.AbolishRelation("park")
return nil
}
func (_ParkService) UpdateParkInfo(park *model.Park) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model(park).
Where("id = ?", park.Id).
Where("user_id = ?", park.UserId).
Column("name", "abbr", "region", "area", "address", "contact", "phone", "capacity", "tenement_quantity", "category", "meter_04kv_type").
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到符合条件的园区。")
} else {
return err
}
}
cache.AbolishRelation(fmt.Sprintf("park:%s", park.Id))
return nil
}
func (_ParkService) ChangeParkState(uid, pid string, state bool) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewUpdate().Model((*model.Park)(nil)).
Where("id = ?", pid).
Where("user_id = ?", uid).
Set("enabled = ?", state).
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到符合条件的园区。")
} else {
return err
}
}
cache.AbolishRelation(fmt.Sprintf("park:%s", pid))
return nil
}
func (_ParkService) DeletePark(uid, pid string) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
res, err := global.DB.NewDelete().Model((*model.Park)(nil)).
Where("id = ?", pid).
Where("user_id = ?", uid).
Exec(ctx)
if err != nil {
if rows, _ := res.RowsAffected(); rows == 0 {
return exceptions.NewNotFoundError("未能找到符合条件的园区。")
} else {
return err
}
}
cache.AbolishRelation("park")
cache.AbolishRelation(fmt.Sprintf("park:%s", pid))
return nil
}
func (_ParkService) ListAllParkBelongsTo(uid, keyword string) ([]model.Park, error) {
if parks, _ := cache.RetreiveSearch[[]model.Park]("park", "belong", uid, keyword); parks != nil {
return *parks, nil
}
parks := make([]model.Park, 0)
cond := global.DB.NewSelect().Model(&parks).
Where("user_id = ?", uid)
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("name like ?", keywordCond).
WhereOr("abbr like ?", keywordCond).
WhereOr("address like ?", keywordCond).
WhereOr("contact like ?", keywordCond).
WhereOr("phone like ?", keywordCond)
})
}
ctx, cancel := global.TimeoutContext()
defer cancel()
err := cond.Scan(ctx)
if err != nil {
return make([]model.Park, 0), err
}
relations := []string{"park"}
for _, p := range parks {
relations = append(relations, fmt.Sprintf("park:%s", p.Id))
}
cache.CacheSearch(parks, relations, "park", "belong", uid, keyword)
return parks, nil
}
func (_ParkService) FetchParkDetail(pid string) (*model.Park, error) {
if park, _ := cache.RetreiveEntity[model.Park]("park", pid); park != nil {
return park, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var park = new(model.Park)
err := global.DB.NewSelect().Model(park).
Where("id = ?", pid).
Scan(ctx)
if err != nil {
return nil, exceptions.NewNotFoundErrorFromError("未找到符合条件的园区记录。", err)
}
cache.CacheEntity(*park, []string{fmt.Sprintf("park:%s", pid)}, "park", pid)
return park, nil
}
func (_ParkService) EnsurePark(uid, pid string) (bool, error) {
if has, _ := cache.CheckExists("park", pid, uid); has {
return has, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
has, err := global.DB.NewSelect().Model((*model.Park)(nil)).
Where("id = ?", pid).
Where("user_id = ?", uid).
Exists(ctx)
if has {
cache.CacheExists([]string{fmt.Sprintf("park:%s", pid)}, "park", pid, uid)
}
return has, err
}
func (_ParkService) AllParkIds(uid string) ([]string, error) {
if ids, _ := cache.RetreiveSearch[[]string]("park", "belong", uid); ids != nil {
return *ids, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var ids = make([]string, 0)
err := global.DB.NewSelect().Model((*model.Park)(nil)).
Where("user_id = ?", uid).
Column("id").
Scan(ctx, &ids)
if err != nil {
return make([]string, 0), err
}
cache.CacheSearch(ids, []string{"park"}, "park", "belong", uid)
return ids, nil
}

View File

@@ -1,70 +0,0 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _RegionService struct {
l *zap.Logger
}
var RegionService = _RegionService{
l: logger.Named("Service", "Region"),
}
func (_RegionService) FetchSubRegions(parent string) ([]model.Region, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
if regions, _ := cache.RetreiveSearch[[]model.Region]("region", "parent", parent); regions != nil {
return *regions, nil
}
regions := make([]model.Region, 0)
err := global.DB.NewSelect().Model(&regions).Where("parent = ?", parent).Order("code asc").Scan(ctx)
if err != nil {
return make([]model.Region, 0), err
}
relationNames := lo.Map(regions, func(r model.Region, index int) string {
return fmt.Sprintf("region:%s", r.Code)
})
cache.CacheSearch(regions, relationNames, "region", "parent", parent)
return regions, err
}
func (r _RegionService) FetchAllParentRegions(code string) ([]model.Region, error) {
regions := make([]model.Region, 0)
region, err := r.fetchRegion(code)
if err != nil {
return regions, err
}
regions = append(regions, *region)
for region.Level > 1 {
region, err = r.fetchRegion(region.Parent)
if err != nil {
return make([]model.Region, 0), nil
}
regions = append(regions, *region)
}
return regions, nil
}
func (_RegionService) fetchRegion(code string) (*model.Region, error) {
ctx, cancel := global.TimeoutContext()
defer cancel()
if cachedRegion, _ := cache.RetreiveSearch[model.Region]("region", code); cachedRegion != nil {
return cachedRegion, nil
}
region := new(model.Region)
err := global.DB.NewSelect().Model(region).Where("code = ?", code).Scan(ctx)
if err != nil {
relationName := fmt.Sprintf("region:%s", code)
cache.CacheSearch(region, []string{relationName}, "region", code)
}
return region, err
}

View File

@@ -1,94 +1,74 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"github.com/samber/lo"
"github.com/uptrace/bun"
"github.com/doug-martin/goqu/v9"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _StatisticsService struct {
l *zap.Logger
l *zap.Logger
ss goqu.DialectWrapper
}
var StatisticsService = _StatisticsService{
l: logger.Named("Service", "Stat"),
logger.Named("Service", "Stat"),
goqu.Dialect("postgres"),
}
func (_StatisticsService) EnabledEnterprises() (int64, error) {
if cachedCount, err := cache.RetreiveCount("enabled_ent"); cachedCount != -1 && err == nil {
return cachedCount, nil
}
//用于统计企业用户数量
func (ss _StatisticsService) EnabledEnterprises() (int64, error) {
ss.l.Info("开始统计企业数量。")
ctx, cancel := global.TimeoutContext()
defer cancel()
c, err := global.DB.NewSelect().Model((*model.User)(nil)).
Where("type = ?", model.USER_TYPE_ENT).
Where("enabled = ?", true).
Count(ctx)
if err == nil {
cache.CacheCount([]string{"user"}, "enabled_ent", int64(c))
}
return int64(c), err
}
UserCountQuery, UserCountQueryArgs, _ := ss.ss.
From(goqu.T("user")).
Where(goqu.I("type").Eq(model.USER_TYPE_ENT)).
Where(goqu.I("enabled").Eq(true)).
Select(goqu.COUNT("*")).ToSQL()
func (_StatisticsService) EnabledParks(userIds ...string) (int64, error) {
if cachedParks, err := cache.RetreiveCount("enabled_parks", userIds...); cachedParks != -1 && err == nil {
return cachedParks, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
query := global.DB.NewSelect().Model((*model.Park)(nil)).
Where("enabled = ?", true)
if len(userIds) > 0 {
query = query.Where("user_id in (?)", bun.In(userIds))
}
c, err := query.Count(ctx)
if err == nil {
cache.CacheCount([]string{"user", "park"}, "enabled_parks", int64(c), userIds...)
}
return int64(c), err
}
func (_StatisticsService) ParksNewestState(userIds ...string) ([]model.ParkPeriodStatistics, error) {
if cachedState, _ := cache.RetreiveSearch[[]model.ParkPeriodStatistics]("park_period_stat", userIds...); cachedState != nil {
return *cachedState, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
query := global.DB.NewSelect().Model((*model.Report)(nil)).
Relation("Park", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Column("id", "name")
}).
Where("park.enabled = ?", true).
Where("r.published = ?", true)
if len(userIds) > 0 {
query = query.Where("park.user_id in (?)", bun.In(userIds))
}
parks := make([]model.ParkPeriodStatistics, 0)
groupedParks := make(map[string]model.ParkPeriodStatistics, 0)
err := query.Column("period").Scan(ctx, &parks)
var c int64
err := pgxscan.Get(ctx, global.DB, &c, UserCountQuery, UserCountQueryArgs...)
if err != nil {
return make([]model.ParkPeriodStatistics, 0), err
ss.l.Error("统计企业数量出错", zap.Error(err))
return 0, err
}
for _, p := range parks {
if c, ok := groupedParks[p.Id]; ok {
if c.Period != nil && p.Period != nil && p.Period.After(c.Period.Time) {
groupedParks[p.Id] = p
}
if c.Period == nil && p.Period != nil {
groupedParks[p.Id] = p
}
} else {
groupedParks[p.Id] = p
}
}
cache.CacheSearch(lo.Values(groupedParks), []string{"user", "park"}, "park_period_stat", userIds...)
return lo.Values(groupedParks), nil
return c, nil
}
//用于统计园区数量
func (ss _StatisticsService) EnabledParks(userIds ...string) (int64, error) {
ss.l.Info("开始统计园区数量", zap.Strings("userId", userIds))
ctx, cancel := global.TimeoutContext()
defer cancel()
ParkCountQuery := ss.ss.
From(goqu.T("park")).
Where(goqu.I("enabled").Eq(true))
if len(userIds) > 0 {
ParkCountQuery = ParkCountQuery.Where(goqu.I("user_id").In(userIds))
}
ParkCountQuerySql, ParkCountQueryArgs, _ := ParkCountQuery.Select(goqu.COUNT("*")).ToSQL()
var c int64
err := pgxscan.Get(ctx, global.DB, &c, ParkCountQuerySql, ParkCountQueryArgs...)
if err != nil {
ss.l.Error("园区数量统计错误", zap.Error(err))
return 0, err
}
return c, nil
}
//用户统计报表
func (ss _StatisticsService) ParkNewestState(userIds ...string) ([]model.ParkPeriodStatistics, error) {
//TODO: 2023.07.26 报表数据库结构改变,此处逻辑复杂放在最后处理
//return nil,errors.New("还未处理逻辑")
return []model.ParkPeriodStatistics{}, nil
}

View File

@@ -80,7 +80,9 @@ func (us _UserService) ProcessEnterpriseUserLogin(username, password string) (*m
us.log.Error("处理企业用户登录失败。", zap.String("username", username), zap.Error(err))
return nil, err
}
token, _ := uuid.NewRandom()
token, _ := uuid.NewRandom() //生成uuid作为会话的token使用
userSession := &model.Session{
Uid: user.Id,
Name: user.Username,

View File

@@ -1,160 +1,39 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"fmt"
"strconv"
"time"
"github.com/samber/lo"
"github.com/uptrace/bun"
"github.com/doug-martin/goqu/v9"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _WithdrawService struct {
l *zap.Logger
log *zap.Logger
ds goqu.DialectWrapper
}
var WithdrawService = _WithdrawService{
l: logger.Named("Service", "Withdraw"),
logger.Named("Service", "Withdraw"),
goqu.Dialect("postgres"),
}
func (_WithdrawService) ApplyWithdraw(reportId string) (bool, error) {
func (wd _WithdrawService) AuditWaits() (int64, error) {
wd.log.Info("获取当前系统中待审核的内容数量。")
ctx, cancel := global.TimeoutContext()
defer cancel()
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).
Where("id = ?", reportId).
Scan(ctx)
if err != nil || report == nil {
return false, exceptions.NewNotFoundErrorFromError("指定报表未能找到", err)
}
if !report.Published {
return false, exceptions.NewImproperOperateError("指定报表尚未发布。")
}
var maxPublished time.Time
err = global.DB.NewSelect().Model((*model.Report)(nil)).
ColumnExpr("max(period)").
Where("park_id = ?", report.ParkId).
Where("published = ?", true).
Scan(ctx, &maxPublished)
CountWithdrawQuery, CountWithdrawQueryArgs, _ := wd.ds.
From(goqu.T("report")).
Where(goqu.I("withdraw").Eq(model.REPORT_WITHDRAW_APPLYING)).
Select(goqu.COUNT("*")).ToSQL()
var total int64
err := pgxscan.Get(ctx, global.DB, &total, CountWithdrawQuery,CountWithdrawQueryArgs...)
if err != nil {
return false, exceptions.NewNotFoundError("未能找到匹配的系列报表。")
wd.log.Error("获取当前系统中待审核的内容数量出错。")
return 0,err
}
if !report.Period.Equal(maxPublished) {
return false, exceptions.NewImproperOperateError("申请撤回的报表必须是最新已发布的报表。")
}
report.Withdraw = model.REPORT_WITHDRAW_APPLIED
report.LastWithdrawAppliedAt = lo.ToPtr(time.Now())
_, err = global.DB.NewUpdate().Model(report).
WherePK().
Column("withdraw", "last_withdraw_applied_at").
Exec(ctx)
if err != nil {
return false, err
}
cache.AbolishRelation("withdraw_stat")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
cache.AbolishRelation(fmt.Sprintf("publicity:%s", reportId))
return true, nil
}
func (_WithdrawService) FetchPagedWithdrawApplies(page int, keyword string) ([]model.JoinedReportForWithdraw, int64, error) {
var (
conditions = make([]string, 0)
reports = make([]model.Report, 0)
cond = global.DB.NewSelect().Model(&reports).
Relation("Park").Relation("Park.Enterprise")
)
conditions = append(conditions, strconv.Itoa(int(model.REPORT_WITHDRAW_APPLIED)), strconv.Itoa(page))
cond = cond.Where("r.withdraw = ?", model.REPORT_WITHDRAW_APPLIED)
if len(keyword) > 0 {
keywordCond := "%" + keyword + "%"
cond = cond.WhereGroup(" and ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("p.name like ?", keywordCond).
WhereOr("p.abbr like ?", keywordCond).
WhereOr("d.name like ?", keywordCond).
WhereOr("d.abbr like ?", keywordCond)
})
conditions = append(conditions, keyword)
}
if cachedTotal, err := cache.RetreiveCount("join_report_for_withdraw", conditions...); cachedTotal != -1 && err == nil {
if cachedReports, _ := cache.RetreiveSearch[[]model.JoinedReportForWithdraw]("join_user_detail", conditions...); cachedReports != nil {
return *cachedReports, cachedTotal, err
}
}
ctx, cancel := global.TimeoutContext()
defer cancel()
startItem := (page - 1) * config.ServiceSettings.ItemsPageSize
total, err := cond.Limit(config.ServiceSettings.ItemsPageSize).
Offset(startItem).
ScanAndCount(ctx)
var (
joinedReports = make([]model.JoinedReportForWithdraw, 0)
relations = []string{"report", "park"}
)
for _, r := range reports {
joinedReports = append(joinedReports, model.JoinedReportForWithdraw{
Report: r,
Park: model.FromPark(*r.Park),
User: model.FromUserDetail(*r.Park.Enterprise),
})
relations = append(relations, fmt.Sprintf("report:%s", r.Id), fmt.Sprintf("publicity:%s", r.Id))
}
cache.CacheCount(relations, "join_report_for_withdraw", int64(total), conditions...)
cache.CacheSearch(joinedReports, relations, "join_report_for_withdraw", conditions...)
return joinedReports, int64(total), err
}
func (_WithdrawService) AuditWithdraw(reportId string, granted bool) error {
ctx, cancel := global.TimeoutContext()
defer cancel()
var report = new(model.Report)
err := global.DB.NewSelect().Model(report).
Where("id = ?", reportId).
Scan(ctx)
if err != nil {
return exceptions.NewNotFoundErrorFromError("指定公示报表未找到。", err)
}
report.Withdraw = lo.If(granted, model.REPORT_WITHDRAW_GRANTED).Else(model.REPORT_WITHDRAW_DENIED)
report.LastWithdrawAuditAt = lo.ToPtr(time.Now())
if granted {
report.Published = false
}
_, err = global.DB.NewUpdate().Model(report).
WherePK().
Column("withdraw", "last_withdraw_audit_at", "published").
Exec(ctx)
if err == nil {
cache.AbolishRelation("withdraw_stat")
cache.AbolishRelation(fmt.Sprintf("report:%s", reportId))
}
return err
}
func (_WithdrawService) AuditWaits() (int64, error) {
if cachedWaits, err := cache.RetreiveCount("withdraw_waits"); cachedWaits != -1 && err == nil {
return cachedWaits, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
total, err := global.DB.NewSelect().Model((*model.Report)(nil)).
Where("withdraw = ?", model.REPORT_WITHDRAW_APPLIED).
Count(ctx)
if err == nil {
cache.CacheCount([]string{"withdraw_stat"}, "withdraw_waits", int64(total))
}
return int64(total), err
return total,nil
}