enhance(meter):基本完成表计大部分功能的放置。

This commit is contained in:
徐涛 2023-06-08 20:55:56 +08:00
parent 6bf4009338
commit 28b1478e9a
10 changed files with 2009 additions and 0 deletions

Binary file not shown.

180
controller/meter.go Normal file
View File

@ -0,0 +1,180 @@
package controller
import (
"electricity_bill_calc/logger"
"electricity_bill_calc/repository"
"electricity_bill_calc/response"
"electricity_bill_calc/security"
"electricity_bill_calc/service"
"electricity_bill_calc/vo"
"fmt"
"net/http"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
var meterLog = logger.Named("Handler", "Meter")
func InitializeMeterHandlers(router *fiber.App) {
router.Get("/meter/:pid", security.EnterpriseAuthorize, searchMetersWithinPark)
router.Post("/meter/:pid", security.EnterpriseAuthorize, createNewMeterManually)
router.Get("/meter/:pid/template", security.EnterpriseAuthorize, downloadMeterArchiveTemplate)
router.Get("/meter/:pid/:code", security.EnterpriseAuthorize, retrieveSpecificMeterDetail)
router.Put("/meter/:pid/:code", security.EnterpriseAuthorize, updateMeterManually)
}
// 查询指定园区下的表计信息
func searchMetersWithinPark(c *fiber.Ctx) error {
parkId := c.Params("parkId")
meterLog.Info("查询指定园区下的表计信息", zap.String("park id", parkId))
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法查询指定园区下的表计信息,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok {
return err
}
keyword := c.Query("keyword")
page := c.QueryInt("page", 1)
meters, total, err := repository.MeterRepository.MetersIn(parkId, uint(page), &keyword)
if err != nil {
meterLog.Error("无法查询指定园区下的表计信息,无法获取表计列表", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
return result.Success(
"已经取得符合条件的0.4kV表计列表。",
response.NewPagedResponse(page, total).ToMap(),
fiber.Map{"meters": meters},
)
}
// 查询指定园区中指定表计的详细信息
func retrieveSpecificMeterDetail(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterId := c.Params("code")
meterLog.Info("查询指定园区中指定表计的详细信息", zap.String("park id", parkId), zap.String("meter id", meterId))
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法查询指定园区中指定表计的详细信息,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok {
return err
}
meter, err := repository.MeterRepository.FetchMeterDetail(parkId, meterId)
if err != nil {
meterLog.Error("无法查询指定园区中指定表计的详细信息,无法获取表计信息", zap.Error(err))
return result.Error(http.StatusInternalServerError, err.Error())
}
if meter == nil {
meterLog.Warn("无法查询指定园区中指定表计的详细信息,表计不存在")
return result.NotFound("指定的表计不存在。")
}
return result.Success("指定表计信息已经找到。", fiber.Map{"meter": meter})
}
// 手动添加一条0.4kV表计记录
func createNewMeterManually(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("手动添加一条0.4kV表计记录", zap.String("park id", parkId))
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法手动添加一条0.4kV表计记录,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok {
return err
}
var creationForm vo.MeterCreationForm
if err := c.BodyParser(&creationForm); err != nil {
meterLog.Error("无法手动添加一条0.4kV表计记录,无法解析表计创建表单", zap.Error(err))
return result.NotAccept(err.Error())
}
if err := service.MeterService.CreateMeterRecord(parkId, &creationForm); err != nil {
meterLog.Error("无法手动添加一条0.4kV表计记录,无法创建表计记录", zap.Error(err))
return result.NotAccept(err.Error())
}
return result.Created("新0.4kV表计已经添加完成。")
}
// 手动更新一条新的0.4kV表计记录
func updateMeterManually(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterId := c.Params("code")
meterLog.Info("手动更新一条新的0.4kV表计记录", zap.String("park id", parkId), zap.String("meter id", meterId))
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok {
return err
}
var updateForm vo.MeterModificationForm
if err := c.BodyParser(&updateForm); err != nil {
meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法解析表计更新表单", zap.Error(err))
return result.NotAccept(err.Error())
}
if err := service.MeterService.UpdateMeterRecord(parkId, meterId, &updateForm); err != nil {
meterLog.Error("无法手动更新一条新的0.4kV表计记录,无法更新表计记录", zap.Error(err))
return result.NotAccept(err.Error())
}
return result.Updated("0.4kV表计已经更新完成。")
}
// 下载指定的园区表计登记模板
func downloadMeterArchiveTemplate(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterLog.Info("下载指定的园区表计登记模板", zap.String("park id", parkId))
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok {
return err
}
parkDetail, err := repository.ParkRepository.RetrieveParkDetail(parkId)
if err != nil {
meterLog.Error("无法下载指定的园区表计登记模板,无法获取园区信息", zap.Error(err))
return result.NotFound(err.Error())
}
return c.Download(
"./assets/meter_04kv_template.xlsx",
fmt.Sprintf("%s_表计档案.xlsx", parkDetail.Name),
)
}
// 从Excel文件中导入表计档案
func uploadMeterArchive(c *fiber.Ctx) error {
return nil
}
// 更换系统中的表计
func replaceMeter(c *fiber.Ctx) error {
parkId := c.Params("pid")
meterId := c.Params("code")
meterLog.Info("更换系统中的表计", zap.String("park id", parkId), zap.String("meter id", meterId))
result := response.NewResult(c)
session, err := _retreiveSession(c)
if err != nil {
meterLog.Error("无法更换系统中的表计,无法获取当前用户会话", zap.Error(err))
return result.Unauthorized(err.Error())
}
if ok, err := checkParkBelongs(meterLog, parkId, session, &result); !ok {
return err
}
var replacementForm vo.MeterReplacingForm
if err := c.BodyParser(&replacementForm); err != nil {
meterLog.Error("无法更换系统中的表计,无法解析表计更换表单", zap.Error(err))
return result.NotAccept(err.Error())
}
return nil
}

10
model/cunsumption.go Normal file
View File

@ -0,0 +1,10 @@
package model
import "github.com/shopspring/decimal"
type ConsumptionUnit struct {
Amount decimal.Decimal `json:"amount"`
Fee decimal.Decimal `json:"fee"`
Price decimal.Decimal `json:"price"`
Proportion decimal.Decimal `json:"proportion"`
}

78
model/meter.go Normal file
View File

@ -0,0 +1,78 @@
package model
import (
"time"
"github.com/shopspring/decimal"
)
type MeterDetail struct {
Code string `json:"code" db:"code"`
Park string `json:"parkId" db:"park_id"`
Address *string `json:"address" db:"address"`
MeterType int16 `json:"meterType" db:"meter_type"`
Building *string `json:"building" db:"building"`
BuildingName *string `json:"buildingName" db:"building_name"`
OnFloor *string `json:"onFloor" db:"on_floor"`
Area decimal.NullDecimal `json:"area" db:"area"`
Ratio decimal.Decimal `json:"ratio" db:"ratio"`
Seq int64 `json:"seq" db:"seq"`
Enabled bool `json:"enabled" db:"enabled"`
AttachedAt *time.Time `json:"attachedAt" db:"attached_at"`
DetachedAt *time.Time `json:"detachedAt" db:"detached_at"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
LastModifiedAt time.Time `json:"lastModifiedAt" db:"last_modified_at"`
}
type MeterRelation struct {
Id string `json:"id"`
Park string `json:"parkId" db:"park_id"`
MasterMeter string `json:"masterMeterId" db:"master_meter_id"`
SlaveMeter string `json:"slaveMeterId" db:"slave_meter_id"`
EstablishedAt time.Time `json:"establishedAt"`
SuspendedAt *time.Time `json:"suspendedAt"`
RevokeAt *time.Time `json:"revokeAt"`
}
type MeterSynchronization struct {
Park string `json:"parkId" db:"park_id"`
Meter string `json:"meterId" db:"meter_id"`
ForeignMeter string `json:"foreignMeter"`
SystemType string `json:"systemType"`
SystemIdentity string `json:"systemIdentity"`
Enabled bool `json:"enabled"`
LastSynchronizedAt time.Time `json:"lastSynchronizedAt" db:"last_synchronized_at"`
RevokeAt *time.Time `json:"revokeAt" db:"revoke_at"`
}
type SimpleMeterDocument struct {
Code string `json:"code"`
Seq int64 `json:"seq"`
Address *string `json:"address"`
Ratio decimal.Decimal `json:"ratio"`
TenementName *string `json:"tenementName"`
}
type NestedMeter struct {
MeterId string `json:"meterId"`
MeterDetail MeterDetail `json:"meterDetail"`
LastTermReadings Reading `json:"lastTermReadings"`
CurrentTermReadings Reading `json:"currentTermReadings"`
Overall ConsumptionUnit `json:"overall"`
Critical ConsumptionUnit `json:"critical"`
Peak ConsumptionUnit `json:"peak"`
Flat ConsumptionUnit `json:"flat"`
Valley ConsumptionUnit `json:"valley"`
BasicPooled decimal.Decimal `json:"basicPooled"`
AdjustPooled decimal.Decimal `json:"adjustPooled"`
LossPooled decimal.Decimal `json:"lossPooled"`
PublicPooled decimal.Decimal `json:"publicPooled"`
FinalTotal decimal.Decimal `json:"finalTotal"`
Area decimal.Decimal `json:"area"`
Proportion decimal.Decimal `json:"proportion"`
}
type PooledMeterDetailCompound struct {
MeterDetail
BindMeters []MeterDetail `json:"bindMeters"`
}

56
model/reading.go Normal file
View File

@ -0,0 +1,56 @@
package model
import (
"time"
"github.com/shopspring/decimal"
)
type Reading struct {
Ratio decimal.Decimal `json:"ratio"`
Overall decimal.Decimal `json:"overall"`
Critical decimal.Decimal `json:"critical"`
Peak decimal.Decimal `json:"peak"`
Flat decimal.Decimal `json:"flat"`
Valley decimal.Decimal `json:"valley"`
}
func NewPVReading(ratio, overall, critical, peak, flat, valley decimal.Decimal) *Reading {
return &Reading{
Ratio: ratio,
Overall: overall,
Critical: critical,
Peak: peak,
Flat: flat,
Valley: valley,
}
}
func NewUnitaryReading(ratio, overall decimal.Decimal) *Reading {
return &Reading{
Ratio: ratio,
Overall: overall,
Critical: decimal.Zero,
Peak: decimal.Zero,
Flat: overall,
Valley: decimal.Zero,
}
}
type MeterReading struct {
ReadAt time.Time `json:"readAt"`
Park string `json:"parkId" db:"park_id"`
Meter string `json:"meterId" db:"meter_id"`
MeterType int16 `json:"meterType"`
Ratio decimal.Decimal `json:"ratio"`
Overall decimal.Decimal `json:"overall"`
Critical decimal.Decimal `json:"critical"`
Peak decimal.Decimal `json:"peak"`
Flat decimal.Decimal `json:"flat"`
Valley decimal.Decimal `json:"valley"`
}
type DetailedMeterReading struct {
Detail MeterDetail `json:"detail"`
Reading MeterReading `json:"reading"`
}

1091
repository/meter.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -48,6 +48,7 @@ func App() *fiber.App {
controller.InitializeRegionHandlers(app)
controller.InitializeChargeHandlers(app)
controller.InitializeParkHandlers(app)
controller.InitializeMeterHandlers(app)
return app
}

539
service/meter.go Normal file
View File

@ -0,0 +1,539 @@
package service
import (
"electricity_bill_calc/cache"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/repository"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"electricity_bill_calc/vo"
"fmt"
"mime/multipart"
"github.com/samber/lo"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
type _MeterService struct {
log *zap.Logger
}
var MeterService = _MeterService{
log: logger.Named("Service", "Meter"),
}
// 创建一条新的表计记录
func (ms _MeterService) CreateMeterRecord(pid string, form *vo.MeterCreationForm) error {
ms.log.Info("创建一条新的表计记录", zap.String("park id", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ms.log.Error("无法启动数据库事务。", zap.Error(err))
return err
}
ok, err := repository.MeterRepository.CreateMeter(tx, ctx, pid, *form)
if err != nil {
ms.log.Error("无法创建一条新的表计记录。", zap.Error(err))
tx.Rollback(ctx)
return err
}
if !ok {
ms.log.Error("数据库未能记录新的表计记录。")
tx.Rollback(ctx)
return err
}
ok, err = repository.MeterRepository.RecordReading(tx, ctx, pid, form.Code, form.MeterType, form.Ratio, &form.MeterReadingForm)
if err != nil {
ms.log.Error("无法记录表计读数。", zap.Error(err))
tx.Rollback(ctx)
return err
}
if !ok {
ms.log.Error("数据库未能记录表计读数。")
tx.Rollback(ctx)
return err
}
err = tx.Commit(ctx)
if err != nil {
ms.log.Error("未能成功提交数据库事务。", zap.Error(err))
tx.Rollback(ctx)
return err
}
cache.AbolishRelation(fmt.Sprintf("meter:%s", pid))
return nil
}
// 更新指定表计的信息
func (ms _MeterService) UpdateMeterRecord(pid string, code string, form *vo.MeterModificationForm) error {
ms.log.Info("更新指定表计的信息", zap.String("park id", pid), zap.String("meter code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ms.log.Error("无法启动数据库事务。", zap.Error(err))
return err
}
ok, err := repository.MeterRepository.UpdateMeter(tx, ctx, pid, code, form)
if err != nil {
ms.log.Error("无法更新指定表计的信息。", zap.Error(err))
tx.Rollback(ctx)
return err
}
if !ok {
ms.log.Error("数据库未能更新指定表计的信息。")
tx.Rollback(ctx)
return err
}
err = tx.Commit(ctx)
if err != nil {
ms.log.Error("未能成功提交数据库事务。", zap.Error(err))
tx.Rollback(ctx)
return err
}
cache.AbolishRelation(fmt.Sprintf("meter:%s", pid))
return nil
}
// 处理上传的Excel格式表计档案文件根据表号自动更新数据库
func (ms _MeterService) BatchImportMeters(pid string, file multipart.FileHeader) error {
return nil
}
// 更换系统中的表计
func (ms _MeterService) ReplaceMeter(
pid string,
oldMeterCode string,
oldMeterReading *vo.MeterReadingForm,
newMeterCode string,
newMeterRatio decimal.Decimal,
newMeterReading *vo.MeterReadingForm,
) error {
ms.log.Info("更换系统中的表计", zap.String("park id", pid), zap.String("old meter code", oldMeterCode), zap.String("new meter code", newMeterCode))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ms.log.Error("无法启动数据库事务。", zap.Error(err))
return err
}
// 步骤1读取旧表信息
oldMeter, err := repository.MeterRepository.FetchMeterDetail(pid, oldMeterCode)
if err != nil {
ms.log.Error("无法读取旧表信息。", zap.Error(err))
tx.Rollback(ctx)
return fmt.Errorf("要替换的旧表计不存在:%w", err)
}
// 步骤2写入旧表读数
ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, oldMeterCode, oldMeter.MeterType, oldMeter.Ratio, oldMeterReading)
switch {
case err != nil:
ms.log.Error("无法写入旧表读数。", zap.Error(err))
tx.Rollback(ctx)
return err
case !ok:
ms.log.Error("数据库未能写入旧表读数。")
tx.Rollback(ctx)
return fmt.Errorf("旧表计读数未能成功保存到数据库。")
}
// 步骤3从系统移除旧表计
ok, err = repository.MeterRepository.DetachMeter(tx, ctx, pid, oldMeterCode)
switch {
case err != nil:
ms.log.Error("无法从系统移除旧表计。", zap.Error(err))
tx.Rollback(ctx)
return err
case !ok:
ms.log.Error("未能从系统移除旧表计。")
tx.Rollback(ctx)
return fmt.Errorf("旧表计未能成功从系统移除。")
}
// 步骤4获取旧表计的关联信息
var oldRelations []*model.MeterRelation
switch oldMeter.MeterType {
case model.METER_INSTALLATION_POOLING:
oldRelations, err = repository.MeterRepository.ListPooledMeterRelations(pid, oldMeterCode)
if err != nil {
ms.log.Error("无法获取旧表计的关联信息。", zap.Error(err))
tx.Rollback(ctx)
return err
}
default:
oldRelations, err = repository.MeterRepository.ListMeterRelations(pid, oldMeterCode)
if err != nil {
ms.log.Error("无法获取旧表计的关联信息。", zap.Error(err))
tx.Rollback(ctx)
return err
}
}
// 步骤5将旧表计的关联信息设置为解除
for _, relation := range oldRelations {
ok, err = repository.MeterRepository.UnbindMeter(tx, ctx, pid, relation.MasterMeter, relation.SlaveMeter)
switch {
case err != nil:
ms.log.Error("无法将旧表计的关联信息设置为解除。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", relation.SlaveMeter), zap.Error(err))
tx.Rollback(ctx)
return err
case !ok:
ms.log.Error("未能将旧表计的关联信息设置为解除。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", relation.SlaveMeter))
tx.Rollback(ctx)
return fmt.Errorf("旧表计的关联信息未能成功设置为解除。")
}
}
// 步骤6将旧表计的部分信息赋予新表计
newMeterCreationForm := vo.MeterCreationForm{
Code: newMeterCode,
Address: oldMeter.Address,
MeterType: oldMeter.MeterType,
Ratio: newMeterRatio,
Seq: oldMeter.Seq,
Enabled: oldMeter.Enabled,
Building: oldMeter.Building,
OnFloor: oldMeter.OnFloor,
Area: oldMeter.Area,
MeterReadingForm: *newMeterReading,
}
// 步骤7将新表计写入系统
ok, err = repository.MeterRepository.CreateMeter(tx, ctx, pid, newMeterCreationForm)
switch {
case err != nil:
ms.log.Error("无法将新表计写入系统。", zap.Error(err))
tx.Rollback(ctx)
return err
case !ok:
ms.log.Error("未能将新表计写入系统。")
tx.Rollback(ctx)
return fmt.Errorf("新表计未能成功写入系统。")
}
// 步骤8将新表计的读数写入系统
ok, err = repository.MeterRepository.RecordReading(tx, ctx, pid, newMeterCode, newMeterCreationForm.MeterType, newMeterCreationForm.Ratio, &newMeterCreationForm.MeterReadingForm)
switch {
case err != nil:
ms.log.Error("无法将新表计的读数写入系统。", zap.Error(err))
tx.Rollback(ctx)
return err
case !ok:
ms.log.Error("未能将新表计的读数写入系统。")
tx.Rollback(ctx)
return fmt.Errorf("新表计的读数未能成功写入系统。")
}
// 步骤9将旧表计的关联信息复制一份赋予新表计
switch oldMeter.MeterType {
case model.METER_INSTALLATION_POOLING:
for _, relation := range oldRelations {
ok, err = repository.MeterRepository.BindMeter(tx, ctx, pid, newMeterCode, relation.SlaveMeter)
switch {
case err != nil:
ms.log.Error("无法将旧表计的关联信息赋予新表计。", zap.String("master meter", newMeterCode), zap.String("slave meter", relation.SlaveMeter), zap.Error(err))
tx.Rollback(ctx)
return err
case !ok:
ms.log.Error("未能将旧表计的关联信息赋予新表计。", zap.String("master meter", newMeterCode), zap.String("slave meter", relation.SlaveMeter))
tx.Rollback(ctx)
return fmt.Errorf("旧表计的关联信息未能成功赋予新表计。")
}
}
default:
for _, relation := range oldRelations {
ok, err = repository.MeterRepository.BindMeter(tx, ctx, pid, relation.MasterMeter, newMeterCode)
switch {
case err != nil:
ms.log.Error("无法将旧表计的关联信息赋予新表计。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", newMeterCode), zap.Error(err))
tx.Rollback(ctx)
return err
case !ok:
ms.log.Error("未能将旧表计的关联信息赋予新表计。", zap.String("master meter", relation.MasterMeter), zap.String("slave meter", newMeterCode))
tx.Rollback(ctx)
return fmt.Errorf("旧表计的关联信息未能成功赋予新表计。")
}
}
}
// 步骤10提交事务
err = tx.Commit(ctx)
if err != nil {
ms.log.Error("未能成功提交数据库事务。", zap.Error(err))
tx.Rollback(ctx)
return err
}
cache.AbolishRelation(fmt.Sprintf("meter:%s", pid))
return nil
}
// 列出园区中指定公摊表计下的所有关联表计
func (ms _MeterService) ListPooledMeterRelations(pid, masterMeter string) ([]*model.MeterDetail, error) {
ms.log.Info("列出园区中指定公摊表计下的所有关联表计", zap.String("park id", pid), zap.String("meter code", masterMeter))
relations, err := repository.MeterRepository.ListPooledMeterRelations(pid, masterMeter)
if err != nil {
ms.log.Error("无法列出园区中指定公摊表计下的所有关联关系。", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
relatedMeterCodes := lo.Map(relations, func(element *model.MeterRelation, _ int) string {
return element.SlaveMeter
})
meters, err := repository.MeterRepository.ListMetersByIDs(pid, relatedMeterCodes)
if err != nil {
ms.log.Error("无法列出园区中指定公摊表计下的所有关联表计详细信息。", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 列出指定园区中所有的公摊表计
func (ms _MeterService) SearchPooledMetersDetail(pid string, page uint, keyword *string) ([]*model.PooledMeterDetailCompound, int64, error) {
ms.log.Info("列出指定园区中所有的公摊表计", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", *keyword))
cacheConditions := []string{
pid,
fmt.Sprintf("%d", page),
tools.DefaultTo(keyword, "UNDEFINED"),
}
if meters, total, err := cache.RetrievePagedSearch[[]*model.PooledMeterDetailCompound]("assemble_pooled_meters_detail", cacheConditions...); err == nil {
ms.log.Info("已经从缓存中获取到了指定园区中所有的公摊表计。", zap.Int("count", len(*meters)), zap.Int64("total", total))
return *meters, total, nil
}
poolingMeters, total, err := repository.MeterRepository.ListPoolingMeters(pid, page, keyword)
if err != nil {
ms.log.Error("无法列出指定园区中所有的公摊表计。", zap.Error(err))
return make([]*model.PooledMeterDetailCompound, 0), 0, err
}
poolingMeterIds := lo.Map(poolingMeters, func(element *model.MeterDetail, _ int) string {
return element.Code
})
relations, err := repository.MeterRepository.ListPooledMeterRelationsByCodes(pid, poolingMeterIds)
if err != nil {
ms.log.Error("无法列出指定园区中所有的公摊表计关联关系。", zap.Error(err))
return make([]*model.PooledMeterDetailCompound, 0), 0, err
}
slaveMeters, err := repository.MeterRepository.ListMetersByIDs(pid, lo.Map(relations, func(element *model.MeterRelation, _ int) string {
return element.SlaveMeter
}))
if err != nil {
ms.log.Error("无法列出指定园区中所有的公摊表计的关联表计详细信息。", zap.Error(err))
return make([]*model.PooledMeterDetailCompound, 0), 0, err
}
var assembled []*model.PooledMeterDetailCompound = make([]*model.PooledMeterDetailCompound, 0)
for _, meter := range poolingMeters {
slaveIDs := lo.Map(lo.Filter(
relations,
func(element *model.MeterRelation, _ int) bool {
return element.MasterMeter == meter.Code
}),
func(element *model.MeterRelation, _ int) string {
return element.SlaveMeter
},
)
slaves := lo.Map(lo.Filter(
slaveMeters,
func(element *model.MeterDetail, _ int) bool {
return lo.Contains(slaveIDs, element.Code)
}),
func(element *model.MeterDetail, _ int) model.MeterDetail {
return *element
},
)
assembled = append(assembled, &model.PooledMeterDetailCompound{
MeterDetail: *meter,
BindMeters: slaves,
})
}
cache.CachePagedSearch(assembled, total, []string{fmt.Sprintf("meter:%s", pid), fmt.Sprintf("meter_relation:%s", pid)}, "assemble_pooled_meter_detail", cacheConditions...)
return assembled, total, nil
}
// 批量向园区中指定公摊表计下绑定关联表计
func (ms _MeterService) BindMeter(pid, masterMeter string, slaveMeters []string) (bool, error) {
ms.log.Info("批量向园区中指定公摊表计下绑定关联表计", zap.String("park id", pid), zap.String("master meter", masterMeter), zap.Strings("slave meters", slaveMeters))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ms.log.Error("无法启动数据库事务。", zap.Error(err))
return false, err
}
for _, slave := range slaveMeters {
ok, err := repository.MeterRepository.BindMeter(tx, ctx, pid, masterMeter, slave)
switch {
case err != nil:
ms.log.Error("无法向园区中指定公摊表计下绑定关联表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave), zap.Error(err))
tx.Rollback(ctx)
return false, err
case !ok:
ms.log.Error("未能向园区中指定公摊表计下绑定关联表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave))
tx.Rollback(ctx)
return false, fmt.Errorf("未能成功向园区中指定公摊表计下绑定关联表计。")
}
}
err = tx.Commit(ctx)
if err != nil {
ms.log.Error("未能成功提交数据库事务。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
cache.AbolishRelation(fmt.Sprintf("meter:%s", pid))
return true, nil
}
// 批量解绑园区中指定表计下的指定表计
func (ms _MeterService) UnbindMeter(pid, masterMeter string, slaveMeters []string) (bool, error) {
ms.log.Info("批量解绑园区中指定表计下的指定表计", zap.String("park id", pid), zap.String("master meter", masterMeter), zap.Strings("slave meters", slaveMeters))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ms.log.Error("无法启动数据库事务。", zap.Error(err))
return false, err
}
for _, slave := range slaveMeters {
ok, err := repository.MeterRepository.UnbindMeter(tx, ctx, pid, masterMeter, slave)
switch {
case err != nil:
ms.log.Error("无法解绑园区中指定表计下的指定表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave), zap.Error(err))
tx.Rollback(ctx)
return false, err
case !ok:
ms.log.Error("未能解绑园区中指定表计下的指定表计。", zap.String("master meter", masterMeter), zap.String("slave meter", slave))
tx.Rollback(ctx)
return false, fmt.Errorf("未能成功解绑园区中指定表计下的指定表计。")
}
}
err = tx.Commit(ctx)
if err != nil {
ms.log.Error("未能成功提交数据库事务。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
cache.AbolishRelation(fmt.Sprintf("meter:%s", pid))
return true, nil
}
// 查询符合条件的表计读数记录
func (ms _MeterService) SearchMeterReadings(pid string, building *string, start, end *types.Date, page uint, keyword *string) ([]*model.DetailedMeterReading, int64, error) {
ms.log.Info(
"查询符合条件的表计读数记录",
zap.String("park id", pid),
zap.Stringp("building", building),
logger.DateFieldp("start", start),
logger.DateFieldp("end", end),
zap.Uint("page", page),
zap.Stringp("keyword", keyword),
)
readings, total, err := repository.MeterRepository.ListMeterReadings(pid, keyword, page, start, end, building)
if err != nil {
ms.log.Error("无法查询符合条件的表计读数记录。", zap.Error(err))
return make([]*model.DetailedMeterReading, 0), 0, err
}
meterCodes := lo.Map(readings, func(element *model.MeterReading, _ int) string {
return element.Meter
})
meterDetails, err := repository.MeterRepository.ListMetersByIDs(pid, meterCodes)
if err != nil {
ms.log.Error("无法查询符合条件的表计读数记录的表计详细信息。", zap.Error(err))
return make([]*model.DetailedMeterReading, 0), 0, err
}
assembles := lo.Map(
readings,
func(element *model.MeterReading, _ int) *model.DetailedMeterReading {
meter, _ := lo.Find(meterDetails, func(detail *model.MeterDetail) bool {
return detail.Code == element.Meter
})
return &model.DetailedMeterReading{
Detail: *meter,
Reading: *element,
}
},
)
return assembles, total, nil
}
// 创建一条新的表计抄表记录
func (ms _MeterService) RecordReading(pid, meterCode string, form *vo.MeterReadingForm) error {
ms.log.Info("创建一条新的表计抄表记录", zap.String("park id", pid), zap.String("meter code", meterCode))
meter, err := repository.MeterRepository.FetchMeterDetail(pid, meterCode)
if err != nil || meter == nil {
ms.log.Error("无法找到指定的表计", zap.Error(err))
return fmt.Errorf("无法找到指定的表计:%w", err)
}
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ms.log.Error("无法启动数据库事务。", zap.Error(err))
return err
}
ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meter.MeterType, meter.Ratio, form)
if err != nil {
ms.log.Error("无法创建一条新的表计抄表记录。", zap.Error(err))
tx.Rollback(ctx)
return err
}
if !ok {
ms.log.Error("未能创建一条新的表计抄表记录。")
tx.Rollback(ctx)
return fmt.Errorf("未能成功创建一条新的表计抄表记录。")
}
err = tx.Commit(ctx)
if err != nil {
ms.log.Error("未能成功提交数据库事务。", zap.Error(err))
tx.Rollback(ctx)
return err
}
return nil
}
// 获取指定园区的全部待抄表计列表并将其输出到Excel文件模板中提供生成文件的二进制内容
func (ms _MeterService) GenerateParkMeterReadingTemplate(pid string, meters []*model.SimpleMeterDocument) ([]byte, error) {
return nil, nil
}
// 处理上传的Excel格式的表计抄表记录所有满足审查条件的记录都将被保存到数据库中。
// 无论峰谷表计还是普通表计,只要抄表记录中不存在峰谷数据,都将自动使用平段配平。
func (ms _MeterService) BatchImportReadings(pid string, uploadContent []byte) error {
// 步骤1将解析到的数据转换成创建表单数据
// 步骤2对目前已经解析到的数据进行合法性检测检测包括表计编号在同一抄表时间是否重复
// 步骤3从数据库中获取当前园区中已有的表计编号
// 步骤4.0:启动数据库事务
// 步骤4.1:对比检查数据库中的表计编号与上传文件中的表计编号是否存在差异。非差异内容将直接保存
// 步骤4.1.1:抄表的表计在数据库中已经存在,可以直接保存起数据。
// 步骤4.1.2:抄表表计在数据库中不存在,需要将其记录进入错误。
// 步骤4.3:如果批处理过程中存在错误,撤销全部导入动作。
// 步骤5执行事务更新数据库获取完成更改的行数。
return nil
}

38
vo/meter.go Normal file
View File

@ -0,0 +1,38 @@
package vo
import "github.com/shopspring/decimal"
type MeterCreationForm struct {
Code string `json:"code"`
Address *string `json:"address"`
Ratio decimal.Decimal `json:"ratio"`
Seq int64 `json:"seq"`
MeterType int16 `json:"meterType"`
Building *string `json:"building"`
OnFloor *string `json:"onFloor"`
Area decimal.NullDecimal `json:"area"`
Enabled bool `json:"enabled"`
MeterReadingForm `json:"-"`
}
type MeterModificationForm struct {
Address *string `json:"address"`
Seq int64 `json:"seq"`
Ratio decimal.Decimal `json:"ratio"`
Enabled bool `json:"enabled"`
MeterType int16 `json:"meterType"`
Building *string `json:"building"`
OnFloor *string `json:"onFloor"`
Area decimal.NullDecimal `json:"area"`
}
type NewMeterForReplacingForm struct {
Code string `json:"code"`
Ratio decimal.Decimal `json:"ratio"`
Reading MeterReadingForm `json:"reading"`
}
type MeterReplacingForm struct {
OldReading MeterReadingForm `json:"oldReading"`
NewMeter NewMeterForReplacingForm `json:"newMeter"`
}

16
vo/reading.go Normal file
View File

@ -0,0 +1,16 @@
package vo
import (
"electricity_bill_calc/types"
"github.com/shopspring/decimal"
)
type MeterReadingForm struct {
Overall decimal.Decimal `json:"overall"`
Critical decimal.Decimal `json:"critical"`
Peak decimal.Decimal `json:"peak"`
Flat decimal.Decimal `json:"flat"`
Valley decimal.Decimal `json:"valley"`
ReadAt *types.DateTime `json:"readAt"`
}