合并分支

This commit is contained in:
2023-08-04 17:11:10 +08:00
parent 12ec8d26bf
commit 020e76b901
100 changed files with 12692 additions and 2574 deletions

70
repository/calculate.go Normal file
View File

@@ -0,0 +1,70 @@
package repository
import (
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _CalculateRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var CalculateRepository = _CalculateRepository{
log: logger.Named("Repository", "Calculate"),
ds: goqu.Dialect("postgres"),
}
// 获取当前正在等待计算的核算任务ID列表
func (cr _CalculateRepository) ListPendingTasks() ([]string, error) {
cr.log.Info("获取当前正在等待计算的核算任务ID列表")
ctx, cancel := global.TimeoutContext()
defer cancel()
var ids []string
querySql, queryArgs, _ := cr.ds.
From("report_task").
Select("id").
Where(goqu.C("status").Eq(model.REPORT_CALCULATE_TASK_STATUS_PENDING)).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &ids, querySql, queryArgs...); err != nil {
cr.log.Error("未能获取到当前正在等待计算的核算任务ID列表", zap.Error(err))
return nil, err
}
return ids, nil
}
// 更新指定报表的核算状态
func (cr _CalculateRepository) UpdateReportTaskStatus(rid string, status int16, message *string) (bool, error) {
cr.log.Info("更新指定报表的核算状态", zap.String("Report", rid), zap.Int16("Status", status))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := cr.ds.
Update("report_task").
Set(goqu.Record{
"status": status,
"last_modified_at": currentTime,
"message": message,
}).
Where(goqu.C("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
cr.log.Error("未能更新指定报表的核算状态", zap.Error(err))
return false, err
}
if res.RowsAffected() == 0 {
cr.log.Warn("未能保存指定报表的核算状态", zap.String("Report", rid))
return false, nil
}
return res.RowsAffected() > 0, nil
}

152
repository/charge.go Normal file
View File

@@ -0,0 +1,152 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"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"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _ChargeRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var ChargeRepository = &_ChargeRepository{
log: logger.Named("Repository", "Charge"),
ds: goqu.Dialect("postgres"),
}
// 分页查询用户的充值记录
func (cr _ChargeRepository) FindCharges(page uint, beginTime, endTime *types.Date, keyword *string) ([]*model.UserChargeDetail, int64, error) {
cr.log.Info("查询用户的充值记录。", logger.DateFieldp("beginTime", beginTime), logger.DateFieldp("endTime", endTime), zap.Stringp("keyword", keyword), zap.Uint("page", page))
ctx, cancel := global.TimeoutContext()
defer cancel()
chargeQuery := cr.ds.
From(goqu.T("user_charge").As("c")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))).
Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.id").Eq(goqu.I("u.id")))).
Select(
"c.seq", "c.user_id", "ud.name", "c.fee", "c.discount", "c.amount", "c.charge_to",
"c.settled", "c.settled_at", "c.cancelled", "c.cancelled_at", "c.refunded", "c.refunded_at", "c.created_at",
)
countQuery := cr.ds.
From(goqu.T("user_charge").As("c")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("c.user_id").Eq(goqu.I("ud.id")))).
Join(goqu.T("user").As("u"), goqu.On(goqu.I("ud.id").Eq(goqu.I("u.id")))).
Select(goqu.COUNT("*"))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
chargeQuery = chargeQuery.Where(goqu.Or(
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("u.username").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("u.username").ILike(pattern),
))
}
if beginTime != nil {
chargeQuery = chargeQuery.Where(goqu.I("c.created_at").Gte(beginTime.ToBeginningOfDate()))
countQuery = countQuery.Where(goqu.I("c.created_at").Gte(beginTime.ToBeginningOfDate()))
}
if endTime != nil {
chargeQuery = chargeQuery.Where(goqu.I("c.created_at").Lte(endTime.ToEndingOfDate()))
countQuery = countQuery.Where(goqu.I("c.created_at").Lte(endTime.ToEndingOfDate()))
}
chargeQuery = chargeQuery.Order(goqu.I("c.created_at").Desc())
currentPostion := (page - 1) * config.ServiceSettings.ItemsPageSize
chargeQuery = chargeQuery.Offset(currentPostion).Limit(config.ServiceSettings.ItemsPageSize)
chargeSql, chargeArgs, _ := chargeQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
charges []*model.UserChargeDetail = make([]*model.UserChargeDetail, 0)
total int64
)
if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil {
cr.log.Error("查询用户的充值记录失败。", zap.Error(err))
return make([]*model.UserChargeDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
cr.log.Error("查询用户的充值记录总数失败。", zap.Error(err))
return make([]*model.UserChargeDetail, 0), 0, err
}
return charges, total, nil
}
// 在用户充值记录中创建一条新的记录
func (cr _ChargeRepository) CreateChargeRecord(tx pgx.Tx, ctx context.Context, uid string, fee, discount, amount *float64, chargeTo types.Date) (bool, error) {
createQuery, createArgs, _ := cr.ds.
Insert(goqu.T("user_charge")).
Cols("user_id", "fee", "discount", "amount", "charge_to", "created_at").
Vals(goqu.Vals{uid, fee, discount, amount, chargeTo, types.Now()}).
Prepared(true).ToSQL()
rs, err := tx.Exec(ctx, createQuery, createArgs...)
if err != nil {
cr.log.Error("创建用户充值记录失败。", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 撤销用户的充值记录
func (cr _ChargeRepository) CancelCharge(tx pgx.Tx, ctx context.Context, uid string, seq int64) (bool, error) {
updateQuerySql, updateArgs, _ := cr.ds.
Update(goqu.T("user_charge")).
Set(goqu.Record{"cancelled": true, "cancelled_at": types.Now()}).
Where(goqu.I("user_id").Eq(uid), goqu.I("seq").Eq(seq)).
Prepared(true).ToSQL()
rs, err := tx.Exec(ctx, updateQuerySql, updateArgs...)
if err != nil {
cr.log.Error("撤销用户的充值记录失败。", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 检索用户最近有效的服务期限
func (cr _ChargeRepository) LatestValidChargeTo(tx pgx.Tx, ctx context.Context, uid string) (*types.Date, error) {
searchSql, searchArgs, _ := cr.ds.
From(goqu.T("user_charge")).
Select("charge_to").
Where(
goqu.I("settled").Eq(true),
goqu.I("cancelled").Eq(false),
goqu.I("refunded").Eq(false),
goqu.I("user_id").Eq(uid),
).
Prepared(true).ToSQL()
var chargeTo []*types.Date
if err := pgxscan.Select(ctx, tx, &chargeTo, searchSql, searchArgs...); err != nil {
cr.log.Error("检索用户有效服务期限列表失败。", zap.Error(err))
return nil, err
}
if len(chargeTo) == 0 {
return nil, fmt.Errorf("无法找到用户最近的有效服务期限。")
}
lastCharge := lo.MaxBy(chargeTo, func(a, b *types.Date) bool { return a.Time.After(b.Time) })
return lastCharge, nil
}

19
repository/god.go Normal file
View File

@@ -0,0 +1,19 @@
package repository
import (
"electricity_bill_calc/logger"
"github.com/doug-martin/goqu/v9"
"go.uber.org/zap"
)
type _GodModRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var GodModRepository = _GodModRepository{
log: logger.Named("Repository", "GodMod"),
ds: goqu.Dialect("postgres"),
}
// 删除指定园区中的表计和商户的绑定关系

348
repository/invoice.go Normal file
View File

@@ -0,0 +1,348 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/types"
"errors"
"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"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
type _InvoiceRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var InvoiceRepository = _InvoiceRepository{
log: logger.Named("Repository", "Invoice"),
ds: goqu.Dialect("postgres"),
}
// 查询指定园区中符合条件的发票
func (ir _InvoiceRepository) ListInvoice(pid *string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.Invoice, int64, error) {
ir.log.Info("查询指定园区的发票。", zap.Stringp("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("Keyword", keyword), zap.Uint("Page", page))
ctx, cancel := global.TimeoutContext()
defer cancel()
invoiceQuery := ir.ds.
From(goqu.T("invoice").As("i")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("i.tenement_id").Eq(goqu.I("t.id")))).
Select("i.*")
countQuery := ir.ds.
From(goqu.T("invoice").As("i")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("i.tenement_id").Eq(goqu.I("t.id")))).
Select(goqu.COUNT("*"))
if pid != nil && len(*pid) > 0 {
invoiceQuery = invoiceQuery.Where(goqu.I("t.park_id").Eq(*pid))
countQuery = countQuery.Where(goqu.I("t.park_id").Eq(*pid))
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
invoiceQuery = invoiceQuery.Where(goqu.Or(
goqu.I("i.invoice_no").ILike(pattern),
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.L("t.invoice_info->>'usci'").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("i.invoice_no").ILike(pattern),
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.L("t.invoice_info->>'usci'").ILike(pattern),
))
}
var queryRange = types.NewEmptyDateTimeRange()
if startDate != nil {
queryRange.SetLower(startDate.ToBeginningOfDate())
}
if endDate != nil {
queryRange.SetUpper(endDate.ToEndingOfDate())
}
if !queryRange.IsEmptyOrWild() {
invoiceQuery = invoiceQuery.Where(goqu.L("i.issued_at <@ ?", queryRange))
countQuery = countQuery.Where(goqu.L("i.issued_at <@ ?", queryRange))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
invoiceQuery = invoiceQuery.
Order(goqu.I("i.issued_at").Desc()).
Offset(startRow).
Limit(config.ServiceSettings.ItemsPageSize)
var (
invoices []*model.Invoice = make([]*model.Invoice, 0)
total int64
)
querySql, queryArgs, _ := invoiceQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &invoices, querySql, queryArgs...); err != nil {
ir.log.Error("查询发票记录失败。", zap.Error(err))
return invoices, 0, err
}
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
ir.log.Error("查询发票记录数失败。", zap.Error(err))
return invoices, 0, err
}
return invoices, total, nil
}
// 查询指定商户未开票的核算记录,改记录将只包括商户整体核算,不包括商户各个表计的详细
func (ir _InvoiceRepository) ListUninvoicedTenementCharges(tid string) ([]*model.SimplifiedTenementCharge, error) {
ir.log.Info("查询指定商户的未开票核算记录", zap.String("Tenement", tid))
ctx, cancel := global.TimeoutContext()
defer cancel()
chargeSql, chargeArgs, _ := ir.ds.
From(goqu.T("report_tenement").As("t")).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("t.report_id").Eq(goqu.I("r.id")))).
Select(
goqu.I("t.report_id"),
goqu.I("r.period"),
goqu.L("(t.overall->>'amount')::numeric").As("amount"),
goqu.I("t.final_charge"),
).
Where(
goqu.I("t.tenement_id").Eq(tid),
goqu.I("t.invoice").IsNull(),
).
Prepared(true).ToSQL()
var charges []*model.SimplifiedTenementCharge
if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil {
ir.log.Error("查询未开票核算记录失败。", zap.Error(err))
return charges, err
}
return charges, nil
}
// 更新指定核算中指定商户的开票状态以及对应发票号。
// 如果给定了发票号,那么指定记录状态为已开票,如果给定的发票号为`nil`,啊么指定记录为未开票。
func (ir _InvoiceRepository) UpdateTenementInvoicedState(tx pgx.Tx, ctx context.Context, rid, tid string, invoiceNo *string) error {
ir.log.Info("更新指定核算中指定商户的开票状态和记录", zap.String("Report", rid), zap.String("Tenement", tid), zap.Stringp("InvoiceNo", invoiceNo))
updateSql, updateArgs, _ := ir.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{
"invoice": invoiceNo,
}).
Where(
goqu.I("report_id").Eq(rid),
goqu.I("tenement_id").Eq(tid),
).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
ir.log.Error("更新核算记录的开票状态失败。", zap.Error(err))
return err
}
return nil
}
// 查询指定发票的详细记录信息
func (ir _InvoiceRepository) GetInvoiceDetail(invoiceNo string) (*model.Invoice, error) {
ir.log.Info("查询指定发票的详细信息", zap.String("InvoiceNo", invoiceNo))
ctx, cancel := global.TimeoutContext()
defer cancel()
invoiceSql, invoiceArgs, _ := ir.ds.
From(goqu.T("invoice")).
Select("*").
Where(goqu.I("invoice_no").Eq(invoiceNo)).
Prepared(true).ToSQL()
var invoice model.Invoice
if err := pgxscan.Get(ctx, global.DB, &invoice, invoiceSql, invoiceArgs...); err != nil {
ir.log.Error("查询发票记录失败。", zap.Error(err))
return nil, err
}
return &invoice, nil
}
// 获取指定商户的简化核算记录
func (ir _InvoiceRepository) GetSimplifiedTenementCharges(tid string, rids []string) ([]*model.SimplifiedTenementCharge, error) {
ir.log.Info("查询庄园商户的简化核算记录", zap.String("Tenement", tid), zap.Strings("Reports", rids))
ctx, cancel := global.TimeoutContext()
defer cancel()
chargeSql, chargeArgs, _ := ir.ds.
From(goqu.T("report_tenement").As("t")).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("t.report_id").Eq(goqu.I("r.id")))).
Select(
goqu.I("t.report_id"),
goqu.I("r.period"),
goqu.L("(t.overall->>'amount')::numeric").As("amount"),
goqu.I("t.final_charge"),
).
Where(
goqu.I("t.tenement_id").Eq(tid),
goqu.I("t.report_id").In(rids),
).
Prepared(true).ToSQL()
var charges []*model.SimplifiedTenementCharge
if err := pgxscan.Select(ctx, global.DB, &charges, chargeSql, chargeArgs...); err != nil {
ir.log.Error("查询简化核算记录失败。", zap.Error(err))
return charges, err
}
return charges, nil
}
// 查询发票号码对应的商户 ID
// ! 这个方法不能被加入缓存,这个方法存在的目的就是为了清除缓存。
func (ir _InvoiceRepository) GetInvoiceBelongs(invoiceNo string) ([]string, error) {
ir.log.Info("查询发票号码对应的商户 ID", zap.String("InvoiceNo", invoiceNo))
ctx, cancel := global.TimeoutContext()
defer cancel()
tenementSql, tenementArgs, _ := ir.ds.
From(goqu.T("invoice")).
Select("tenement_id").
Where(goqu.I("i.invoice_no").Eq(invoiceNo)).
Prepared(true).ToSQL()
var tenementIds []string
if err := pgxscan.Select(ctx, global.DB, &tenementIds, tenementSql, tenementArgs...); err != nil {
ir.log.Error("查询发票号码对应的商户 ID 失败。", zap.Error(err))
return tenementIds, err
}
return tenementIds, nil
}
// 删除指定的发票记录
func (ir _InvoiceRepository) Delete(tx pgx.Tx, ctx context.Context, invoiceNo string) error {
ir.log.Info("删除指定的发票记录", zap.String("InvoiceNo", invoiceNo))
deleteSql, deleteArgs, _ := ir.ds.
Delete(goqu.T("invoice")).
Where(goqu.I("invoice_no").Eq(invoiceNo)).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, deleteSql, deleteArgs...); err != nil {
ir.log.Error("删除发票记录失败。", zap.Error(err))
return err
}
return nil
}
// 删除指定发票记录与指定核算记录之间的关联
func (ir _InvoiceRepository) DeleteInvoiceTenementRelation(tx pgx.Tx, ctx context.Context, invoiceNo string) error {
ir.log.Info("删除指定发票记录与指定核算记录之间的关联", zap.String("InvoiceNo", invoiceNo))
updateSql, updateArgs, _ := ir.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{
"invoice": nil,
}).
Where(goqu.I("invoice").Eq(invoiceNo)).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
ir.log.Error("删除发票记录与核算记录之间的关联失败。", zap.Error(err))
return err
}
return nil
}
// 确认发票的归属
func (ir _InvoiceRepository) IsBelongsTo(invoiceNo, uid string) (bool, error) {
ir.log.Info("确认发票的归属", zap.String("InvoiceNo", invoiceNo), zap.String("User", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryArgs, _ := ir.ds.
From(goqu.T("invoice").As("i")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("i.park_id")))).
Select(goqu.COUNT("i.*")).
Where(
goqu.I("i.invoice_no").Eq(invoiceNo),
goqu.I("p.user_id").Eq(uid),
).
Prepared(true).ToSQL()
var count int64
if err := pgxscan.Get(ctx, global.DB, &count, querySql, queryArgs...); err != nil {
ir.log.Error("查询发票归属失败", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 创建一条新的发票记录
func (ir _InvoiceRepository) Create(pid, tid, invoiceNo string, invoiceType *string, amount decimal.Decimal, issuedAt types.DateTime, taxMethod int16, taxRate decimal.Decimal, cargos *[]*model.InvoiceCargo, covers *[]string) error {
ir.log.Info("记录一个新的发票", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("Invoice", invoiceNo))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ir.log.Error("开启事务失败。", zap.Error(err))
return err
}
tenemenetSql, tenementArgs, _ := ir.ds.
From(goqu.T("tenement").As("t")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("t.building").Eq(goqu.I("b.id")))).
Select(
"t.*", goqu.I("b.name").As("building_name"),
).
Where(goqu.I("t.id").Eq(tid)).
Prepared(true).ToSQL()
var tenement model.Tenement
if err := pgxscan.Get(ctx, global.DB, &tenement, tenemenetSql, tenementArgs...); err != nil {
ir.log.Error("查询商户信息失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
if tenement.InvoiceInfo == nil {
ir.log.Error("尚未设定商户的发票抬头信息")
tx.Rollback(ctx)
return errors.New("尚未设定商户的发票抬头信息")
}
createSql, createArgs, _ := ir.ds.
Insert(goqu.T("invoice")).
Cols(
"invoice_no", "park_id", "tenement_id", "invoice_type", "amount", "issued_at", "tax_method", "tax_rate", "cargos", "covers",
).
Vals(goqu.Vals{
invoiceNo, pid, tid, invoiceType, amount, issuedAt, taxMethod, taxRate, cargos, covers,
}).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, createSql, createArgs...); err != nil {
ir.log.Error("创建发票记录失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
updateSql, updateArgs, _ := ir.ds.
Update(goqu.T("report_tenement")).
Set(goqu.Record{
"invoice": invoiceNo,
}).
Where(
goqu.I("tenement_id").Eq(tid),
goqu.I("report_id").In(*covers),
).
Prepared(true).ToSQL()
if _, err := tx.Exec(ctx, updateSql, updateArgs...); err != nil {
ir.log.Error("更新核算记录的开票状态失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
err = tx.Commit(ctx)
if err != nil {
ir.log.Error("提交事务失败。", zap.Error(err))
tx.Rollback(ctx)
return err
}
return nil
}

991
repository/meter.go Normal file
View File

@@ -0,0 +1,991 @@
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"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
type _MeterRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var MeterRepository = _MeterRepository{
log: logger.Named("Repository", "Meter"),
ds: goqu.Dialect("postgres"),
}
// 获取指定园区中所有的表计信息
func (mr _MeterRepository) AllMeters(pid string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定园区中的所有表计", zap.String("park id", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.detachedAt").IsNull(),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 列出指定园区下的所有表计信息,包含已经拆除的表计
func (mr _MeterRepository) AllUsedMeters(pid string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定园区中的所有使用过的表计", zap.String("park id", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 列出指定核算报表中所使用的所有表计,包含已经拆除的表计
func (mr _MeterRepository) AllUsedMetersInReport(rid string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定核算报表中所使用的所有表计", zap.String("report id", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("m.park_id").Eq(goqu.I("r.park_id")))).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("m.enabled").Eq(true),
goqu.L("m.attached_at::date < upper(r.period)"),
goqu.Or(
goqu.I("m.detached_at").IsNull(),
goqu.L("m.detached_at::date >= lower(r.period)"),
),
).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 分页列出指定园区下的表计信息
func (mr _MeterRepository) MetersIn(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) {
mr.log.Info("分页列出指定园区下的表计信息", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, "")))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.detached_at").IsNull(),
)
countQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
Select(goqu.COUNT("*")).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.detached_at").IsNull(),
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
countQuery = countQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
meterQuery = meterQuery.Order(goqu.I("m.seq").Asc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
meters []*model.MeterDetail
total int64
)
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
mr.log.Error("查询表计数量失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
return meters, total, nil
}
// 列出指定园区中指定列表中所有表计的详细信息,将忽略所有表计的当前状态
func (mr _MeterRepository) ListMetersByIDs(pid string, ids []string) ([]*model.MeterDetail, error) {
mr.log.Info("列出指定园区中指定列表中所有表计的详细信息", zap.String("park id", pid), zap.Strings("meter ids", ids))
if len(ids) == 0 {
return make([]*model.MeterDetail, 0), nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var meters []*model.MeterDetail
metersSql, metersArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.code").In(ids),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &meters, metersSql, metersArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 获取指定表计的详细信息
func (mr _MeterRepository) FetchMeterDetail(pid, code string) (*model.MeterDetail, error) {
mr.log.Info("获取指定表计的详细信息", zap.String("park id", pid), zap.String("meter code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var meter model.MeterDetail
meterSql, meterArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.code").Eq(code),
).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &meter, meterSql, meterArgs...); err != nil {
mr.log.Error("查询表计信息失败", zap.Error(err))
return nil, err
}
return &meter, nil
}
// 创建一条新的表计信息
func (mr _MeterRepository) CreateMeter(tx pgx.Tx, ctx context.Context, pid string, meter vo.MeterCreationForm) (bool, error) {
mr.log.Info("创建一条新的表计信息", zap.String("park id", pid), zap.String("meter code", meter.Code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Insert(goqu.T("meter_04kv")).
Cols(
"park_id", "code", "address", "ratio", "seq", "meter_type", "building", "on_floor", "area", "enabled",
"attached_at", "created_at", "last_modified_at",
).
Vals(
goqu.Vals{pid, meter.Code, meter.Address, meter.Ratio, meter.Seq, meter.MeterType, meter.Building, meter.OnFloor, meter.Area, meter.Enabled,
timeNow, timeNow, timeNow,
},
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("创建表计信息失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 创建或者更新一条表计的信息
func (mr _MeterRepository) CreateOrUpdateMeter(tx pgx.Tx, ctx context.Context, pid string, meter vo.MeterCreationForm) (bool, error) {
mr.log.Info("创建或者更新一条表计的信息", zap.String("park id", pid), zap.String("meter code", meter.Code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Insert(goqu.T("meter_04kv")).
Cols(
"park_id", "code", "address", "ratio", "seq", "meter_type", "building", "on_floor", "area", "enabled",
"attached_at", "created_at", "last_modified_at",
).
Vals(
goqu.Vals{pid, meter.Code, meter.Address, meter.Ratio, meter.Seq, meter.MeterType, meter.Building, meter.OnFloor, meter.Area, meter.Enabled,
timeNow, timeNow, timeNow,
},
).
OnConflict(
goqu.DoUpdate("code, park_id",
goqu.Record{
"address": goqu.I("excluded.address"),
"seq": goqu.I("excluded.seq"),
"ratio": goqu.I("excluded.ratio"),
"meter_type": goqu.I("excluded.meter_type"),
"building": goqu.I("excluded.building"),
"on_floor": goqu.I("excluded.on_floor"),
"area": goqu.I("excluded.area"),
"last_modified_at": goqu.I("excluded.last_modified_at"),
}),
).
Prepared(true).ToSQL()
res, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("创建或者更新表计信息失败", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 记录一条表计的抄表信息
func (mr _MeterRepository) RecordReading(tx pgx.Tx, ctx context.Context, pid, code string, meterType int16, ratio decimal.Decimal, reading *vo.MeterReadingForm) (bool, error) {
mr.log.Info("记录一条表计的抄表信息", zap.String("park id", pid), zap.String("meter code", code))
readAt := tools.DefaultTo(reading.ReadAt, types.Now())
readingSql, readingArgs, _ := mr.ds.
Insert(goqu.T("meter_reading")).
Cols(
"park_id", "meter_id", "read_at", "meter_type", "ratio", "overall", "critical", "peak", "flat", "valley",
).
Vals(
goqu.Vals{pid, code, readAt, meterType, ratio, reading.Overall, reading.Critical, reading.Peak, reading.Flat, reading.Valley},
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, readingSql, readingArgs...)
if err != nil {
mr.log.Error("记录表计抄表信息失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 更新一条表计的详细信息
func (mr _MeterRepository) UpdateMeter(tx pgx.Tx, ctx context.Context, pid, code string, detail *vo.MeterModificationForm) (bool, error) {
mr.log.Info("更新一条表计的详细信息", zap.String("park id", pid), zap.String("meter code", code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Update(goqu.T("meter_04kv")).
Set(
goqu.Record{
"address": detail.Address,
"seq": detail.Seq,
"ratio": detail.Ratio,
"enabled": detail.Enabled,
"meter_type": detail.MeterType,
"building": detail.Building,
"on_floor": detail.OnFloor,
"area": detail.Area,
"last_modified_at": timeNow,
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("code").Eq(code),
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("更新表计信息失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 列出指定园区中已经存在的表计编号,无论该表计是否已经不再使用。
func (mr _MeterRepository) ListMeterCodes(pid string) ([]string, error) {
mr.log.Info("列出指定园区中已经存在的表计编号", zap.String("park id", pid))
cacheConditions := []string{pid}
if codes, err := cache.RetrieveSearch[[]string]("meter_codes", cacheConditions...); err == nil {
mr.log.Info("从缓存中获取到了指定园区中的表计编号", zap.Int("count", len(*codes)))
return *codes, nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var codes []string
codesSql, codesArgs, _ := mr.ds.
From(goqu.T("meter_04kv")).
Select("code").
Where(
goqu.I("park_id").Eq(pid),
).
Order(goqu.I("seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &codes, codesSql, codesArgs...); err != nil {
mr.log.Error("查询表计编号失败", zap.Error(err))
return make([]string, 0), err
}
return codes, nil
}
// 解除指定园区中指定表计的使用
func (mr _MeterRepository) DetachMeter(tx pgx.Tx, ctx context.Context, pid, code string) (bool, error) {
mr.log.Info("解除指定园区中指定表计的使用", zap.String("park id", pid), zap.String("meter code", code))
timeNow := types.Now()
meterSql, meterArgs, _ := mr.ds.
Update(goqu.T("meter_04kv")).
Set(
goqu.Record{
"detached_at": timeNow,
"last_modified_at": timeNow,
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("code").Eq(code),
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, meterSql, meterArgs...)
if err != nil {
mr.log.Error("解除表计使用失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 将商户表计绑定到公摊表计上
func (mr _MeterRepository) BindMeter(tx pgx.Tx, ctx context.Context, pid, masterMeter, slaveMeter string) (bool, error) {
mr.log.Info("将商户表计绑定到公摊表计上", zap.String("master meter code", masterMeter), zap.String("slave meter code", slaveMeter))
masterDetail, err := mr.FetchMeterDetail(pid, masterMeter)
if err != nil {
mr.log.Error("查询公摊表计信息失败", zap.Error(err))
return false, err
}
if masterDetail.MeterType != model.METER_INSTALLATION_POOLING {
mr.log.Error("给定的公摊表计不是公摊表计", zap.Error(err))
return false, fmt.Errorf("给定的公摊表计不是公摊表计")
}
slaveDetail, err := mr.FetchMeterDetail(pid, slaveMeter)
if err != nil {
mr.log.Error("查询商户表计信息失败", zap.Error(err))
return false, err
}
if slaveDetail.MeterType != model.METER_INSTALLATION_TENEMENT {
mr.log.Error("给定的商户表计不是商户表计", zap.Error(err))
return false, fmt.Errorf("给定的商户表计不是商户表计")
}
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("PB", <-serial.StringSerialResponseChan)
relationSql, relationArgs, _ := mr.ds.
Insert(goqu.T("meter_relations")).
Cols(
"id", "park_id", "master_meter_id", "slave_meter_id", "established_at",
).
Vals(
goqu.Vals{
code,
pid,
masterMeter,
slaveMeter,
timeNow,
},
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, relationSql, relationArgs...)
if err != nil {
mr.log.Error("绑定表计关系失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 解除两个表计之间的关联
func (mr _MeterRepository) UnbindMeter(tx pgx.Tx, ctx context.Context, pid, masterMeter, slaveMeter string) (bool, error) {
mr.log.Info("解除两个表计之间的关联", zap.String("master meter code", masterMeter), zap.String("slave meter code", slaveMeter))
relationSql, relationArgs, _ := mr.ds.
Update(goqu.T("meter_relations")).
Set(
goqu.Record{
"revoked_at": types.Now(),
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("master_meter_id").Eq(masterMeter),
goqu.I("slave_meter_id").Eq(slaveMeter),
goqu.I("revoked_at").IsNull(),
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, relationSql, relationArgs...)
if err != nil {
mr.log.Error("解除表计关系失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 列出指定公摊表计的所有关联表计关系
func (mr _MeterRepository) ListPooledMeterRelations(pid, code string) ([]*model.MeterRelation, error) {
mr.log.Info("列出指定公摊表计的所有关联表计关系", zap.String("park id", pid), zap.String("meter code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var relations []*model.MeterRelation
relationsSql, relationsArgs, _ := mr.ds.
From(goqu.T("meter_relations").As("r")).
Select("r.*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.master_meter_id").Eq(code),
goqu.I("r.revoked_at").IsNull(),
).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil {
mr.log.Error("查询表计关系失败", zap.Error(err))
return make([]*model.MeterRelation, 0), err
}
return relations, nil
}
// 列出指定公摊表计列表所包含的全部关联表计关系
func (mr _MeterRepository) ListPooledMeterRelationsByCodes(pid string, codes []string) ([]*model.MeterRelation, error) {
mr.log.Info("列出指定公摊表计列表所包含的全部关联表计关系", zap.String("park id", pid), zap.Strings("meter codes", codes))
ctx, cancel := global.TimeoutContext()
defer cancel()
var relations []*model.MeterRelation
relationsSql, relationsArgs, _ := mr.ds.
From(goqu.T("meter_relations").As("r")).
Select("r.*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.master_meter_id").In(codes),
goqu.I("r.revoked_at").IsNull(),
).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil {
mr.log.Error("查询表计关系失败", zap.Error(err))
return make([]*model.MeterRelation, 0), err
}
return relations, nil
}
// 列出指定商户表计、园区表计与公摊表计之间的关联关系
func (mr _MeterRepository) ListMeterRelations(pid, code string) ([]*model.MeterRelation, error) {
mr.log.Info("列出指定商户表计、园区表计与公摊表计之间的关联关系", zap.String("park id", pid), zap.String("meter code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var relations []*model.MeterRelation
relationsSql, relationsArgs, _ := mr.ds.
From(goqu.T("meter_relations")).
Select("*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.slave_meter_id").Eq(code),
goqu.I("r.revoked_at").IsNull(),
).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &relations, relationsSql, relationsArgs...); err != nil {
mr.log.Error("查询表计关系失败", zap.Error(err))
return make([]*model.MeterRelation, 0), err
}
return relations, nil
}
// 列出指定园区中的所有公摊表计
func (mr _MeterRepository) ListPoolingMeters(pid string, page uint, keyword *string) ([]*model.MeterDetail, int64, error) {
mr.log.Info("列出指定园区中的所有公摊表计", zap.String("park id", pid), zap.Uint("page", page), zap.String("keyword", tools.DefaultTo(keyword, "")))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.enabled").IsTrue(),
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_POOLING),
)
countQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
Select(goqu.COUNT("*")).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.enabled").IsTrue(),
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_POOLING),
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
countQuery = countQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
meterQuery = meterQuery.Order(goqu.I("m.code").Asc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
meters []*model.MeterDetail
total int64
)
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询公摊表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
mr.log.Error("查询公摊表计数量失败", zap.Error(err))
return make([]*model.MeterDetail, 0), 0, err
}
return meters, total, nil
}
// 列出目前尚未绑定到公摊表计的商户表计
func (mr _MeterRepository) ListUnboundMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) {
mr.log.Info("列出目前尚未绑定到公摊表计的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0)))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_TENEMENT),
goqu.I("m.enabled").IsTrue(),
)
if pid != nil && len(*pid) > 0 {
meterQuery = meterQuery.Where(
goqu.I("m.park_id").Eq(*pid),
)
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
}
slaveMeterQuery := mr.ds.
From("meter_relations").
Select("id")
if pid != nil && len(*pid) > 0 {
slaveMeterQuery = slaveMeterQuery.Where(
goqu.I("park_id").Eq(*pid),
)
} else {
slaveMeterQuery = slaveMeterQuery.Where(
goqu.I("park_id").In(
mr.ds.
From("park").
Select("id").
Where(goqu.I("user_id").Eq(uid)),
))
}
slaveMeterQuery = slaveMeterQuery.Where(
goqu.I("revoked_at").IsNull(),
)
meterQuery = meterQuery.Where(
goqu.I("m.code").NotIn(slaveMeterQuery),
).
Order(goqu.I("m.attached_at").Asc())
if limit != nil && *limit > 0 {
meterQuery = meterQuery.Limit(*limit)
}
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
var meters []*model.MeterDetail
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询商户表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 列出目前未绑定到商户的商户表计
func (mr _MeterRepository) ListUnboundTenementMeters(uid string, pid *string, keyword *string, limit *uint) ([]*model.MeterDetail, error) {
mr.log.Info("列出目前未绑定到商户的商户表计", zap.Stringp("park id", pid), zap.String("user id", uid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("limit", tools.DefaultTo(limit, 0)))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterQuery := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("m.building").Eq(goqu.I("b.id")))).
Select(
"m.*", goqu.I("b.name").As("building_name"),
).
Where(
goqu.I("m.meter_type").Eq(model.METER_INSTALLATION_TENEMENT),
goqu.I("m.enabled").IsTrue(),
)
if pid != nil && len(*pid) > 0 {
meterQuery = meterQuery.Where(
goqu.I("m.park_id").Eq(*pid),
)
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
meterQuery = meterQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
}
subMeterQuery := mr.ds.
From("tenement_meter").
Select("meter_id")
if pid != nil && len(*pid) > 0 {
subMeterQuery = subMeterQuery.Where(
goqu.I("park_id").Eq(*pid),
)
} else {
subMeterQuery = subMeterQuery.Where(
goqu.I("park_id").In(
mr.ds.
From("park").
Select("id").
Where(goqu.I("user_id").Eq(uid)),
))
}
subMeterQuery = subMeterQuery.Where(
goqu.I("disassociated_at").IsNull(),
)
meterQuery = meterQuery.Where(
goqu.I("m.code").NotIn(subMeterQuery),
).
Order(goqu.I("m.attached_at").Asc())
if limit != nil && *limit > 0 {
meterQuery = meterQuery.Limit(*limit)
}
meterSql, meterArgs, _ := meterQuery.Prepared(true).ToSQL()
var meters []*model.MeterDetail
if err := pgxscan.Select(ctx, global.DB, &meters, meterSql, meterArgs...); err != nil {
mr.log.Error("查询商户表计信息失败", zap.Error(err))
return make([]*model.MeterDetail, 0), err
}
return meters, nil
}
// 查询指定园区中的符合条件的抄表记录
func (mr _MeterRepository) ListMeterReadings(pid string, keyword *string, page uint, start, end *types.Date, buidling *string) ([]*model.MeterReading, int64, error) {
mr.log.Info("查询指定园区中的符合条件的抄表记录", zap.String("park id", pid), zap.String("keyword", tools.DefaultTo(keyword, "")), zap.Uint("page", page), logger.DateFieldp("start", start), logger.DateFieldp("end", end), zap.String("building", tools.DefaultTo(buidling, "")))
ctx, cancel := global.TimeoutContext()
defer cancel()
readingQuery := mr.ds.
From(goqu.T("meter_reading").As("r")).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("r.meter_id").Eq(goqu.I("m.code")))).
Select("r.*").
Where(
goqu.I("r.park_id").Eq(pid),
)
countQuery := mr.ds.
From(goqu.T("meter_reading").As("r")).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("r.meter_id").Eq(goqu.I("m.code")))).
Select(goqu.COUNT("*")).
Where(
goqu.I("r.park_id").Eq(pid),
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
readingQuery = readingQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
countQuery = countQuery.Where(
goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
),
)
}
if start != nil {
readingQuery = readingQuery.Where(
goqu.I("r.read_at").Gte(start.ToBeginningOfDate()),
)
countQuery = countQuery.Where(
goqu.I("r.read_at").Gte(start.ToBeginningOfDate()),
)
}
if end != nil {
readingQuery = readingQuery.Where(
goqu.I("r.read_at").Lte(end.ToEndingOfDate()),
)
countQuery = countQuery.Where(
goqu.I("r.read_at").Lte(end.ToEndingOfDate()),
)
}
if buidling != nil && len(*buidling) > 0 {
readingQuery = readingQuery.Where(
goqu.I("m.building").Eq(*buidling),
)
countQuery = countQuery.Where(
goqu.I("m.building").Eq(*buidling),
)
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
readingQuery = readingQuery.Order(goqu.I("r.read_at").Desc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
readingSql, readingArgs, _ := readingQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
readings []*model.MeterReading
total int64
)
if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil {
mr.log.Error("查询抄表记录失败", zap.Error(err))
return make([]*model.MeterReading, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
mr.log.Error("查询抄表记录数量失败", zap.Error(err))
return make([]*model.MeterReading, 0), 0, err
}
return readings, total, nil
}
// 修改指定表计的指定抄表记录
func (mr _MeterRepository) UpdateMeterReading(pid, mid string, readAt types.DateTime, reading *vo.MeterReadingForm) (bool, error) {
mr.log.Info("修改指定表计的指定抄表记录", zap.String("park id", pid), zap.String("meter id", mid), logger.DateTimeField("read at", readAt), zap.Any("reading", reading))
ctx, cancel := global.TimeoutContext()
defer cancel()
updateSql, updateArgs, _ := mr.ds.
Update(goqu.T("meter_reading")).
Set(
goqu.Record{
"overall": reading.Overall,
"critical": reading.Critical,
"peak": reading.Peak,
"flat": reading.Flat,
"valley": reading.Valley,
},
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("meter_id").Eq(mid),
goqu.I("read_at").Eq(readAt),
).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
mr.log.Error("更新抄表记录失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 列出指定园区中指定时间区域内的所有表计抄表记录
func (mr _MeterRepository) ListMeterReadingsByTimeRange(pid string, start, end types.Date) ([]*model.MeterReading, error) {
mr.log.Info("列出指定园区中指定时间区域内的所有表计抄表记录", zap.String("park id", pid), zap.Time("start", start.Time), zap.Time("end", end.Time))
ctx, cancel := global.TimeoutContext()
defer cancel()
var readings []*model.MeterReading
readingSql, readingArgs, _ := mr.ds.
From(goqu.T("meter_reading").As("r")).
Select("*").
Where(
goqu.I("r.park_id").Eq(pid),
goqu.I("r.read_at").Gte(start.ToBeginningOfDate()),
goqu.I("r.read_at").Lte(end.ToEndingOfDate()),
).
Order(goqu.I("r.read_at").Desc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil {
mr.log.Error("查询抄表记录失败", zap.Error(err))
return make([]*model.MeterReading, 0), err
}
return readings, nil
}
// 列出指定园区中在指定日期之前的最后一次抄表记录
func (mr _MeterRepository) ListLastMeterReading(pid string, date types.Date) ([]*model.MeterReading, error) {
mr.log.Info("列出指定园区中在指定日期之前的最后一次抄表记录", zap.String("park id", pid), zap.Time("date", date.Time))
ctx, cancel := global.TimeoutContext()
defer cancel()
var readings []*model.MeterReading
readingSql, readingArgs, _ := mr.ds.
From(goqu.T("meter_reading")).
Select(
goqu.MAX("read_at").As("read_at"),
"park_id", "meter_id", "overall", "critical", "peak", "flat", "valley",
).
Where(
goqu.I("park_id").Eq(pid),
goqu.I("read_at").Lt(date.ToEndingOfDate()),
).
GroupBy("park_id", "meter_id", "overall", "critical", "peak", "flat", "valley").
Order(goqu.I("read_at").Desc()).
Limit(1).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &readings, readingSql, readingArgs...); err != nil {
mr.log.Error("查询抄表记录失败", zap.Error(err))
return make([]*model.MeterReading, 0), err
}
return readings, nil
}
// 列出指定园区中的表计与商户的关联详细记录用于写入Excel模板文件
func (mr _MeterRepository) ListMeterDocForTemplate(pid string) ([]*model.SimpleMeterDocument, error) {
mr.log.Info("列出指定园区中的表计与商户的关联详细记录", zap.String("park id", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var docs []*model.SimpleMeterDocument
docSql, docArgs, _ := mr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(
goqu.T("tenement_meter").As("tm"),
goqu.On(
goqu.I("m.code").Eq(goqu.I("tm.meter_id")),
goqu.I("m.park_id").Eq(goqu.I("tm.park_id")),
),
).
LeftJoin(
goqu.T("tenement").As("t"),
goqu.On(
goqu.I("tm.tenement_id").Eq(goqu.I("t.id")),
goqu.I("tm.park_id").Eq(goqu.I("t.park_id")),
),
).
Select(
"m.code", "m.address", "m.ratio", "m.seq", goqu.I("t.full_name").As("tenement_name"),
).
Where(
goqu.I("m.park_id").Eq(pid),
goqu.I("m.enabled").IsTrue(),
goqu.I("tm.disassociated_at").IsNull(),
).
Order(goqu.I("m.seq").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &docs, docSql, docArgs...); err != nil {
mr.log.Error("查询表计与商户关联信息失败", zap.Error(err))
return make([]*model.SimpleMeterDocument, 0), err
}
return docs, nil
}

419
repository/park.go Normal file
View File

@@ -0,0 +1,419 @@
package repository
import (
"context"
"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"
"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 _ParkRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var ParkRepository = _ParkRepository{
log: logger.Named("Repository", "Park"),
ds: goqu.Dialect("postgres"),
}
// 列出指定用户下的所有园区
func (pr _ParkRepository) ListAllParks(uid string) ([]*model.Park, error) {
pr.log.Info("列出指定用户下的所有园区", zap.String("uid", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var parks = make([]*model.Park, 0)
parkQuerySql, parkParams, _ := pr.ds.
From("park").
Select(
"id", "user_id", "name", "area", "tenement_quantity", "capacity", "category",
"meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at",
"deleted_at",
).
Where(
goqu.I("user_id").Eq(uid),
goqu.I("deleted_at").IsNull(),
).
Order(goqu.I("created_at").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &parks, parkQuerySql, parkParams...); err != nil {
pr.log.Error("列出指定用户下的所有园区失败!", zap.Error(err))
return make([]*model.Park, 0), err
}
return parks, nil
}
// 检查并确定指定园区的归属情况
func (pr _ParkRepository) IsParkBelongs(pid, uid string) (bool, error) {
pr.log.Info("检查并确定指定园区的归属情况", zap.String("pid", pid), zap.String("uid", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var count int64
parkQuerySql, parkParams, _ := pr.ds.
From("park").
Select(goqu.COUNT("*")).
Where(
goqu.I("id").Eq(pid),
goqu.I("user_id").Eq(uid),
).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &count, parkQuerySql, parkParams...); err != nil {
pr.log.Error("检查并确定指定园区的归属情况失败!", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 创建一个属于指定用户的新园区。该创建功能不会对园区的名称进行检查。
func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, error) {
pr.log.Info("创建一个属于指定用户的新园区", zap.String("ownerId", ownerId))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("P", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := pr.ds.
Insert("park").
Cols(
"id", "user_id", "name", "abbr", "area", "tenement_quantity", "capacity", "category",
"meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
code,
ownerId, park.Name, tools.PinyinAbbr(park.Name),
park.Area, park.TenementQuantity, park.Capacity, park.Category,
park.MeterType, park.Region, park.Address, park.Contact, park.Phone, park.Enabled, park.PricePolicy, park.TaxRate,
park.BasicPooled, park.AdjustPooled, park.LossPooled, park.PublicPooled, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("创建一个属于指定用户的新园区失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 获取指定园区的详细信息
func (pr _ParkRepository) RetrieveParkDetail(pid string) (*model.Park, error) {
pr.log.Info("获取指定园区的详细信息", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var park model.Park
parkSql, parkArgs, _ := pr.ds.
From("park").
Select(
"id", "user_id", "name", "area", "tenement_quantity", "capacity", "category",
"meter_04kv_type", "region", "address", "contact", "phone", "enabled", "price_policy", "tax_rate",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at",
"deleted_at",
).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &park, parkSql, parkArgs...); err != nil {
pr.log.Error("获取指定园区的详细信息失败!", zap.Error(err))
return nil, err
}
return &park, nil
}
// 获取园区对应的用户ID
func (pr _ParkRepository) RetrieveParkBelongs(pid string) (string, error) {
pr.log.Info("获取园区对应的用户ID", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var uid string
parkSql, parkArgs, _ := pr.ds.
From("park").
Select(goqu.I("user_id")).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &uid, parkSql, parkArgs...); err != nil {
pr.log.Error("获取园区对应的用户ID失败", zap.Error(err))
return "", err
}
return uid, nil
}
// 更新指定园区的信息
func (pr _ParkRepository) UpdatePark(pid string, park *model.Park) (bool, error) {
pr.log.Info("更新指定园区的信息", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"name": park.Name,
"abbr": tools.PinyinAbbr(park.Name),
"area": park.Area,
"tenement_quantity": park.TenementQuantity,
"capacity": park.Capacity,
"category": park.Category,
"meter_04kv_type": park.MeterType,
"region": park.Region,
"address": park.Address,
"contact": park.Contact,
"phone": park.Phone,
"price_policy": park.PricePolicy,
"tax_rate": park.TaxRate,
"basic_pooled": park.BasicPooled,
"adjust_pooled": park.AdjustPooled,
"loss_pooled": park.LossPooled,
"public_pooled": park.PublicPooled,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("更新指定园区的信息失败!", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 设定园区的可用状态
func (pr _ParkRepository) EnablingPark(pid string, enabled bool) (bool, error) {
pr.log.Info("设定园区的可用状态", zap.String("pid", pid), zap.Bool("enabled", enabled))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"enabled": enabled,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("设定园区的可用状态失败!", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 删除指定园区(软删除)
func (pr _ParkRepository) DeletePark(pid string) (bool, error) {
pr.log.Info("删除指定园区(软删除)", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park").
Set(goqu.Record{
"deleted_at": timeNow,
"last_modified_at": timeNow,
}).
Where(goqu.I("id").Eq(pid)).
Prepared(true).ToSQL()
ok, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("删除指定园区(软删除)失败!", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}
// 检索给定的园区详细信息列表
func (pr _ParkRepository) RetrieveParks(pids []string) ([]*model.Park, error) {
pr.log.Info("检索给定的园区详细信息列表", zap.Strings("pids", pids))
if len(pids) == 0 {
pr.log.Info("给定要检索的园区ID列表为空执行快速返回。")
return make([]*model.Park, 0), nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var parks []*model.Park
parkSql, parkArgs, _ := pr.ds.
From("park").
Where(goqu.I("id").In(pids)).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &parks, parkSql, parkArgs...); err != nil {
pr.log.Error("检索给定的园区详细信息列表失败!", zap.Error(err))
return nil, err
}
return parks, nil
}
// 获取指定园区中的建筑
func (pr _ParkRepository) RetrieveParkBuildings(pid string) ([]*model.ParkBuilding, error) {
pr.log.Info("获取指定园区中的建筑", zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var buildings []*model.ParkBuilding
buildingSql, buildingArgs, _ := pr.ds.
From("park_building").
Where(
goqu.I("park_id").Eq(pid),
goqu.I("deleted_at").IsNull(),
).
Order(goqu.I("created_at").Asc()).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &buildings, buildingSql, buildingArgs...); err != nil {
pr.log.Error("获取指定园区中的建筑失败!", zap.Error(err))
return nil, err
}
return buildings, nil
}
// 在指定园区中创建一个新建筑
func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (bool, error) {
pr.log.Info("在指定园区中创建一个新建筑", zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("B", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := pr.ds.
Insert("park_building").
Cols(
"id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
code,
pid, name, floor, true, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("在指定园区中创建一个新建筑失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 在指定园区中创建一个建筑,这个方法会使用事务
func (pr _ParkRepository) CreateParkBuildingWithTransaction(tx pgx.Tx, ctx context.Context, pid, name string, floor *string) (bool, error) {
timeNow := types.Now()
serial.StringSerialRequestChan <- 1
code := serial.Prefix("B", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := pr.ds.
Insert("park_building").
Cols(
"id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at",
).
Vals(goqu.Vals{
code,
pid, name, floor, true, timeNow, timeNow,
}).
Prepared(true).ToSQL()
rs, err := tx.Exec(ctx, createSql, createArgs...)
if err != nil {
pr.log.Error("在指定园区中创建一个新建筑失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 修改指定园区中指定建筑的信息
func (pr _ParkRepository) ModifyParkBuilding(id, pid, name string, floor *string) (bool, error) {
pr.log.Info("修改指定园区中指定建筑的信息", zap.String("id", id), zap.String("pid", pid), zap.String("name", name), zap.Stringp("floor", floor))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"name": name,
"floors": floor,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("修改指定园区中指定建筑的信息失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 修改指定建筑的可以状态
func (pr _ParkRepository) EnablingParkBuilding(id, pid string, enabled bool) (bool, error) {
pr.log.Info("修改指定建筑的可以状态", zap.String("id", id), zap.String("pid", pid), zap.Bool("enabled", enabled))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"enabled": enabled,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("修改指定建筑的可以状态失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}
// 删除指定建筑(软删除)
func (pr _ParkRepository) DeleteParkBuilding(id, pid string) (bool, error) {
pr.log.Info("删除指定建筑(软删除)", zap.String("id", id), zap.String("pid", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
timeNow := types.Now()
updateSql, updateArgs, _ := pr.ds.
Update("park_building").
Set(goqu.Record{
"deleted_at": timeNow,
"last_modified_at": timeNow,
}).
Where(
goqu.I("id").Eq(id),
goqu.I("park_id").Eq(pid),
).
Prepared(true).ToSQL()
rs, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
pr.log.Error("删除指定建筑(软删除)失败!", zap.Error(err))
return false, err
}
return rs.RowsAffected() > 0, nil
}

79
repository/region.go Normal file
View File

@@ -0,0 +1,79 @@
package repository
import (
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/georgysavva/scany/v2/pgxscan"
"go.uber.org/zap"
)
type _RegionRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var RegionRepository = _RegionRepository{
log: logger.Named("Repository", "Region"),
ds: goqu.Dialect("postgres"),
}
// 获取指定行政区划下所有直接子级行政区划
func (r *_RegionRepository) FindSubRegions(parent string) ([]model.Region, error) {
r.log.Info("获取指定行政区划下所有直接子级行政区划", zap.String("parent", parent))
ctx, cancel := global.TimeoutContext()
defer cancel()
var regions []model.Region
regionQuerySql, regionParams, _ := r.ds.
From("region").
Where(goqu.Ex{"parent": parent}).
Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &regions, regionQuerySql, regionParams...); err != nil {
r.log.Error("获取指定行政区划下所有直接子级行政区划失败!", zap.Error(err))
return nil, err
}
return regions, nil
}
// 获取一个指定编号的行政区划详细信息
func (r *_RegionRepository) FindRegion(code string) (*model.Region, error) {
r.log.Info("获取指定行政区划信息", zap.String("code", code))
ctx, cancel := global.TimeoutContext()
defer cancel()
var region model.Region
regionQuerySql, regionParams, _ := r.ds.
From("region").
Where(goqu.Ex{"code": code}).
Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &region, regionQuerySql, regionParams...); err != nil {
r.log.Error("获取指定行政区划信息失败!", zap.Error(err))
return nil, err
}
return &region, nil
}
// 获取指定行政区划的所有直接和非直接父级
func (r *_RegionRepository) FindParentRegions(code string) ([]*model.Region, error) {
r.log.Info("获取指定行政区划的所有直接和非直接父级", zap.String("code", code))
var (
regionsScanTask = []string{code}
regions = make([]*model.Region, 0)
)
for len(regionsScanTask) > 0 {
region, err := r.FindRegion(regionsScanTask[0])
regionsScanTask = append([]string{}, regionsScanTask[1:]...)
if err == nil && region != nil {
regions = append(regions, region)
if region.Parent != "0" {
regionsScanTask = append(regionsScanTask, region.Parent)
}
}
}
return regions, nil
}

846
repository/report.go Normal file
View File

@@ -0,0 +1,846 @@
package repository
import (
"electricity_bill_calc/config"
"electricity_bill_calc/exceptions"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"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/samber/lo"
"go.uber.org/zap"
)
type _ReportRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var ReportRepository = _ReportRepository{
log: logger.Named("Repository", "Report"),
ds: goqu.Dialect("postgres"),
}
// 检查指定核算报表的归属情况
func (rr _ReportRepository) IsBelongsTo(rid, uid string) (bool, error) {
rr.log.Info("检查指定核算报表的归属", zap.String("User", uid), zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Select(goqu.COUNT("r.*")).
Where(
goqu.I("r.id").Eq(rid),
goqu.I("p.user_id").Eq(uid),
).
Prepared(true).ToSQL()
var count int64
if err := pgxscan.Get(ctx, global.DB, &count, querySql, queryParams...); err != nil {
rr.log.Error("检查指定核算报表的归属出现错误", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 获取指定用户下所有园区的尚未发布的简易核算报表索引内容
func (rr _ReportRepository) ListDraftReportIndicies(uid string) ([]*model.ReportIndex, error) {
rr.log.Info("获取指定用户下的所有尚未发布的报表索引", zap.String("User", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message")).
Where(
goqu.I("p.user_id").Eq(uid),
goqu.I("r.published").IsFalse(),
).
Order(goqu.I("r.created_at").Desc()).
Prepared(true).ToSQL()
var indicies []*model.ReportIndex = make([]*model.ReportIndex, 0)
if err := pgxscan.Select(ctx, global.DB, &indicies, querySql, queryParams...); err != nil {
rr.log.Error("获取指定用户下的所有尚未发布的报表索引出现错误", zap.Error(err))
return indicies, err
}
return indicies, nil
}
// 获取指定报表的详细索引内容
func (rr _ReportRepository) GetReportIndex(rid string) (*model.ReportIndex, error) {
rr.log.Info("获取指定报表的详细索引", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message")).
Where(goqu.I("r.id").Eq(rid)).
Prepared(true).ToSQL()
var index model.ReportIndex
if err := pgxscan.Get(ctx, global.DB, &index, querySql, queryParams...); err != nil {
rr.log.Error("获取指定报表的详细索引出现错误", zap.Error(err))
return nil, err
}
return &index, nil
}
// 为指园区创建一个新的核算报表
func (rr _ReportRepository) CreateReport(form *vo.ReportCreationForm) (bool, error) {
rr.log.Info("为指定园区创建一个新的核算报表", zap.String("Park", form.Park))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
rr.log.Error("未能开始一个数据库事务", zap.Error(err))
return false, err
}
park, err := ParkRepository.RetrieveParkDetail(form.Park)
if err != nil || park == nil {
rr.log.Error("未能获取指定园区的详细信息", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewNotFoundErrorFromError("未能获取指定园区的详细信息", err)
}
createTime := types.Now()
periodRange := types.NewDateRange(&form.PeriodBegin, &form.PeriodEnd)
serial.StringSerialRequestChan <- 1
reportId := serial.Prefix("R", <-serial.StringSerialResponseChan)
createSql, createArgs, _ := rr.ds.
Insert(goqu.T("report")).
Cols(
"id", "park_id", "period", "category", "meter_o4kv_type", "price_policy",
"basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at",
"last_modified_at",
).
Vals(goqu.Vals{
reportId, park.Id, periodRange, park.Category, park.MeterType, park.PricePolicy,
park.BasicPooled, park.AdjustPooled, park.LossPooled, park.PublicPooled, createTime,
createTime,
}).
Prepared(true).ToSQL()
summarySql, summaryArgs, _ := rr.ds.
Insert(goqu.T("report_summary")).
Cols(
"report_id", "overall", "critical", "peak", "flat", "valley", "basic_fee",
"adjust_fee",
).
Vals(goqu.Vals{
reportId,
model.ConsumptionUnit{
Amount: form.Overall,
Fee: form.OverallFee,
},
model.ConsumptionUnit{
Amount: form.Critical,
Fee: form.CriticalFee,
},
model.ConsumptionUnit{
Amount: form.Peak,
Fee: form.PeakFee,
},
model.ConsumptionUnit{
Amount: form.Flat,
Fee: form.FlatFee,
},
model.ConsumptionUnit{
Amount: form.Valley,
Fee: form.ValleyFee,
},
form.BasicFee,
form.AdjustFee,
}).
Prepared(true).ToSQL()
taskSql, taskArgs, _ := rr.ds.
Insert(goqu.T("report_task")).
Cols("id", "status", "last_modified_at").
Vals(goqu.Vals{reportId, model.REPORT_CALCULATE_TASK_STATUS_PENDING, createTime}).
Prepared(true).ToSQL()
resIndex, err := tx.Exec(ctx, createSql, createArgs...)
if err != nil {
rr.log.Error("创建核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
if resIndex.RowsAffected() == 0 {
rr.log.Error("保存核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewUnsuccessCreateError("创建核算报表索引时出现错误")
}
resSummary, err := tx.Exec(ctx, summarySql, summaryArgs...)
if err != nil {
rr.log.Error("创建核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
if resSummary.RowsAffected() == 0 {
rr.log.Error("保存核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewUnsuccessCreateError("创建核算报表汇总时出现错误")
}
resTask, err := tx.Exec(ctx, taskSql, taskArgs...)
if err != nil {
rr.log.Error("创建核算报表任务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
if resTask.RowsAffected() == 0 {
rr.log.Error("保存核算报表任务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewUnsuccessCreateError("创建核算报表任务时出现错误")
}
err = tx.Commit(ctx)
if err != nil {
rr.log.Error("提交核算报表创建事务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
return resIndex.RowsAffected() > 0 && resSummary.RowsAffected() > 0 && resTask.RowsAffected() > 0, nil
}
// 更新报表的基本信息
func (rr _ReportRepository) UpdateReportSummary(rid string, form *vo.ReportModifyForm) (bool, error) {
rr.log.Info("更新指定报表的基本信息", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
rr.log.Error("未能开始一个数据库事务", zap.Error(err))
return false, err
}
updateTime := types.Now()
newPeriod := types.NewDateRange(&form.PeriodBegin, &form.PeriodEnd)
udpateIndexSql, updateIndexArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"period": newPeriod,
"last_modified_at": updateTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
updateSummarySql, updateSummaryArgs, _ := rr.ds.
Update(goqu.T("report_summary")).
Set(goqu.Record{
"overall": model.ConsumptionUnit{Amount: form.Overall, Fee: form.OverallFee},
"critical": model.ConsumptionUnit{Amount: form.Critical, Fee: form.CriticalFee},
"peak": model.ConsumptionUnit{Amount: form.Peak, Fee: form.PeakFee},
"flat": model.ConsumptionUnit{Amount: form.Flat, Fee: form.FlatFee},
"valley": model.ConsumptionUnit{Amount: form.Valley, Fee: form.ValleyFee},
"basic_fee": form.BasicFee,
"adjust_fee": form.AdjustFee,
}).
Where(goqu.I("report_id").Eq(rid)).
Prepared(true).ToSQL()
resIndex, err := tx.Exec(ctx, udpateIndexSql, updateIndexArgs...)
if err != nil {
rr.log.Error("更新核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
if resIndex.RowsAffected() == 0 {
rr.log.Error("保存核算报表索引时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewUnsuccessUpdateError("更新核算报表索引时出现错误")
}
resSummary, err := tx.Exec(ctx, updateSummarySql, updateSummaryArgs...)
if err != nil {
rr.log.Error("更新核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
if resSummary.RowsAffected() == 0 {
rr.log.Error("保存核算报表汇总时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, exceptions.NewUnsuccessUpdateError("更新核算报表汇总时出现错误")
}
err = tx.Commit(ctx)
if err != nil {
rr.log.Error("提交核算报表更新事务时出现错误", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
return resIndex.RowsAffected() > 0 && resSummary.RowsAffected() > 0, nil
}
// 获取指定报表的总览信息
func (rr _ReportRepository) RetrieveReportSummary(rid string) (*model.ReportSummary, error) {
rr.log.Info("获取指定报表的总览信息", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report_summary")).
Select("*").
Where(goqu.I("report_id").Eq(rid)).
Prepared(true).ToSQL()
var summary model.ReportSummary
if err := pgxscan.Get(ctx, global.DB, &summary, querySql, queryParams...); err != nil {
rr.log.Error("获取指定报表的总览信息时出现错误", zap.Error(err))
return nil, err
}
return &summary, nil
}
// 获取指定用户的尚未发布的核算报表的计算状态
func (rr _ReportRepository) GetReportTaskStatus(uid string) ([]*model.ReportTask, error) {
rr.log.Info("获取指定用户的尚未发布的核算报表的计算状态", zap.String("User", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report_task").As("t")).
Join(goqu.T("report").As("r"), goqu.On(goqu.I("r.id").Eq(goqu.I("t.id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Select(
goqu.I("t.*"),
).
Where(
goqu.I("p.user_id").Eq(uid),
goqu.I("r.published").IsFalse(),
).
Prepared(true).ToSQL()
var tasks []*model.ReportTask = make([]*model.ReportTask, 0)
if err := pgxscan.Select(ctx, global.DB, &tasks, querySql, queryParams...); err != nil {
rr.log.Error("获取指定用户的尚未发布的核算报表的计算状态时出现错误", zap.Error(err))
return tasks, err
}
return tasks, nil
}
// 检索指定核算报表中园区公共表计的核算记录
func (rr _ReportRepository) ListPublicMetersInReport(rid string, page uint, keyword *string) ([]*model.ReportDetailedPublicConsumption, int64, error) {
rr.log.Info("检索指定核算报表中园区公共表计的核算记录", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report_public_consumption").As("r")).
Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(
goqu.I("r.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"),
).
Where(goqu.I("r.report_id").Eq(rid))
countQuery := rr.ds.
From(goqu.T("report_public_consumption").As("r")).
Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.park_meter_id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(goqu.COUNT(goqu.I("r.*"))).
Where(goqu.I("r.report_id").Eq(rid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("m.code").Asc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
consumptions []*model.ReportDetailedPublicConsumption = make([]*model.ReportDetailedPublicConsumption, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &consumptions, querySql, queryArgs...); err != nil {
rr.log.Error("检索指定核算报表中园区公共表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("检索指定核算报表中园区公共表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
return consumptions, count, nil
}
// 检索指定核算报表中公摊表计的核算记录
func (rr _ReportRepository) ListPooledMetersInReport(rid string, page uint, keyword *string) ([]*model.ReportDetailedPooledConsumption, int64, error) {
rr.log.Info("检索指定核算报表中公摊表计的核算记录", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report_pooled_consumption").As("r")).
Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.pooled_meter_id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(
goqu.I("r.*"), goqu.I("m.*"), goqu.I("b.name").As("building_name"), goqu.I("p.public_pooled"),
).
Where(goqu.I("r.report_id").Eq(rid))
countQuery := rr.ds.
From(goqu.T("report_pooled_consumption").As("r")).
Join(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("r.pooled_meter_id")))).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("m.park_id")))).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(goqu.COUNT(goqu.I("r.*"))).
Where(goqu.I("r.report_id").Eq(rid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("m.code").Asc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
consumptions []*model.ReportDetailedPooledConsumption = make([]*model.ReportDetailedPooledConsumption, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &consumptions, querySql, queryArgs...); err != nil {
rr.log.Error("检索指定核算报表中公摊表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("检索指定核算报表中公摊表计的核算记录时出现错误", zap.Error(err))
return consumptions, 0, err
}
return consumptions, count, nil
}
// 列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计及分摊详细
func (rr _ReportRepository) ListPooledMeterDetailInReport(rid, mid string) ([]*model.ReportDetailNestedMeterConsumption, error) {
rr.log.Info("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计及分摊详细", zap.String("Report", rid), zap.String("Meter", mid))
ctx, cancel := global.TimeoutContext()
defer cancel()
meterSql, meterArgs, _ := rr.ds.
From(goqu.T("report_pooled_consumption")).
Select("*").
Where(goqu.I("report_id").Eq(rid), goqu.I("pooled_meter_id").Eq(mid)).
Prepared(true).ToSQL()
var meter model.ReportPooledConsumption
if err := pgxscan.Get(ctx, global.DB, &meter, meterSql, meterArgs...); err != nil {
rr.log.Error("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计编号时出现错误", zap.Error(err))
return make([]*model.ReportDetailNestedMeterConsumption, 0), err
}
meterCodes := lo.Map(meter.Diluted, func(m model.NestedMeter, _ int) string {
return m.MeterId
})
meterSql, meterArgs, _ = rr.ds.
From(goqu.T("meter_04kv").As("m")).
LeftJoin(goqu.T("park_building").As("b"), goqu.On(goqu.I("b.id").Eq(goqu.I("m.building")))).
Select(
goqu.I("m.*"), goqu.I("b.name").As("building_name"),
).
Where(goqu.I("m.code").In(meterCodes)).
Prepared(true).ToSQL()
var meterDetails []*model.MeterDetail = make([]*model.MeterDetail, 0)
if err := pgxscan.Select(ctx, global.DB, &meterDetails, meterSql, meterArgs...); err != nil {
rr.log.Error("列出指定核算报表中指定公共表计下参与公共表计费用分摊的表计详细时出现错误", zap.Error(err))
return make([]*model.ReportDetailNestedMeterConsumption, 0), err
}
assembled := lo.Map(meter.Diluted, func(m model.NestedMeter, _ int) *model.ReportDetailNestedMeterConsumption {
meterDetail, _ := lo.Find(meterDetails, func(elem *model.MeterDetail) bool {
return elem.Code == m.MeterId
})
return &model.ReportDetailNestedMeterConsumption{
Meter: *meterDetail,
Consumption: m,
}
})
return assembled, nil
}
// 列出指定核算报表下商户的简要计费信息
func (rr _ReportRepository) ListTenementInReport(rid string, page uint, keyword *string) ([]*model.ReportTenement, int64, error) {
rr.log.Info("查询指定核算报表下的商户简要计费信息", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report_tenement").As("rt")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("rt.tenement_id")))).
Select("rt.*").
Where(goqu.I("rt.report_id").Eq(rid))
countQuery := rr.ds.
From(goqu.T("report_tenement").As("rt")).
Join(goqu.T("tenement").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("rt.tenement_id")))).
Select(goqu.COUNT(goqu.I("rt.*"))).
Where(goqu.I("rt.report_id").Eq(rid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("t.full_name").ILike(pattern),
goqu.T("t.short_name").ILike(pattern),
goqu.I("t.abbr").ILike(pattern),
goqu.I("t.address").ILike(pattern),
goqu.I("t.contact_name").ILike(pattern),
goqu.I("t.contact_phone").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("t.full_name").ILike(pattern),
goqu.T("t.short_name").ILike(pattern),
goqu.I("t.abbr").ILike(pattern),
goqu.I("t.address").ILike(pattern),
goqu.I("t.contact_name").ILike(pattern),
goqu.I("t.contact_phone").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("t.moved_in_at").Asc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
tenements []*model.ReportTenement = make([]*model.ReportTenement, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &tenements, querySql, queryArgs...); err != nil {
rr.log.Error("查询指定核算报表下的商户简要计费信息时出现错误", zap.Error(err))
return tenements, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("查询指定核算报表下的商户简要计费信息总数量时出现错误", zap.Error(err))
return tenements, 0, err
}
return tenements, count, nil
}
// 获取指定核算报表中指定商户的详细核算信息
func (rr _ReportRepository) GetTenementDetailInReport(rid, tid string) (*model.ReportTenement, error) {
rr.log.Info("获取指定核算报表中指定商户的详细核算信息", zap.String("Report", rid), zap.String("Tenement", tid))
ctx, cancel := global.TimeoutContext()
defer cancel()
querySql, queryParams, _ := rr.ds.
From(goqu.T("report_tenement").As("rt")).
Select("rt.*").
Where(goqu.I("rt.report_id").Eq(rid), goqu.I("rt.tenement_id").Eq(tid)).
Prepared(true).ToSQL()
var tenement model.ReportTenement
if err := pgxscan.Get(ctx, global.DB, &tenement, querySql, queryParams...); err != nil {
rr.log.Error("获取指定核算报表中指定商户的详细核算信息时出现错误", zap.Error(err))
return nil, err
}
return &tenement, nil
}
// 更改指定核算报表的状态为已发布
func (rr _ReportRepository) PublishReport(rid string) (bool, error) {
rr.log.Info("更改指定核算报表的状态为已发布", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"published": true,
"published_at": currentTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("更改指定核算报表的状态为已发布时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 对指定核算报表进行综合检索
func (rr _ReportRepository) ComprehensiveReportSearch(uid, pid *string, page uint, keyword *string, start, end *types.Date) ([]*model.ReportIndex, int64, error) {
rr.log.Info("对指定核算报表进行综合检索", zap.Stringp("User", uid), zap.Stringp("Park", pid))
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message"))
countQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select(goqu.COUNT(goqu.I("r.*")))
if uid != nil && len(*uid) > 0 {
reportQuery = reportQuery.Where(goqu.I("ud.id").Eq(*uid))
countQuery = countQuery.Where(goqu.I("ud.id").Eq(*uid))
}
if pid != nil && len(*pid) > 0 {
reportQuery = reportQuery.Where(goqu.I("p.id").Eq(*pid))
countQuery = countQuery.Where(goqu.I("p.id").Eq(*pid))
}
queryDateRange := types.NewDateRange(start, end)
reportQuery = reportQuery.Where(goqu.L("r.period <@ ?", queryDateRange))
countQuery = countQuery.Where(goqu.L("r.period <@ ?", queryDateRange))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("r.created_at").Desc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
reports []*model.ReportIndex = make([]*model.ReportIndex, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &reports, querySql, queryArgs...); err != nil {
rr.log.Error("对指定核算报表进行综合检索时出现错误", zap.Error(err))
return reports, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("对指定核算报表进行综合检索总数量时出现错误", zap.Error(err))
return reports, 0, err
}
return reports, count, nil
}
// 判断指定报表是否是当前园区的最后一张报表
func (rr _ReportRepository) IsLastReport(rid string) (bool, error) {
rr.log.Info("判断指定报表是否是当前园区的最后一张报表", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
checkSql, checkArgs, _ := rr.ds.
From(goqu.T("report")).
Select(goqu.COUNT("*")).
Where(
goqu.I("r.id").Eq(rid),
goqu.Func("lower", goqu.I("r.period")).Gte(rr.ds.
From(goqu.T("report").As("ri")).
Select(goqu.Func("max", goqu.Func("upper", goqu.I("ri.period")))).
Where(
goqu.I("ri.park_id").Eq(goqu.I("r.park_id")),
goqu.I("ri.id").Neq(rid),
),
),
goqu.I("r.published").IsTrue(),
).
Prepared(true).ToSQL()
var count int64
if err := pgxscan.Get(ctx, global.DB, &count, checkSql, checkArgs...); err != nil {
rr.log.Error("判断指定报表是否是当前园区的最后一张报表时出现错误", zap.Error(err))
return false, err
}
return count > 0, nil
}
// 申请撤回指定的核算报表
func (rr _ReportRepository) ApplyWithdrawReport(rid string) (bool, error) {
rr.log.Info("申请撤回指定的核算报表", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"withdraw": model.REPORT_WITHDRAW_APPLYING,
"last_withdraw_applied_at": currentTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("申请撤回指定的核算报表时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 批准核算报表的撤回申请
func (rr _ReportRepository) ApproveWithdrawReport(rid string) (bool, error) {
rr.log.Info("批准核算报表的撤回申请", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"withdraw": model.REPORT_WITHDRAW_GRANTED,
"last_withdraw_audit_at": currentTime,
"published": false,
"published_at": nil,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("批准核算报表的撤回申请时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 拒绝核算报表的撤回申请
func (rr _ReportRepository) RejectWithdrawReport(rid string) (bool, error) {
rr.log.Info("拒绝核算报表的撤回申请", zap.String("Report", rid))
ctx, cancel := global.TimeoutContext()
defer cancel()
currentTime := types.Now()
updateSql, updateArgs, _ := rr.ds.
Update(goqu.T("report")).
Set(goqu.Record{
"withdraw": model.REPORT_WITHDRAW_DENIED,
"last_withdraw_audit_at": currentTime,
}).
Where(goqu.I("id").Eq(rid)).
Prepared(true).ToSQL()
res, err := global.DB.Exec(ctx, updateSql, updateArgs...)
if err != nil {
rr.log.Error("拒绝核算报表的撤回申请时出现错误", zap.Error(err))
return false, err
}
return res.RowsAffected() > 0, nil
}
// 列出当前正在等待审核的已经申请撤回的核算报表
func (rr _ReportRepository) ListWithdrawAppliedReports(page uint, keyword *string) ([]*model.ReportIndex, int64, error) {
rr.log.Info("列出当前正在等待审核的已经申请撤回的核算报表")
ctx, cancel := global.TimeoutContext()
defer cancel()
reportQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select("r.*", goqu.I("t.status"), goqu.I("t.message")).
Where(goqu.I("r.withdraw").Eq(model.REPORT_WITHDRAW_APPLYING))
countQuery := rr.ds.
From(goqu.T("report").As("r")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("r.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("p.user_id")))).
LeftJoin(goqu.T("report_task").As("t"), goqu.On(goqu.I("t.id").Eq(goqu.I("r.id")))).
Select(goqu.COUNT(goqu.I("r.*"))).
Where(goqu.I("r.withdraw").Eq(model.REPORT_WITHDRAW_APPLYING))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
reportQuery = reportQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("p.phone").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
goqu.I("ud.phone").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("p.phone").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
goqu.I("ud.phone").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
reportQuery = reportQuery.
Order(goqu.I("r.last_withdraw_applied_at").Desc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
reports []*model.ReportIndex = make([]*model.ReportIndex, 0)
count int64
)
querySql, queryArgs, _ := reportQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &reports, querySql, queryArgs...); err != nil {
rr.log.Error("列出当前正在等待审核的已经申请撤回的核算报表时出现错误", zap.Error(err))
return reports, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
rr.log.Error("列出当前正在等待审核的已经申请撤回的核算报表总数量时出现错误", zap.Error(err))
return reports, 0, err
}
return reports, count, nil
}

208
repository/synchronize.go Normal file
View File

@@ -0,0 +1,208 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/vo"
"fmt"
"github.com/doug-martin/goqu/v9"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
"strconv"
)
type _SynchronizeRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var SynchronizeRepository = _SynchronizeRepository{
log: logger.Named("Repository", "Synchronize"),
ds: goqu.Dialect("postgres"),
}
func (sr _SynchronizeRepository) SearchSynchronizeSchedules(userId *string, parkId *string, page uint, keyword *string) ([]*model.SynchronizeSchedule, int64, error) {
sr.log.Info("检索符合指定条件的同步记录", zap.String("user id", tools.DefaultTo(userId, "")),
zap.String("park id", tools.DefaultTo(parkId, "")), zap.Uint("page", page),
zap.String("keyword", tools.DefaultTo(keyword, "")))
ctx, cancelFunc := global.TimeoutContext()
defer cancelFunc()
//scheduleQuery := "select ss.*, ud.name as user_name, p.name as park_name from synchronize_schedule as ss
//join park as p on p.id=ss.park_id join user_detail as ud on ud.id=ss.user_id where 1=1"
schedulequery := sr.ds.From(goqu.T("synchronize_schedule").As("ss")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("ss.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("ss.user_id")))).
Select("ss.*", goqu.I("ud.name").As("user_name"), goqu.I("p.name").As("park_name"))
//countQuery := "select count(ss.*) from synchronize_schedule as ss
//join park as p on p.id=ss.park_id join user_detail as ud on ud.id=ss.user_id where 1=1"
countquery := sr.ds.From(goqu.T("synchronize_schedule").As("ss")).
Join(goqu.T("park").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("ss.park_id")))).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.I("ud.id").Eq(goqu.I("ss.user_id")))).
Select(goqu.COUNT(goqu.I("ss.*")))
if userId != nil && len(*userId) > 0 {
schedulequery = schedulequery.Where(goqu.I("ss.user_id").Eq(*userId))
countquery = countquery.Where(goqu.I("ss.user_id").Eq(*userId))
}
if parkId != nil && len(*parkId) > 0 {
schedulequery = schedulequery.Where(goqu.I("ss.park_id").Eq(*parkId))
countquery = countquery.Where(goqu.I("ss.park_id").Eq(*parkId))
}
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
schedulequery = schedulequery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("p.phone").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
goqu.I("ud.phone").ILike(pattern),
goqu.I("ss.task_name").ILike(pattern),
goqu.I("ss.task_description").ILike(pattern),
))
countquery = countquery.Where(goqu.Or(
goqu.I("p.name").ILike(pattern),
goqu.I("p.abbr").ILike(pattern),
goqu.I("p.address").ILike(pattern),
goqu.I("p.contact").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
goqu.I("ud.contact").ILike(pattern),
goqu.I("ud.phone").ILike(pattern),
goqu.I("ss.task_name").ILike(pattern),
goqu.I("ss.task_description").ILike(pattern),
))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
schedulequery = schedulequery.
Order(goqu.I("ss.created_at").Desc()).
Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
var (
schedule []*model.SynchronizeSchedule = make([]*model.SynchronizeSchedule, 0)
count int64
)
querySql, queryArgs, _ := schedulequery.Prepared(true).ToSQL()
countSql, countArgs, _ := countquery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &schedule, querySql, queryArgs...); err != nil {
sr.log.Error("获取同步任务时出现错误", zap.Error(err))
return schedule, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &count, countSql, countArgs...); err != nil {
sr.log.Error("检索同步任务总数量时出现错误", zap.Error(err))
return schedule, 0, err
}
return schedule, count, nil
}
// From("synchronize_schedule").
//
// Select(
// goqu.I("synchronize_schedule.*"),
// goqu.I("user_detail.name").As("user_name"),
// goqu.I("park.name").As("park_name"),
// ).
// Join(
// goqu.T("park").On(goqu.I("park.id").Eq(goqu.I("synchronize_schedule.park_id"))),
// goqu.T("user_detail").On(goqu.I("user_detail.id").Eq(goqu.I("synchronize_schedule.user_id"))),
// ).
// Where(goqu.C("1").Eq(1))
//
// SELECT count(ss.*)
// FROM synchronize_schedule AS ss
// JOIN park AS p ON p.id = ss.park_id
// JOIN user_detail AS ud ON ud.id = ss.user_id
// WHERE true`
//
// var args []interface{}
//
// if uid != nil {
// scheduleQuery += " AND ss.user_id = $1"
// countQuery += " AND ss.user_id = $1"
// args = append(args, *uid)
// }
//
// if pid != nil {
// scheduleQuery += " AND ss.park_id = $2"
// countQuery += " AND ss.park_id = $2"
// args = append(args, *pid)
// }
//
// if keyword != nil {
// pattern := "%" + *keyword + "%"
// scheduleQuery += ` AND (p.name LIKE $3 OR p.abbr LIKE $3 OR p.address LIKE $3 OR p.contact LIKE $3 OR
//
// p.phone LIKE $3 OR ud.name LIKE $3 OR ud.abbr LIKE $3 OR ud.contact LIKE $3 OR
// ud.phone LIKE $3 OR ss.task_name LIKE $3 OR ss.task_description LIKE $3)`
//
// args = append(args, pattern)
// }
func (sr _SynchronizeRepository) RetrieveSynchronizeConfiguration(uId, pId string) (vo.SynchronizeConfiguration, error) {
sr.log.Info("检索符合指定条件的同步记录", zap.String("user id", uId), zap.String("park id", pId))
ctx, cancelFunc := global.TimeoutContext()
defer cancelFunc()
//select * from synchronize_config where user_id=$1 and park_id=$2
configSql, configArgs, _ := sr.ds.
From(goqu.T("synchronize_config")).
Where(goqu.I("user_id").Eq(uId)).
Where(goqu.I("park_id").Eq(pId)).
Prepared(true).Select("*").ToSQL()
fmt.Println(configSql)
var configs []model.SynchronizeConfiguration
if err := pgxscan.Select(ctx, global.DB, &configs, configSql, configArgs...); err != nil {
fmt.Println(err)
sr.log.Error("获取同步任务时出现错误", zap.Error(err))
return vo.SynchronizeConfiguration{}, err
}
if len(configs) <= 0 {
return vo.SynchronizeConfiguration{}, nil
}
maxr := strconv.Itoa(int(configs[0].MaxRetries))
retry := strconv.Itoa(int(configs[0].RetryInterval))
synconfig := vo.SynchronizeConfiguration{
CollectAt: configs[0].CollectAt.Format("15:04:05"),
EntID: configs[0].User,
Imrs: configs[0].ImrsType,
ImrsAccount: configs[0].AuthorizationAccount,
ImrsKey: string(configs[0].AuthorizationKey),
ImrsSecret: configs[0].AuthorizationSecret,
Interval: float64(configs[0].Interval),
MaxRetries: maxr,
ParkID: configs[0].Park,
ReadingType: float64(configs[0].MeterReadingType),
RetryAlgorithm: float64(configs[0].RetryIntervalAlgorithm),
RetryInterval: retry,
}
return synconfig, nil
}
func (sr _SynchronizeRepository) CreateSynchronizeConfiguration(tx pgx.Tx, ctx context.Context, uId string, form *vo.SynchronizeConfigurationCreateForm) (bool, error) {
sr.log.Info("创建新的同步用户配置", zap.String("user Id", uId))
ctx, cancel := global.TimeoutContext()
defer cancel()
//insert into synchronize_config (user_id, park_id, meter_reading_type, imrs_type, imrs_authorization_account,
// imrs_authorization_secret, imrs_authorization_key, interval, collect_at, max_retries, retry_interval, retry_interval_algorithm) values
configSql, configArgs, _ := sr.ds.
Insert(goqu.T("synchronize_config")).
Cols(
"user_id", "park_id", "meter_reading_type", "imrs_type", "imrs_authorization_account", "imrs_authorization_secret",
"imrs_authorization_key", "interval", "collect_at", "max_retries",
"retry_interval", "retry_interval_algorithm").
Vals(
goqu.Vals{uId, form.ParkID, form.ReadingType, form.Imrs, form.ImrsAccount, form.ImrsSecret, form.ImrsKey, form.Interval,
form.CollectAt, form.MaxRetries, form.RetryInterval, form.RetryAlgorithm,
},
).
Prepared(true).ToSQL()
ok, err := tx.Exec(ctx, configSql, configArgs...)
if err != nil {
sr.log.Error("创建同步配置信息失败", zap.Error(err))
return false, err
}
return ok.RowsAffected() > 0, nil
}

458
repository/tenement.go Normal file
View File

@@ -0,0 +1,458 @@
package repository
import (
"context"
"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))
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
}
return count > 0, nil
}
// 列出指定园区中的所有商户
func (tr _TenementRepository) ListTenements(pid string, page uint, keyword, building *string, startDate, endDate *types.Date, state int) ([]*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.Int("State", state),
)
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))
countQuery := tr.ds.
From(goqu.T("tenement").As("t")).
Select(goqu.COUNT("t.*")).
Where(goqu.I("t.park_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),
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 == 0 {
tenementQuery = tenementQuery.Where(
goqu.I("t.moved_out_at").IsNull(),
)
countQuery = countQuery.Where(
goqu.I("t.moved_out_at").IsNull(),
)
} else {
tenementQuery = tenementQuery.Where(
goqu.I("t.moved_out_at").IsNotNull(),
)
countQuery = countQuery.Where(
goqu.I("t.moved_out_at").IsNotNull(),
)
}
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
}
return tenements, total, nil
}
// 查询指定园区中某一商户下的所有表计编号,不包含公摊表计
func (tr _TenementRepository) ListMeterCodesBelongsTo(pid, tid string) ([]string, error) {
tr.log.Info("查询指定商户下所有的表计编号", zap.String("Park", pid), zap.String("Tenement", tid))
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
}
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()
if _, err := tx.Exec(
ctx,
"INSERT INTO tenement (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) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
[]interface{}{
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,
}...,
); 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
}
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))
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("t.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
}
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))
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
}
return &tenement, nil
}

166
repository/top_up.go Normal file
View File

@@ -0,0 +1,166 @@
package repository
import (
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"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"
"go.uber.org/zap"
)
type _TopUpRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var TopUpRepository = _TopUpRepository{
log: logger.Named("Repository", "TopUp"),
ds: goqu.Dialect("postgres"),
}
// 检索符合条件的商户充值记录
func (tur _TopUpRepository) ListTopUps(pid string, startDate, endDate *types.Date, keyword *string, page uint) ([]*model.TopUp, int64, error) {
tur.log.Info("查询符合条件的商户充值记录", zap.String("Park", pid), logger.DateFieldp("StartDate", startDate), logger.DateFieldp("EndDate", endDate), zap.Stringp("keyword", keyword), zap.Uint("page", page))
ctx, cancel := global.TimeoutContext()
defer cancel()
topUpQuery := tur.ds.
From(goqu.T("tenement_top_ups").As("t")).
LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))).
Select("t.*", goqu.I("te.full_name").As("tenement_name"), goqu.I("m.address").As("meter_address")).
Where(goqu.I("t.park_id").Eq(pid))
countQuery := tur.ds.
From(goqu.T("tenement_top_ups").As("t")).
LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))).
LeftJoin(goqu.T("meter_04kv").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))).
Select(goqu.COUNT("t.*")).
Where(goqu.I("t.park_id").Eq(pid))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
topUpQuery = topUpQuery.Where(goqu.Or(
goqu.I("te.full_name").ILike(pattern),
goqu.I("te.short_name").ILike(pattern),
goqu.I("te.abbr").ILike(pattern),
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
countQuery = countQuery.Where(goqu.Or(
goqu.I("te.full_name").ILike(pattern),
goqu.I("te.short_name").ILike(pattern),
goqu.I("te.abbr").ILike(pattern),
goqu.I("m.code").ILike(pattern),
goqu.I("m.address").ILike(pattern),
))
}
if startDate != nil {
topUpQuery = topUpQuery.Where(goqu.I("t.topped_up_at").Gte(startDate.ToBeginningOfDate()))
countQuery = countQuery.Where(goqu.I("t.topped_up_at").Gte(startDate.ToBeginningOfDate()))
}
if endDate != nil {
topUpQuery = topUpQuery.Where(goqu.I("t.topped_up_at").Lte(endDate.ToEndingOfDate()))
countQuery = countQuery.Where(goqu.I("t.topped_up_at").Lte(endDate.ToEndingOfDate()))
}
startRow := (page - 1) * config.ServiceSettings.ItemsPageSize
topUpQuery = topUpQuery.Order(goqu.I("t.topped_up_at").Desc()).Offset(startRow).Limit(config.ServiceSettings.ItemsPageSize)
topUpSql, topUpArgs, _ := topUpQuery.Prepared(true).ToSQL()
countSql, countArgs, _ := countQuery.Prepared(true).ToSQL()
var (
topUps []*model.TopUp = make([]*model.TopUp, 0)
total int64 = 0
)
if err := pgxscan.Select(ctx, global.DB, &topUps, topUpSql, topUpArgs...); err != nil {
tur.log.Error("查询商户充值记录失败", zap.Error(err))
return topUps, 0, err
}
if err := pgxscan.Get(ctx, global.DB, &total, countSql, countArgs...); err != nil {
tur.log.Error("查询商户充值记录总数失败", zap.Error(err))
return topUps, 0, err
}
return topUps, total, nil
}
// 取得一个充值记录的详细信息
func (tur _TopUpRepository) GetTopUp(pid, topUpCode string) (*model.TopUp, error) {
tur.log.Info("查询充值记录", zap.String("Park", pid), zap.String("TopUpCode", topUpCode))
ctx, cancel := global.TimeoutContext()
defer cancel()
topUpSql, topUpArgs, _ := tur.ds.
From(goqu.T("tenement_top_ups").As("t")).
LeftJoin(goqu.T("tenement").As("te"), goqu.On(goqu.I("te.id").Eq(goqu.I("t.tenement_id")), goqu.I("te.park_id").Eq(goqu.I("t.park_id")))).
LeftJoin(goqu.T("meter").As("m"), goqu.On(goqu.I("m.code").Eq(goqu.I("t.meter_id")), goqu.I("m.park_id").Eq(goqu.I("t.park_id")))).
Select("t.*", goqu.I("te.full_name").As("tenement_name"), goqu.I("m.address").As("meter_address")).
Where(goqu.I("t.park_id").Eq(pid), goqu.I("t.top_up_code").Eq(topUpCode)).
Prepared(true).ToSQL()
var topUp model.TopUp
if err := pgxscan.Get(ctx, global.DB, &topUp, topUpSql, topUpArgs...); err != nil {
tur.log.Error("查询充值记录失败", zap.Error(err))
return nil, err
}
return &topUp, nil
}
// 创建一条新的充值记录
func (tur _TopUpRepository) CreateTopUp(pid string, form *vo.TopUpCreationForm) error {
tur.log.Info("创建一条新的充值记录", zap.String("Park", pid), zap.String("Tenement", form.Tenement), zap.String("Meter", form.Meter))
ctx, cancel := global.TimeoutContext()
defer cancel()
serial.StringSerialRequestChan <- 1
topUpCode := serial.Prefix("U", <-serial.StringSerialResponseChan)
topUpTime := types.Now()
topUpSql, topUpArgs, _ := tur.ds.
Insert("tenement_top_ups").
Cols("top_up_code", "park_id", "tenement_id", "meter_id", "topped_up_at", "amount", "payment_type").
Vals(goqu.Vals{
topUpCode,
pid,
form.Tenement,
form.Meter,
topUpTime,
form.Amount,
model.PAYMENT_CASH,
}).
Prepared(true).ToSQL()
if _, err := global.DB.Exec(ctx, topUpSql, topUpArgs...); err != nil {
tur.log.Error("创建充值记录失败", zap.Error(err))
return err
}
return nil
}
// 删除一条充值记录
func (tur _TopUpRepository) DeleteTopUp(pid, topUpCode string) error {
tur.log.Info("删除一条充值记录", zap.String("Park", pid), zap.String("TopUpCode", topUpCode))
ctx, cancel := global.TimeoutContext()
defer cancel()
topUpSql, topUpArgs, _ := tur.ds.
Update("tenement_top_ups").
Set(goqu.Record{"cancelled_at": types.Now()}).
Where(goqu.I("park_id").Eq(pid), goqu.I("top_up_code").Eq(topUpCode)).
Prepared(true).ToSQL()
if _, err := global.DB.Exec(ctx, topUpSql, topUpArgs...); err != nil {
tur.log.Error("删除充值记录失败", zap.Error(err))
return err
}
return nil
}

419
repository/user.go Normal file
View File

@@ -0,0 +1,419 @@
package repository
import (
"context"
"electricity_bill_calc/config"
"electricity_bill_calc/global"
"electricity_bill_calc/logger"
"electricity_bill_calc/model"
"electricity_bill_calc/tools"
"electricity_bill_calc/types"
"fmt"
"time"
"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/fufuok/utils/xhash"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
"github.com/samber/lo"
"go.uber.org/zap"
)
type _UserRepository struct {
log *zap.Logger
ds goqu.DialectWrapper
}
var UserRepository = _UserRepository{
log: logger.Named("Repository", "User"),
ds: goqu.Dialect("postgres"),
}
// 使用用户名查询指定用户的基本信息
func (ur _UserRepository) FindUserByUsername(username string) (*model.User, error) {
ur.log.Info("根据用户名查询指定用户的基本信息。", zap.String("username", username))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.User
sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"username": username}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err))
return nil, err
}
return &user, nil
}
// 使用用户唯一编号查询指定用户的基本信息
func (ur _UserRepository) FindUserById(uid string) (*model.User, error) {
ur.log.Info("根据用户唯一编号查询指定用户的基本信息。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.User
sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
}
return &user, nil
}
// 使用用户的唯一编号获取用户的详细信息
func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, error) {
ur.log.Info("根据用户唯一编号查询指定用户的详细信息。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.UserDetail
sql, params, _ := ur.ds.From("user_detail").Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
}
return &user, nil
}
// 使用用户唯一编号获取用户的综合详细信息
func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail, error) {
ur.log.Info("根据用户唯一编号查询用户的综合详细信息", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var user model.UserWithDetail
sql, params, _ := ur.ds.
From("user").As("u").
Join(
goqu.T("user_detail").As("ud"),
goqu.On(goqu.Ex{
"ud.id": goqu.I("u.id"),
})).
Select(
"u.id", "u.username", "u.reset_needed", "u.type", "u.enabled",
"ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone",
"ud.unit_service_fee", "ud.service_expiration",
"ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by").
Where(goqu.Ex{"u.id": uid}).
Prepared(true).ToSQL()
if err := pgxscan.Get(
ctx, global.DB, &user, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err))
return nil, err
}
return &user, nil
}
// 检查指定用户唯一编号是否存在对应的用户
func (ur _UserRepository) IsUserExists(uid string) (bool, error) {
ur.log.Info("检查指定用户唯一编号是否存在对应的用户。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
var userCount int
sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"id": uid}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err))
return false, err
}
return userCount > 0, nil
}
// 检查指定用户名在数据库中是否已经存在
func (ur _UserRepository) IsUsernameExists(username string) (bool, error) {
ur.log.Info("检查指定用户名在数据库中是否已经存在。", zap.String("username", username))
ctx, cancel := global.TimeoutContext()
defer cancel()
var userCount int
sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"username": username}).Prepared(true).ToSQL()
if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil {
ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err))
return false, err
}
return userCount > 0, nil
}
// 创建一个新用户
func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, operator *string) (bool, error) {
ur.log.Info("创建一个新用户。", zap.String("username", user.Username))
ctx, cancel := global.TimeoutContext()
defer cancel()
tx, err := global.DB.Begin(ctx)
if err != nil {
ur.log.Error("启动数据库事务失败。", zap.Error(err))
return false, err
}
createdTime := types.Now()
userSql, userParams, _ := ur.ds.
Insert("user").
Rows(
goqu.Record{
"id": user.Id, "username": user.Username, "password": user.Password,
"reset_needed": user.ResetNeeded, "type": user.UserType, "enabled": user.Enabled,
"created_at": createdTime,
},
).
Prepared(true).ToSQL()
userResult, err := tx.Exec(ctx, userSql, userParams...)
if err != nil {
ur.log.Error("向数据库插入新用户基本信息失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
userDetailSql, userDetailParams, _ := ur.ds.
Insert("user_detail").
Rows(
goqu.Record{
"id": user.Id, "name": detail.Name, "abbr": tools.PinyinAbbr(*detail.Name), "region": detail.Region,
"address": detail.Address, "contact": detail.Contact, "phone": detail.Phone,
"unit_service_fee": detail.UnitServiceFee, "service_expiration": detail.ServiceExpiration,
"created_at": createdTime, "created_by": operator,
"last_modified_at": createdTime, "last_modified_by": operator,
},
).
Prepared(true).ToSQL()
detailResult, err := tx.Exec(ctx, userDetailSql, userDetailParams...)
if err != nil {
ur.log.Error("向数据库插入新用户详细信息失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
err = tx.Commit(ctx)
if err != nil {
ur.log.Error("提交数据库事务失败。", zap.Error(err))
tx.Rollback(ctx)
return false, err
}
return userResult.RowsAffected() > 0 && detailResult.RowsAffected() > 0, nil
}
// 根据给定的条件检索用户
func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) ([]*model.UserWithDetail, int64, error) {
ur.log.Info("根据给定的条件检索用户。", zap.Uint("page", page), zap.Stringp("keyword", keyword), zap.Int16("user type", userType), zap.Boolp("state", state))
ctx, cancel := global.TimeoutContext()
defer cancel()
var (
userWithDetails []*model.UserWithDetail
userCount int64
)
userQuery := ur.ds.
From(goqu.T("user").As("u")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})).
Select(
"u.id", "u.username", "u.reset_needed", "u.type", "u.enabled",
"ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone",
"ud.unit_service_fee", "ud.service_expiration",
"ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by",
)
countQuery := ur.ds.
From(goqu.T("user").As("u")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})).
Select(goqu.COUNT("*"))
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
userQuery = userQuery.Where(
goqu.Or(
goqu.I("u.username").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
),
)
countQuery = countQuery.Where(
goqu.Or(
goqu.I("u.username").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
),
)
}
if userType != -1 {
userQuery = userQuery.Where(goqu.Ex{"u.type": userType})
countQuery = countQuery.Where(goqu.Ex{"u.type": userType})
}
if state != nil {
userQuery = userQuery.Where(goqu.Ex{"u.enabled": state})
countQuery = countQuery.Where(goqu.Ex{"u.enabled": state})
}
userQuery.Order(goqu.I("u.created_at").Desc())
currentPosition := (page - 1) * config.ServiceSettings.ItemsPageSize
userQuery = userQuery.Offset(currentPosition).Limit(config.ServiceSettings.ItemsPageSize)
userSql, userParams, _ := userQuery.Prepared(true).ToSQL()
countSql, countParams, _ := countQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &userWithDetails, userSql, userParams...); err != nil {
ur.log.Error("从数据库查询用户列表失败。", zap.Error(err))
return make([]*model.UserWithDetail, 0), 0, err
}
if err := pgxscan.Get(ctx, global.DB, &userCount, countSql, countParams...); err != nil {
ur.log.Error("从数据库查询用户列表总数失败。", zap.Error(err))
return make([]*model.UserWithDetail, 0), 0, err
}
return userWithDetails, userCount, nil
}
// 更新指定用户的详细信息
func (ur _UserRepository) UpdateDetail(uid string, userDetail model.UserModificationForm, operator *string) (bool, error) {
ur.log.Info("更新指定用户的详细信息。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
updates := goqu.Record{
"name": userDetail.Name, "abbr": tools.PinyinAbbr(userDetail.Name), "region": userDetail.Region,
"address": userDetail.Address, "contact": userDetail.Contact, "phone": userDetail.Phone,
"last_modified_at": types.Now(), "last_modified_by": operator,
}
if userDetail.UnitServiceFee != nil {
updates = lo.Assign(updates, goqu.Record{"unit_service_fee": userDetail.UnitServiceFee})
}
userDetailUpdateQuery := ur.ds.
Update("user_detail").
Set(updates).
Where(goqu.Ex{"id": uid})
userDetailSql, userDetailParams, _ := userDetailUpdateQuery.
Prepared(true).ToSQL()
if res, err := global.DB.Exec(ctx, userDetailSql, userDetailParams...); err != nil {
ur.log.Error("向数据库更新指定用户的详细信息失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 更新指定用户的登录凭据
func (ur _UserRepository) UpdatePassword(uid, newCredential string, needReset bool) (bool, error) {
ur.log.Info("更新指定用户的登录凭据。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
userUpdateQuery := ur.ds.
Update("user").
Set(goqu.Record{"password": xhash.Sha512Hex(newCredential), "reset_needed": needReset}).
Where(goqu.Ex{"id": uid})
userSql, userParams, _ := userUpdateQuery.
Prepared(true).ToSQL()
if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil {
ur.log.Error("向数据库更新指定用户的登录凭据失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 更新指定用户的可用性状态
func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) {
ur.log.Info("更新指定用户的可用性状态。", zap.String("user id", uid))
ctx, cancel := global.TimeoutContext()
defer cancel()
userUpdateQuery := ur.ds.
Update("user").
Set(goqu.Record{"enabled": state}).
Where(goqu.Ex{"id": uid})
userSql, userParams, _ := userUpdateQuery.
Prepared(true).ToSQL()
if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil {
ur.log.Error("向数据库更新指定用户的可用性状态失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 检索条目数量有限的用户详细信息
func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) ([]*model.UserWithDetail, error) {
ur.log.Info("检索条目数量有限的用户详细信息。", zap.Int16p("user type", userType), zap.Uint("limit", limit), zap.Stringp("keyword", keyword))
actualUserType := tools.DefaultTo(userType, model.USER_TYPE_ENT)
ctx, cancel := global.TimeoutContext()
defer cancel()
var users = make([]*model.UserWithDetail, 0)
userQuery := ur.ds.
From(goqu.T("user").As("u")).
Join(goqu.T("user_detail").As("ud"), goqu.On(goqu.Ex{"ud.id": goqu.I("u.id")})).
Select(
"u.id", "u.username", "u.reset_needed", "u.type", "u.enabled",
"ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone",
"ud.unit_service_fee", "ud.service_expiration",
"ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by",
)
if keyword != nil && len(*keyword) > 0 {
pattern := fmt.Sprintf("%%%s%%", *keyword)
userQuery = userQuery.Where(
goqu.Or(
goqu.I("u.username").ILike(pattern),
goqu.I("ud.name").ILike(pattern),
goqu.I("ud.abbr").ILike(pattern),
),
)
}
userQuery = userQuery.Where(goqu.Ex{"u.type": actualUserType})
userQuery.Order(goqu.I("u.created_at").Desc()).Limit(limit)
userSql, userParams, _ := userQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil {
ur.log.Error("从数据库查询用户列表失败。", zap.Error(err))
return nil, err
}
return users, nil
}
// 更新指定用户的服务有效期限
func (ur _UserRepository) UpdateServiceExpiration(tx pgx.Tx, ctx context.Context, uid string, expiration time.Time) (bool, error) {
ur.log.Info("更新指定用户的服务有效期限。", zap.String("user id", uid))
userDetailUpdateQuery := ur.ds.
Update("user_detail").
Set(goqu.Record{"service_expiration": expiration}).
Where(goqu.Ex{"id": uid})
userDetailSql, userDetailParams, _ := userDetailUpdateQuery.
Prepared(true).ToSQL()
if res, err := tx.Exec(ctx, userDetailSql, userDetailParams...); err != nil {
ur.log.Error("向数据库更新指定用户的服务有效期限失败。", zap.String("user id", uid), zap.Error(err))
return false, err
} else {
return res.RowsAffected() > 0, nil
}
}
// 检索指定用户列表的详细信息
func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]*model.UserDetail, error) {
ur.log.Info("检索指定用户列表的详细信息。", zap.Strings("user ids", uids))
if len(uids) == 0 {
return make([]*model.UserDetail, 0), nil
}
ctx, cancel := global.TimeoutContext()
defer cancel()
var users []*model.UserDetail
userQuery := ur.ds.
From("user_detail").
Where(goqu.Ex{"id": uids})
userSql, userParams, _ := userQuery.Prepared(true).ToSQL()
if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil {
ur.log.Error("从数据库查询用户列表失败。", zap.Error(err))
return make([]*model.UserDetail, 0), err
}
return users, nil
}