forked from free-lancers/electricity_bill_calc_service
enhance(calculate): 完善计算部分
This commit is contained in:
@@ -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
|
||||
}
|
33
service/calculate/checking.go
Normal file
33
service/calculate/checking.go
Normal 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
|
||||
}
|
10
service/calculate/meter.go
Normal file
10
service/calculate/meter.go
Normal 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
|
@@ -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
|
||||
|
@@ -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
37
service/calculate/park.go
Normal 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
|
||||
}
|
@@ -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
111
service/calculate/pooled.go
Normal 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
105
service/calculate/shared.go
Normal 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
|
||||
}
|
@@ -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
|
||||
|
||||
}
|
||||
|
@@ -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 := ¤tTermReading
|
||||
meter := calculate.Meter{
|
||||
Code: meter.MeterId,
|
||||
Detail: detail,
|
||||
CoveredArea: decimal.NewFromFloat(detail.Area.Decimal.InexactFloat64()),
|
||||
|
||||
LastTermReading: (*calculate.Reading)(unsafe.Pointer(lastTermReadingPtr)),
|
||||
CurrentTermReading: (*calculate.Reading)(unsafe.Pointer(currentTermReadingPtr)),
|
||||
|
||||
Overall: overall,
|
||||
Critical: critical,
|
||||
Peak: peak,
|
||||
Flat: flat,
|
||||
Valley: valley,
|
||||
|
||||
AdjustLoss: model.ConsumptionUnit{},
|
||||
PooledBasic: model.ConsumptionUnit{},
|
||||
PooledAdjust: model.ConsumptionUnit{},
|
||||
PooledLoss: model.ConsumptionUnit{},
|
||||
PooledPublic: model.ConsumptionUnit{},
|
||||
SharedPoolingProportion: decimal.Decimal{},
|
||||
Poolings: nil,
|
||||
}
|
||||
|
||||
meters = append(meters, meter)
|
||||
}
|
||||
|
||||
return calculate.PrimaryTenementStatistics{
|
||||
Tenement: tenement,
|
||||
Meters: meters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getMeterDetail(meterDetails []*model.MeterDetail, code string) (model.MeterDetail, error) {
|
||||
for _, detail := range meterDetails {
|
||||
if detail.Code == code {
|
||||
return *detail, nil
|
||||
}
|
||||
}
|
||||
return model.MeterDetail{}, errors.New(fmt.Sprintf("表计 %s 的详细信息不存在", code))
|
||||
}
|
||||
|
||||
// 确定指定表计的起始读数
|
||||
func determineTenementMeterStartReading(meterId string, periodStart time.Time, tenementMovedInAt time.Time,
|
||||
meterRelation model.TenementMeter, currentTermReadings []model.MeterReading,
|
||||
lastPeriodReadings []model.MeterReading) (*model.MeterReading, error) {
|
||||
var startTime time.Time
|
||||
timeList := []time.Time{
|
||||
periodStart,
|
||||
tenementMovedInAt,
|
||||
meterRelation.AssociatedAt.Time,
|
||||
}
|
||||
|
||||
for _, t := range timeList {
|
||||
if t.After(startTime) {
|
||||
startTime = t
|
||||
}
|
||||
}
|
||||
if startTime.IsZero() {
|
||||
return nil, fmt.Errorf("无法确定表计 %s 的计量的起始时间", meterId)
|
||||
}
|
||||
|
||||
var startReading *model.MeterReading
|
||||
if startTime.Equal(periodStart) {
|
||||
for _, reading := range lastPeriodReadings {
|
||||
if reading.Meter == meterId {
|
||||
if startReading == nil || reading.ReadAt.After(startReading.ReadAt.Time) {
|
||||
startReading = &reading
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, reading := range currentTermReadings {
|
||||
readingAt := ShiftToAsiaShanghai(reading.ReadAt.Time)
|
||||
if reading.Meter == meterId && readingAt.After(startTime) {
|
||||
if startReading == nil || readingAt.Before(startReading.ReadAt.Time) {
|
||||
startReading = &reading
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if startReading == nil {
|
||||
return nil, errors.New("无法确定表计 " + meterId + " 的计量的起始读数")
|
||||
}
|
||||
return startReading, nil
|
||||
}
|
||||
|
||||
// 确定指定表计的终止读书
|
||||
func determineTenementMeterEndReading(meterId string, periodEnd time.Time,
|
||||
TenementMovedOutAt time.Time, meterRelation model.TenementMeter,
|
||||
currentTermReadings []model.MeterReading) (*model.MeterReading, error) {
|
||||
var endTime time.Time
|
||||
timeList := []time.Time{
|
||||
periodEnd,
|
||||
TenementMovedOutAt,
|
||||
ShiftToAsiaShanghai(meterRelation.DisassociatedAt.Time),
|
||||
}
|
||||
for _, t := range timeList {
|
||||
if t.After(endTime) {
|
||||
endTime = t
|
||||
}
|
||||
}
|
||||
if endTime.IsZero() {
|
||||
return nil, fmt.Errorf("无法确定表计 %s 的计量的结束时间", meterId)
|
||||
}
|
||||
|
||||
var endReading *model.MeterReading
|
||||
|
||||
for _, reading := range currentTermReadings {
|
||||
readingAt := ShiftToAsiaShanghai(reading.ReadAt.Time)
|
||||
if reading.Meter == meterId && readingAt.Before(endTime) {
|
||||
if endReading == nil || readingAt.After(ShiftToAsiaShanghai(endReading.ReadAt.Time)) {
|
||||
endReading = &reading
|
||||
}
|
||||
}
|
||||
}
|
||||
if endReading == nil {
|
||||
return nil, errors.New(fmt.Sprintf("无法确定表计 %s 的计量的结束读数", meterId))
|
||||
}
|
||||
return endReading, nil
|
||||
}
|
||||
|
||||
func ShiftToAsiaShanghai(t time.Time) time.Time {
|
||||
location, _ := time.LoadLocation("Asia/Shanghai")
|
||||
return t.In(location)
|
||||
}
|
||||
|
||||
// 计算各个商户的合计信息,并归总与商户关联的表计记录
|
||||
func TenementChargeCalculate(tenements []calculate.PrimaryTenementStatistics,
|
||||
summary calculate.Summary, meters MeterMap) []calculate.TenementCharge {
|
||||
result := make(map[string][]string)
|
||||
for _, t := range tenements {
|
||||
meterCodes := make([]string, 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
141
service/calculate/utils.go
Normal 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
|
||||
}
|
168
service/calculate/wattCost.go
Normal file
168
service/calculate/wattCost.go
Normal 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)
|
||||
|
||||
}
|
@@ -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
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
171
service/park.go
171
service/park.go
@@ -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
|
||||
}
|
@@ -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(®ions).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
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user