401 lines
15 KiB
Go
401 lines
15 KiB
Go
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/types"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"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))
|
|
cacheCondition := []string{
|
|
cache.NullableStringKey(pid),
|
|
fmt.Sprintf("%d", page),
|
|
cache.NullableStringKey(keyword),
|
|
cache.NullableConditionKey(startDate),
|
|
cache.NullableConditionKey(endDate),
|
|
}
|
|
if invoices, total, err := cache.RetrievePagedSearch[[]*model.Invoice]("invoice", cacheCondition...); err != nil && invoices != nil && len(*invoices) > 0 {
|
|
ir.log.Info("从缓存中获取到了符合条件的发票记录。", zap.Int("Count", len(*invoices)), zap.Int64("Total", total))
|
|
return *invoices, total, nil
|
|
}
|
|
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
|
|
}
|
|
var relationName string
|
|
if pid != nil && len(*pid) > 0 {
|
|
relationName = fmt.Sprintf("invoice:%s", *pid)
|
|
} else {
|
|
relationName = "invoice"
|
|
}
|
|
cache.CachePagedSearch(invoices, total, []string{relationName}, "invoice", cacheCondition...)
|
|
return invoices, total, nil
|
|
}
|
|
|
|
// 查询指定商户未开票的核算记录,改记录将只包括商户整体核算,不包括商户各个表计的详细
|
|
func (ir _InvoiceRepository) ListUninvoicedTenementCharges(tid string) ([]*model.SimplifiedTenementCharge, error) {
|
|
ir.log.Info("查询指定商户的未开票核算记录", zap.String("Tenement", tid))
|
|
cacheConditions := []string{
|
|
tid,
|
|
}
|
|
if records, err := cache.RetrieveSearch[[]*model.SimplifiedTenementCharge]("uninvoiced_tenement_charge", cacheConditions...); err == nil && records != nil && len(*records) > 0 {
|
|
ir.log.Info("从缓存中获取到了符合条件的未开票核算记录。", zap.Int("Count", len(*records)))
|
|
return *records, nil
|
|
}
|
|
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
|
|
}
|
|
cache.CacheSearch(charges, []string{fmt.Sprintf("uninvoiced_tenement_charge:%s", tid)}, "uninvoiced_tenement_charge", cacheConditions...)
|
|
searchKey := cache.AssembleSearchKey("uninvoiced_tenement_charge", cacheConditions...)
|
|
for _, charge := range charges {
|
|
cache.CacheRelation(fmt.Sprintf("report:%s", charge.ReportId), cache.STORE_TYPE_KEY, searchKey)
|
|
}
|
|
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))
|
|
if invoice, err := cache.RetrieveEntity[model.Invoice]("invoice", invoiceNo); err == nil && invoice != nil {
|
|
ir.log.Info("从缓存中获取到了符合条件的发票记录。")
|
|
return invoice, nil
|
|
}
|
|
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
|
|
}
|
|
cache.CacheEntity(invoice, []string{fmt.Sprintf("invoice:%s", invoiceNo)}, "invoice", invoiceNo)
|
|
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))
|
|
cacheConditions := []string{
|
|
tid,
|
|
strings.Join(rids, ":"),
|
|
}
|
|
if records, err := cache.RetrieveSearch[[]*model.SimplifiedTenementCharge]("simplified_tenement_charge", cacheConditions...); err == nil && records != nil && len(*records) > 0 {
|
|
ir.log.Info("从缓存中获取到了符合条件的简化核算记录。", zap.Int("Count", len(*records)))
|
|
return *records, nil
|
|
}
|
|
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
|
|
}
|
|
cache.CacheSearch(charges, []string{fmt.Sprintf("tenement:%s", tid)}, "simplified_tenement_charge", cacheConditions...)
|
|
searchKey := cache.AssembleSearchKey("simplified_tenement_charge", cacheConditions...)
|
|
for _, charge := range charges {
|
|
cache.CacheRelation(fmt.Sprintf("report:%s", charge.ReportId), cache.STORE_TYPE_KEY, searchKey)
|
|
}
|
|
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
|
|
}
|
|
for _, rid := range *covers {
|
|
cache.AbolishRelation(fmt.Sprintf("report:%s", rid))
|
|
}
|
|
cache.AbolishRelation(fmt.Sprintf("invoice:%s", pid))
|
|
cache.AbolishRelation("invoice")
|
|
return nil
|
|
}
|