package repository import ( "context" "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/tools" "electricity_bill_calc/tools/serial" "electricity_bill_calc/types" "electricity_bill_calc/vo" "fmt" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "go.uber.org/zap" ) type _TenementRepository struct { log *zap.Logger ds goqu.DialectWrapper } var TenementRepository = _TenementRepository{ log: logger.Named("Repository", "Tenement"), ds: goqu.Dialect("postgres"), } // 判断指定商户是否属于指定用户的管辖 func (tr _TenementRepository) IsTenementBelongs(tid, uid string) (bool, error) { tr.log.Info("检查指定商户是否属于指定企业管辖", zap.String("Tenement", tid), zap.String("Enterprise", uid)) cacheConditions := []string{ tid, "belongs", uid, } if exists, err := cache.CheckExists("tenement", cacheConditions...); err != nil && exists { tr.log.Error("检查指定商户是否属于指定企业管辖失败", zap.Error(err)) return exists, err } ctx, cancel := global.TimeoutContext() defer cancel() countSql, countArgs, _ := tr.ds. From(goqu.T("tenement").As("t")). Join(goqu.T("park").As("p"), goqu.On(goqu.I("t.park_id").Eq(goqu.I("p.id")))). Select(goqu.COUNT("t.*")). Where( goqu.I("t.id").Eq(tid), goqu.I("p.user_id").Eq(uid), ). Prepared(true).ToSQL() var count int if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil { tr.log.Error("检查指定商户是否属于指定企业管辖失败", zap.Error(err)) return false, err } if count > 0 { cache.CacheExists([]string{fmt.Sprintf("tenement:%s", tid)}, "tenement", cacheConditions...) } return count > 0, nil } // 列出指定园区中的所有商户 func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, building *string, startDate, endDate *types.Date, state *string) ([]*model.Tenement, int64, error) { tr.log.Info( "检索查询指定园区中符合条件的商户", zap.String("Park", pid), zap.Uint("Page", page), zap.Stringp("Keyword", keyword), zap.Stringp("Building", building), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("State", state), ) cacheConditions := []string{ pid, fmt.Sprintf("%d", page), cache.NullableStringKey(keyword), cache.NullableStringKey(building), cache.NullableConditionKey(startDate), cache.NullableConditionKey(endDate), cache.NullableStringKey(state), } if tenements, total, err := cache.RetrievePagedSearch[[]*model.Tenement]("tenements", cacheConditions...); err != nil && tenements != nil { tr.log.Info("从缓存中获取到了符合条件的商户记录", zap.Int64("Total", total), zap.Int("Count", len(*tenements))) return *tenements, total, nil } ctx, cancel := global.TimeoutContext() defer cancel() tenementQuery := tr.ds. From(goqu.T("tenement").As("t")). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). Select("t.*", goqu.I("b.name").As("building_name")) countQuery := tr.ds. From(goqu.T("tenement").As("t")). Select(goqu.COUNT("t.*")) if keyword != nil && len(*keyword) > 0 { pattern := fmt.Sprintf("%%%s%%", *keyword) tenementQuery = tenementQuery.Where( goqu.Or( goqu.I("t.full_name").ILike(pattern), goqu.I("t.short_name").ILike(pattern), goqu.I("t.abbr").ILike(pattern), goqu.I("t.contact_name").ILike(pattern), goqu.I("t.contact_phone").ILike(pattern), goqu.I("t.address").ILike(pattern), ), ) countQuery = countQuery.Where( goqu.Or( goqu.I("t.full_name").ILike(pattern), goqu.I("t.short_name").ILike(pattern), goqu.I("t.abbr").ILike(pattern), goqu.I("t.contact_name").ILike(pattern), goqu.I("t.contact_phone").ILike(pattern), goqu.I("t.address").ILike(pattern), ), ) } if building != nil && len(*building) > 0 { tenementQuery = tenementQuery.Where(goqu.I("t.building").Eq(*building)) countQuery = countQuery.Where(goqu.I("t.building").Eq(*building)) } if startDate != nil { tenementQuery = tenementQuery.Where( goqu.Or( goqu.I("t.moved_in_at").Gte(startDate.ToBeginningOfDate()), goqu.I("t.moved_out_at").Gte(startDate.ToBeginningOfDate()), ), ) countQuery = countQuery.Where( goqu.Or( goqu.I("t.moved_in_at").Gte(startDate.ToBeginningOfDate()), goqu.I("t.moved_out_at").Gte(startDate.ToBeginningOfDate()), ), ) } if endDate != nil { tenementQuery = tenementQuery.Where( goqu.Or( goqu.I("t.moved_in_at").Lte(endDate.ToEndingOfDate()), goqu.I("t.moved_out_at").Lte(endDate.ToEndingOfDate()), ), ) countQuery = countQuery.Where( goqu.Or( goqu.I("t.moved_in_at").Lte(endDate.ToEndingOfDate()), goqu.I("t.moved_out_at").Lte(endDate.ToEndingOfDate()), ), ) } if state != nil && *state == "1" { tenementQuery = tenementQuery.Where( goqu.I("t.moved_out_at").IsNull(), ) countQuery = countQuery.Where( goqu.I("t.moved_out_at").IsNull(), ) } startRow := (page - 1) * config.ServiceSettings.ItemsPageSize tenementQuery = tenementQuery.Order(goqu.I("t.created_at").Desc()).Limit(config.ServiceSettings.ItemsPageSize).Offset(startRow) tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL() countSql, countArgs, _ := countQuery.Prepared(true).ToSQL() var ( tenements []*model.Tenement = make([]*model.Tenement, 0) total int64 ) if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil { tr.log.Error("检索查询指定园区中符合条件的商户失败", zap.Error(err)) return tenements, 0, err } if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil { tr.log.Error("检索查询指定园区中符合条件的商户总数量失败", zap.Error(err)) return tenements, 0, err } cache.CachePagedSearch(tenements, total, []string{fmt.Sprintf("tenements:%s", pid)}, "tenements", cacheConditions...) return tenements, total, nil } // 查询指定园区中某一商户下的所有表计编号,不包含公摊表计 func (tr _TenementRepository) ListMeterCodesBelongsTo(pid, tid string) ([]string, error) { tr.log.Info("查询指定商户下所有的表计编号", zap.String("Park", pid), zap.String("Tenement", tid)) cacheConditions := []string{ pid, tid, } if meterCodes, err := cache.RetrieveSearch[[]string]("tenement_submeter", cacheConditions...); err != nil && meterCodes != nil { tr.log.Info("从缓存中获取到了指定商户下所有的表计编号", zap.Int("Count", len(*meterCodes))) return *meterCodes, nil } ctx, cancel := global.TimeoutContext() defer cancel() sql, args, _ := tr.ds. From("tenement_meter"). Select("meter_id"). Where( goqu.I("park_id").Eq(pid), goqu.I("tenement_id").Eq(tid), goqu.I("disassociated_at").IsNull(), ). Prepared(true).ToSQL() var meterCodes []string = make([]string, 0) if err := pgxscan.Select(ctx, global.DB, &meterCodes, sql, args...); err != nil { tr.log.Error("查询指定商户下所有的表计编号失败", zap.Error(err)) return meterCodes, err } cache.CacheSearch(&meterCodes, []string{"tenement", "tenement_submeter", "meter", fmt.Sprintf("tenement:%s", pid)}, "tenement_submeter", cacheConditions...) return meterCodes, nil } // 在指定园区中创建一个新的商户 func (tr _TenementRepository) AddTenement(tx pgx.Tx, ctx context.Context, pid string, tenement *vo.TenementCreationForm) error { tr.log.Info("在指定园区中创建一个新的商户", zap.String("Park", pid)) serial.StringSerialRequestChan <- 1 tenementId := serial.Prefix("T", <-serial.StringSerialResponseChan) currentTime := types.Now() createSql, createArgs, _ := tr.ds. Insert("tenement"). Cols( "id", "park_id", "full_name", "short_name", "abbr", "address", "contact_name", "contact_phone", "building", "on_floor", "invoice_info", "moved_in_at", "created_at", "last_modified_at", ). Vals( goqu.Vals{ tenementId, pid, tenement.Name, tenement.ShortName, tools.PinyinAbbr(tenement.Name), tenement.Address, tenement.Contact, tenement.Phone, tenement.Building, tenement.OnFloor, &model.InvoiceTitle{ Name: tenement.Name, USCI: tenement.USCI, Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""), Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""), Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""), Account: tools.DefaultOrEmptyStr(tenement.Account, ""), }, currentTime, currentTime, currentTime, }, ). Prepared(true).ToSQL() if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil { tr.log.Error("在指定园区中创建一个新的商户失败", zap.Error(err)) return err } return nil } // 向园区中指定商户下绑定一个新的表计 func (tr _TenementRepository) BindMeter(tx pgx.Tx, ctx context.Context, pid, tid, meter string) error { tr.log.Info("向园区中指定商户下绑定一个新的表计", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meter)) createSql, createArgs, _ := tr.ds. Insert("tenement_meter"). Cols( "park_id", "tenement_id", "meter_id", "associated_at", ). Vals( goqu.Vals{ pid, tid, meter, types.Now(), }, ). Prepared(true).ToSQL() if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil { tr.log.Error("向园区中指定商户下绑定一个新的表计失败", zap.Error(err)) return err } return nil } // 将指定商户与指定表计解绑 func (tr _TenementRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, tid, meter string) error { tr.log.Info("将指定商户与指定表计解绑", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Meter", meter)) updateSql, updateArgs, _ := tr.ds. Update("tenement_meter"). Set( goqu.Record{ "disassociated_at": types.Now(), }, ). Where( goqu.I("park_id").Eq(pid), goqu.I("tenement_id").Eq(tid), goqu.I("meter_id").Eq(meter), ). Prepared(true).ToSQL() if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil { tr.log.Error("将指定商户与指定表计解绑失败", zap.Error(err)) return err } return nil } // 修改指定商户的信息 func (tr _TenementRepository) UpdateTenement(pid, tid string, tenement *vo.TenementCreationForm) error { tr.log.Info("修改指定商户的信息", zap.String("Park", pid), zap.String("Tenement", tid)) ctx, cancel := global.TimeoutContext() defer cancel() updateSql, updateArgs, _ := tr.ds. Update("tenement"). Set( goqu.Record{ "full_name": tenement.Name, "short_name": tenement.ShortName, "abbr": tools.PinyinAbbr(tenement.Name), "address": tenement.Address, "contact_name": tenement.Contact, "contact_phone": tenement.Phone, "building": tenement.Building, "on_floor": tenement.OnFloor, "invoice_info": &model.InvoiceTitle{ Name: tenement.Name, USCI: tenement.USCI, Address: tools.DefaultOrEmptyStr(tenement.InvoiceAddress, ""), Phone: tools.DefaultOrEmptyStr(tenement.InvoicePhone, ""), Bank: tools.DefaultOrEmptyStr(tenement.Bank, ""), Account: tools.DefaultOrEmptyStr(tenement.Account, ""), }, "last_modified_at": types.Now(), }, ). Where( goqu.I("id").Eq(tid), goqu.I("park_id").Eq(pid), ). Prepared(true).ToSQL() if _, err := global.DB.Exec(ctx, updateSql, updateArgs...); err != nil { tr.log.Error("修改指定商户的信息失败", zap.Error(err)) return err } cache.AbolishRelation(fmt.Sprintf("tenement:%s", pid)) return nil } // 迁出指定商户 func (tr _TenementRepository) MoveOut(tx pgx.Tx, ctx context.Context, pid, tid string) error { tr.log.Info("迁出指定商户", zap.String("Park", pid), zap.String("Tenement", tid)) updateSql, updateArgs, _ := tr.ds. Update("tenement"). Set( goqu.Record{ "moved_out_at": types.Now(), }, ). Where( goqu.I("id").Eq(tid), goqu.I("park_id").Eq(pid), ). Prepared(true).ToSQL() if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil { tr.log.Error("迁出指定商户失败", zap.Error(err)) return err } return nil } // 列出用于下拉列表的符合指定条件的商户信息 func (tr _TenementRepository) ListForSelect(uid string, pid, keyword *string, limit *uint) ([]*model.Tenement, error) { tr.log.Info("列出用于下拉列表的符合指定条件的商户信息", zap.String("Ent", uid), zap.String("Park", tools.DefaultOrEmptyStr(pid, "All")), zap.Stringp("Keyword", keyword), zap.Uintp("Limit", limit)) cacheConditions := []string{ uid, cache.NullableStringKey(pid), cache.NullableStringKey(keyword), fmt.Sprintf("%d", tools.DefaultTo(limit, 0)), } if tenements, err := cache.RetrieveSearch[[]*model.Tenement]("tenement_choice", cacheConditions...); err != nil && tenements != nil { tr.log.Info("从缓存中获取到了用于下拉列表的符合指定条件的商户信息", zap.Int("Count", len(*tenements))) return *tenements, nil } ctx, cancel := global.TimeoutContext() defer cancel() tenementQuery := tr.ds. From(goqu.T("tenement").As("t")). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("b.park_id")))). Select( "t.*", goqu.I("b.name").As("building_name"), ). Where( goqu.I("p.user_id").Eq(uid), goqu.I("t.moved_out_at").IsNull(), ) if pid != nil && len(*pid) > 0 { tenementQuery = tenementQuery.Where(goqu.I("p.id").Eq(*pid)) } if keyword != nil && len(*keyword) > 0 { pattern := fmt.Sprintf("%%%s%%", *keyword) tenementQuery = tenementQuery.Where( goqu.Or( goqu.I("t.full_name").ILike(pattern), goqu.I("t.short_name").ILike(pattern), goqu.I("t.abbr").ILike(pattern), ), ) } tenementQuery = tenementQuery.Order(goqu.I("t.created_at").Desc()) if limit != nil && *limit > 0 { tenementQuery = tenementQuery.Limit(*limit) } tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL() var tenements = make([]*model.Tenement, 0) if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil { tr.log.Error("列出用于下拉列表的符合指定条件的商户信息失败", zap.Error(err)) return tenements, err } cache.CacheSearch(&tenements, []string{"tenement"}, "tenement_choice", cacheConditions...) return tenements, nil } // 列出指定园区中在指定时间区间内存在过入住的商户 func (tr _TenementRepository) ListTenementsInTimeRange(pid string, start, end types.Date) ([]*model.Tenement, error) { tr.log.Info("列出指定园区中在指定时间区间内存在过入住的商户", zap.String("Park", pid), logger.DateField("Start", start), logger.DateField("End", end)) ctx, cancel := global.TimeoutContext() defer cancel() tenementQuery := tr.ds. From(goqu.T("tenement").As("t")). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). Select( "t.*", goqu.I("b.name").As("building_name"), ). Where( goqu.I("t.park_id").Eq(pid), goqu.I("t.moved_in_at").Lte(end.ToEndingOfDate()), goqu.Or( goqu.I("t.moved_out_at").IsNull(), goqu.I("t.moved_out_at").Gte(start.ToBeginningOfDate()), ), ). Order(goqu.I("t.created_at").Desc()) tenementSql, tenementArgs, _ := tenementQuery.Prepared(true).ToSQL() var tenements = make([]*model.Tenement, 0) if err := pgxscan.Select(ctx, global.DB, &tenements, tenementSql, tenementArgs...); err != nil { tr.log.Error("列出指定园区中在指定时间区间内存在过入住的商户失败", zap.Error(err)) return tenements, err } return tenements, nil } // 获取指定园区中指定商户的详细信息 func (tr _TenementRepository) RetrieveTenementDetail(pid, tid string) (*model.Tenement, error) { tr.log.Info("获取指定园区中指定商户的详细信息", zap.String("Park", pid), zap.String("Tenement", tid)) if tenement, err := cache.RetrieveEntity[model.Tenement](fmt.Sprintf("tenement:%s", pid), tid); err != nil && tenement != nil { tr.log.Info("从缓存中获取到了指定园区中指定商户的详细信息") return tenement, nil } ctx, cancel := global.TimeoutContext() defer cancel() tenementSql, tenementArgs, _ := tr.ds. From(goqu.T("tenement").As("t")). LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("t.building")))). Select( "t.*", goqu.I("b.name").As("building_name"), ). Where( goqu.I("t.id").Eq(tid), goqu.I("t.park_id").Eq(pid), ). Prepared(true).ToSQL() var tenement model.Tenement if err := pgxscan.Get(ctx, global.DB, &tenement, tenementSql, tenementArgs...); err != nil { tr.log.Error("获取指定园区中指定商户的详细信息失败", zap.Error(err)) return nil, err } cache.CacheEntity(&tenement, []string{"tenement", fmt.Sprintf("tenement:%s", pid)}, fmt.Sprintf("tenement:%s", pid), tid) return &tenement, nil }