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")). 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()) } 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 }