diff --git a/service/tenement.go b/service/tenement.go new file mode 100644 index 0000000..c0999bb --- /dev/null +++ b/service/tenement.go @@ -0,0 +1,245 @@ +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/vo" + "fmt" + + "github.com/samber/lo" + "go.uber.org/zap" +) + +type _TenementService struct { + log *zap.Logger +} + +var TenementService = _TenementService{ + log: logger.Named("Service", "Tenement"), +} + +// 列出指定商户下的全部计量表计,不包含公摊表计 +func (ts _TenementService) ListMeter(pid, tid string) ([]*model.MeterDetail, error) { + ts.log.Info("列出指定商户下的全部表计", zap.String("Park", pid), zap.String("Tenement", tid)) + meterCodes, err := repository.TenementRepository.ListMeterCodesBelongsTo(pid, tid) + if err != nil { + ts.log.Error("列出指定商户下的全部表计失败,未能获取属于商户的表计编号", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + meters, err := repository.MeterRepository.ListMetersByIDs(pid, meterCodes) + if err != nil { + ts.log.Error("列出指定商户下的全部表计失败,未能获取表计编号对应的表计详细信息", zap.Error(err)) + return make([]*model.MeterDetail, 0), err + } + return meters, nil +} + +// 增加一个新的商户 +func (ts _TenementService) CreateTenementRecord(pid string, creationForm *vo.TenementCreationForm) error { + ts.log.Info("增加一个新的商户", zap.String("Park", pid), zap.Any("Form", creationForm)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("增加一个新商户失败的,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + err = repository.TenementRepository.AddTenement(tx, ctx, pid, creationForm) + if err != nil { + ts.log.Error("增加一个新商户失败的,未能增加商户记录", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能增加商户记录,%w", err) + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("增加一个新商户失败的,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + return nil +} + +// 向商户绑定一个新表计 +func (ts _TenementService) BindMeter(pid, tid, meterCode string, reading *vo.MeterReadingForm) error { + ts.log.Info("向商户绑定一个新表计", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meterCode)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + meterDetail, err := repository.MeterRepository.FetchMeterDetail(pid, meterCode) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能获取表计详细信息", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取表计详细信息,%w", err) + } + err = repository.TenementRepository.BindMeter(tx, ctx, pid, tid, meterCode) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能绑定表计", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能绑定表计,%w", err) + } + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meterDetail.MeterType, meterDetail.Ratio, reading) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,记录表计读数出现错误", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数出现错误,%w", err) + } + if !ok { + ts.log.Error("向商户绑定一个新表计失败,记录表计读数失败") + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数失败") + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("向商户绑定一个新表计失败,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} + +// 解除商户与指定表计的绑定 +func (ts _TenementService) UnbindMeter(pid, tid, meterCode string, reading *vo.MeterReadingForm) error { + ts.log.Info("解除商户与指定表计的绑定", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meterCode)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + meterDetail, err := repository.MeterRepository.FetchMeterDetail(pid, meterCode) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能获取表计详细信息", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取表计详细信息,%w", err) + } + err = repository.TenementRepository.UnbindMeter(tx, ctx, pid, tid, meterCode) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能解除绑定", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能解除绑定,%w", err) + } + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meterDetail.MeterType, meterDetail.Ratio, reading) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,记录表计读数出现错误", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数出现错误,%w", err) + } + if !ok { + ts.log.Error("解除商户与指定表计的绑定失败,记录表计读数失败") + tx.Rollback(ctx) + return fmt.Errorf("记录表计读数失败") + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("解除商户与指定表计的绑定失败,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} + +// 迁出指定商户 +func (ts _TenementService) MoveOutTenement(pid, tid string, reading []*vo.MeterReadingFormWithCode) error { + ts.log.Info("迁出指定商户", zap.String("Park", pid), zap.String("Tenement", tid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + tx, err := global.DB.Begin(ctx) + if err != nil { + ts.log.Error("迁出指定商户失败,未能启动数据库事务", zap.Error(err)) + return fmt.Errorf("未能启动数据库事务,%w", err) + } + + meterCodes, err := repository.TenementRepository.ListMeterCodesBelongsTo(pid, tid) + if err != nil { + ts.log.Error("迁出指定商户失败,未能获取属于商户的表计编号", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取属于商户的表计编号,%w", err) + } + meters, err := repository.MeterRepository.ListMetersByIDs(pid, meterCodes) + if err != nil { + ts.log.Error("迁出指定商户失败,未能获取表涉及计编号对应的表计详细信息", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能获取涉及表计编号对应的表计详细信息,%w", err) + } + + for _, meterCode := range meterCodes { + meterDetail, exists := lo.Find(meters, func(m *model.MeterDetail) bool { + return m.Code == meterCode + }) + if !exists { + ts.log.Error("迁出指定商户失败,找不到指定表计的详细信息", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("找不到指定表计[%s]的详细信息,%w", meterCode, err) + } + if meterDetail.MeterType != model.METER_INSTALLATION_TENEMENT { + ts.log.Error("迁出指定商户失败,需要解绑的表计不是商户表计", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("需要解绑的表计[%s]不是商户表计,%w", meterCode, err) + } + reading, exists := lo.Find(reading, func(r *vo.MeterReadingFormWithCode) bool { + return r.Code == meterCode + }) + if !exists { + ts.log.Error("迁出指定商户失败,找不到指定表计的抄表信息", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("找不到指定表计[%s]的抄表信息,%w", meterCode, err) + } + err = repository.TenementRepository.UnbindMeter(tx, ctx, pid, tid, meterCode) + if err != nil { + ts.log.Error("迁出指定商户失败,未能解除表计绑定", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能解除表计[%s]绑定,%w", meterCode, err) + } + ok, err := repository.MeterRepository.RecordReading(tx, ctx, pid, meterCode, meterDetail.MeterType, meterDetail.Ratio, &reading.MeterReadingForm) + if err != nil { + ts.log.Error("迁出指定商户失败,记录表计抄表信息出现错误", zap.String("Meter", meterCode), zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计[%s]抄表信息出现错误,%w", meterCode, err) + } + if !ok { + ts.log.Error("迁出指定商户失败,记录表计抄表数据失败", zap.String("Meter", meterCode)) + tx.Rollback(ctx) + return fmt.Errorf("记录表计[%s]抄表数据失败", meterCode) + } + } + err = repository.TenementRepository.MoveOut(tx, ctx, pid, tid) + if err != nil { + ts.log.Error("迁出指定商户失败,未能迁出指定商户", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能迁出指定商户,%w", err) + } + + err = tx.Commit(ctx) + if err != nil { + ts.log.Error("迁出指定商户失败,未能提交数据库事务", zap.Error(err)) + tx.Rollback(ctx) + return fmt.Errorf("未能提交数据库事务,%w", err) + } + + cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) + cache.AbolishRelation(fmt.Sprintf("meter:%s", pid)) + return nil +} diff --git a/vo/reading.go b/vo/reading.go index d3c3f1e..0826d01 100644 --- a/vo/reading.go +++ b/vo/reading.go @@ -22,6 +22,11 @@ func (r MeterReadingForm) Validate() bool { return flat.LessThan(decimal.Zero) } +type MeterReadingFormWithCode struct { + Code string `json:"code"` + MeterReadingForm +} + type MeterReadingDetailResponse struct { Code string `json:"code"` Park string `json:"parkId"`