From 2558a83024613037bcad502dcfc1b7d52163a4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 14 Jun 2023 14:26:18 +0800 Subject: [PATCH] =?UTF-8?q?enhance(tenement):=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=95=86=E6=88=B7=E9=83=A8=E5=88=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=E5=BA=93=E4=BA=A4=E4=BA=92=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/invoice.go | 10 + model/tenement.go | 22 ++ repository/tenement.go | 517 +++++++++++++++++++++++++++++++++++++++++ vo/tenement.go | 16 ++ 4 files changed, 565 insertions(+) create mode 100644 model/invoice.go create mode 100644 model/tenement.go create mode 100644 repository/tenement.go create mode 100644 vo/tenement.go diff --git a/model/invoice.go b/model/invoice.go new file mode 100644 index 0000000..1f07041 --- /dev/null +++ b/model/invoice.go @@ -0,0 +1,10 @@ +package model + +type InvoiceTitle struct { + Name string `json:"name"` + USCI string `json:"usci"` + Address string `json:"address"` + Phone string `json:"phone"` + Bank string `json:"bank"` + Account string `json:"account"` +} diff --git a/model/tenement.go b/model/tenement.go new file mode 100644 index 0000000..16448f4 --- /dev/null +++ b/model/tenement.go @@ -0,0 +1,22 @@ +package model + +import "electricity_bill_calc/types" + +type Tenement struct { + Id string `json:"id"` + Park string `json:"parkId" db:"park_id"` + FullName string `json:"fullName" db:"full_name"` + ShortName *string `json:"shortName" db:"short_name"` + Address string `json:"address"` + ContactName string `json:"contactName" db:"contact_name"` + ContactPhone string `json:"contactPhone" db:"contact_phone"` + Building string `json:"building"` + BuildingName *string `json:"buildingName" db:"building_name"` + OnFloor *string `json:"onFloor" db:"on_floor"` + InvoiceInfo *InvoiceTitle `json:"invoiceInfo" db:"invoice_info"` + MovedInAt *types.DateTime `json:"movedInAt" db:"moved_in_at"` + MovedOutAt *types.DateTime `json:"movedOutAt" db:"moved_out_at"` + CreatedAt types.DateTime `json:"createdAt" db:"created_at"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` + DeletedAt *types.DateTime `json:"deletedAt" db:"deleted_at"` +} diff --git a/repository/tenement.go b/repository/tenement.go new file mode 100644 index 0000000..48642a6 --- /dev/null +++ b/repository/tenement.go @@ -0,0 +1,517 @@ +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 +} diff --git a/vo/tenement.go b/vo/tenement.go new file mode 100644 index 0000000..5916612 --- /dev/null +++ b/vo/tenement.go @@ -0,0 +1,16 @@ +package vo + +type TenementCreationForm struct { + Name string `json:"name"` + ShortName *string `json:"shortName"` + Address string `json:"address"` + Contact string `json:"contact"` + Phone string `json:"phone"` + Building *string `json:"building"` + OnFloor *string `json:"onFloor"` + USCI string `json:"usci"` + InvoiceAddress *string `json:"invoiceAddress"` + InvoicePhone *string `json:"invoicePhone"` + Bank *string `json:"bank"` + Account *string `json:"bankAccount"` +}