diff --git a/model/invoice.go b/model/invoice.go index 1f07041..827bfac 100644 --- a/model/invoice.go +++ b/model/invoice.go @@ -1,5 +1,12 @@ package model +import ( + "electricity_bill_calc/tools" + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + type InvoiceTitle struct { Name string `json:"name"` USCI string `json:"usci"` @@ -8,3 +15,31 @@ type InvoiceTitle struct { Bank string `json:"bank"` Account string `json:"account"` } + +type InvoiceCargo struct { + Name string `json:"name"` + Price decimal.Decimal `json:"price"` + Unit string `json:"unit"` + Quantity decimal.Decimal `json:"quantity"` + TaxRate decimal.Decimal `json:"taxRate"` + Tax decimal.Decimal `json:"tax"` + Total decimal.Decimal `json:"total"` +} + +type Invoice struct { + InvoiceNo string `json:"invoiceNo"` + Park string `json:"parkId" db:"park_id"` + Tenement string `json:"tenementId" db:"tenement_id"` + InvoiceType *string `json:"type" db:"type"` + Info InvoiceTitle `json:"invoiceInfo" db:"invoice_info"` + Cargos []InvoiceCargo `json:"cargos"` + TaxRate decimal.Decimal `json:"taxRate" db:"tax_rate"` + TaxMethod int16 `json:"taxMethod" db:"tax_method"` + Total decimal.Decimal `json:"total" db:"total"` + IssuedAt types.DateTime `json:"issuedAt" db:"issued_at"` + Covers []string `json:"covers"` +} + +func (i Invoice) Type() string { + return tools.DefaultOrEmptyStr(i.InvoiceType, "") +} diff --git a/model/report.go b/model/report.go new file mode 100644 index 0000000..5b58f60 --- /dev/null +++ b/model/report.go @@ -0,0 +1,129 @@ +package model + +import ( + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type ReportIndex struct { + Id string `json:"id"` + Park string `json:"parkId" db:"park_id"` + Period types.DateRange `json:"period"` + Category int16 `json:"category"` + MeterType int16 `json:"meter04kvType" db:"meter_04kv_type"` + PricePolicy int16 `json:"pricePolicy"` + BasisPooled int16 `json:"basisPooled"` + AdjustPooled int16 `json:"adjustPooled"` + LossPooled int16 `json:"lossPooled"` + PublicPooled int16 `json:"publicPooled"` + Published bool `json:"published"` + PublishedAt *types.DateTime `json:"publishedAt" db:"published_at"` + Withdraw int16 `json:"withdraw"` + LastWithdrawAppliedAt *types.DateTime `json:"lastWithdrawAppliedAt" db:"last_withdraw_applied_at"` + LastWithdrawAuditAt *types.DateTime `json:"lastWithdrawAuditAt" db:"last_withdraw_audit_at"` + Status *int16 `json:"status"` + Message *string `json:"message"` + CreatedAt types.DateTime `json:"createdAt" db:"created_at"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` +} + +type ReportSummary struct { + ReportId string `json:"reportId" db:"report_id"` + OverallArea decimal.Decimal `json:"overallArea" db:"overall_area"` + Overall ConsumptionUnit `json:"overall"` + ConsumptionFee decimal.NullDecimal `json:"consumptionFee" db:"consumption_fee"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + Loss decimal.NullDecimal `json:"loss"` + LossFee decimal.NullDecimal `json:"lossFee" db:"loss_fee"` + LossProportion decimal.NullDecimal `json:"lossProportion" db:"loss_proportion"` + AuthorizeLoss *ConsumptionUnit `json:"authorizeLoss" db:"authorize_loss"` + BasicFee decimal.Decimal `json:"basicFee" db:"basic_fee"` + BasicPooledPriceConsumption decimal.NullDecimal `json:"basicPooledPriceConsumption" db:"basic_pooled_price_consumption"` + BasicPooledPriceArea decimal.NullDecimal `json:"basicPooledPriceArea" db:"basic_pooled_price_area"` + AdjustFee decimal.Decimal `json:"adjustFee" db:"adjust_fee"` + AdjustPooledPriceConsumption decimal.NullDecimal `json:"adjustPooledPriceConsumption" db:"adjust_pooled_price_consumption"` + AdjustPooledPriceArea decimal.NullDecimal `json:"adjustPooledPriceArea" db:"adjust_pooled_price_area"` + LossDilutedPrice decimal.NullDecimal `json:"lossDilutedPrice" db:"loss_diluted_price"` + TotalConsumption decimal.Decimal `json:"totalConsumption" db:"total_consumption"` + FinalDilutedOverall decimal.NullDecimal `json:"finalDilutedOverall" db:"final_diluted_overall"` +} + +type ReportPublicConsumption struct { + ReportId string `json:"reportId" db:"report_id"` + MeterId string `json:"parkMeterId" db:"park_meter_id"` + Overall ConsumptionUnit `json:"overall"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + LossAdjust ConsumptionUnit `json:"lossAdjust"` + ConsumptionTotal decimal.Decimal `json:"consumptionTotal" db:"consumption_total"` + LossAdjustTotal decimal.Decimal `json:"lossAdjustTotal" db:"loss_adjust_total"` + FinalTotal decimal.Decimal `json:"finalTotal" db:"final_total"` +} + +type ReportDetailedPublicConsumption struct { + MeterDetail + ReportPublicConsumption +} + +type ReportPooledConsumption struct { + ReportId string `json:"reportId" db:"report_id"` + MeterId string `json:"parkMeterId" db:"park_meter_id"` + Overall ConsumptionUnit `json:"overall"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + PooledArea decimal.Decimal `json:"pooledArea" db:"pooled_area"` + Diluted []NestedMeter `json:"diluted"` +} + +type ReportDetailedPooledConsumption struct { + MeterDetail + ReportPooledConsumption + PublicPooled int16 `json:"publicPooled"` +} + +type ReportDetailNestedMeterConsumption struct { + Meter MeterDetail `json:"meter"` + Consumption NestedMeter `json:"consumption"` +} + +type ReportTenement struct { + ReportId string `json:"reportId" db:"report_id"` + Tenement string `json:"tenementId" db:"tenement_id"` + Detail Tenement `json:"tenementDetail" db:"tenement_detail"` + Period types.DateRange `json:"calcPeriod" db:"calc_period"` + Overall ConsumptionUnit `json:"overall"` + Critical ConsumptionUnit `json:"critical"` + Peak ConsumptionUnit `json:"peak"` + Flat ConsumptionUnit `json:"flat"` + Valley ConsumptionUnit `json:"valley"` + BasicFeePooled decimal.Decimal `json:"basicFeePooled" db:"basic_fee_pooled"` + AdjustFeePooled decimal.Decimal `json:"adjustFeePooled" db:"adjust_fee_pooled"` + LossFeePooled decimal.Decimal `json:"lossFeePooled" db:"loss_fee_pooled"` + FinalPooled decimal.Decimal `json:"finalPooled" db:"final_pooled"` + FinalCharge decimal.Decimal `json:"finalCharge" db:"final_charge"` + Invoice []string `json:"invoice" db:"invoice"` + Meters []NestedMeter `json:"meters" db:"meters"` + Pooled []NestedMeter `json:"pooled" db:"pooled"` +} + +type ReportTask struct { + Id string `json:"id"` + LastModifiedAt types.DateTime `json:"lastModifiedAt" db:"last_modified_at"` + Status int16 `json:"status"` + Message *string `json:"message"` +} + +type SimplifiedTenementCharge struct { + ReportId string `json:"reportId" db:"report_id"` + Period types.DateRange `json:"period"` + TotalConsumption decimal.Decimal `json:"totalConsumption" db:"total_consumption"` + FinalCharge decimal.Decimal `json:"finalCharge" db:"final_charge"` +} diff --git a/repository/invoice.go b/repository/invoice.go new file mode 100644 index 0000000..413e726 --- /dev/null +++ b/repository/invoice.go @@ -0,0 +1,400 @@ +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 +} diff --git a/vo/invoice.go b/vo/invoice.go new file mode 100644 index 0000000..e60a3fa --- /dev/null +++ b/vo/invoice.go @@ -0,0 +1,27 @@ +package vo + +import ( + "electricity_bill_calc/model" + "electricity_bill_calc/types" + + "github.com/shopspring/decimal" +) + +type InvoiceResponse struct { + No string `json:"no" copier:"InvoiceNo"` + Tenement string `json:"tenement"` + Title model.InvoiceTitle `json:"title" copier:"Info"` + IssuedAt types.DateTime `json:"issuedAt"` + Amount decimal.Decimal `json:"amount" copier:"Total"` + TaxMethod int16 `json:"taxMethod"` + TaxRate decimal.Decimal `json:"taxRate"` + InvoiceType string `json:"invoiceType" copier:"Type"` +} + +type InvoiceCreationForm struct { + Park string `json:"park"` + Tenement string `json:"tenement"` + TaxMethod int16 `json:"taxMethod"` + TaxRate decimal.NullDecimal `json:"taxRate"` + Covers []string `json:"covers"` +}