package service import ( "electricity_bill_calc/exceptions" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/repository" "electricity_bill_calc/types" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" "github.com/samber/lo" "github.com/shopspring/decimal" "go.uber.org/zap" ) type _InvoiceSerivce struct { log *zap.Logger ds goqu.DialectWrapper } var InvoiceService = _InvoiceSerivce{ log: logger.Named("Service", "Invoice"), ds: goqu.Dialect("postgres"), } // 获取指定的发票信息,包括发票覆盖的商户核算信息 func (is _InvoiceSerivce) GetInvoice(invoiceNo string) (*model.Invoice, []*model.SimplifiedTenementCharge, error) { is.log.Info("获取指定发票的信息", zap.String("InvoiceNo", invoiceNo)) invoice, err := repository.InvoiceRepository.GetInvoiceDetail(invoiceNo) if err != nil || invoice == nil { is.log.Error("获取指定发票的信息失败", zap.Error(err)) return nil, nil, exceptions.NewNotFoundError("指定发票信息不存在。") } charges, err := repository.InvoiceRepository.GetSimplifiedTenementCharges(invoice.Tenement, invoice.Covers) if err != nil { is.log.Error("获取指定发票的信息失败", zap.Error(err)) return nil, nil, err } return invoice, charges, nil } // 根据给定的商户核算记录和发票基本信息,计算发票中的货物信息 func (is _InvoiceSerivce) CalculateInvoiceAmount(method int16, rate decimal.Decimal, reports []*model.SimplifiedTenementCharge) (decimal.Decimal, []*model.InvoiceCargo, error) { is.log.Info("计算指定商户发票中的货物信息", zap.Int16("Method", method), logger.DecimalField("Rate", rate)) tenementConsumptionTotal := lo.Reduce(reports, func(agg decimal.Decimal, r *model.SimplifiedTenementCharge, _ int) decimal.Decimal { return agg.Add(r.TotalConsumption) }, decimal.Zero) tenementChargeTotal := lo.Reduce(reports, func(agg decimal.Decimal, r *model.SimplifiedTenementCharge, _ int) decimal.Decimal { return agg.Add(r.FinalCharge) }, decimal.Zero) if tenementConsumptionTotal.IsZero() { err := exceptions.NewInsufficientDataError("TotalConsumption", "商户核算记录中没有电量消耗数据。") is.log.Warn("计算指定商户发票中的货物信息失败", zap.Error(err)) return decimal.Zero, nil, err } var tenementTaxTotal, chargePrice, cargoTotal decimal.Decimal switch method { case model.TAX_METHOD_INCLUSIVE: tenementTaxTotal = tenementChargeTotal.Div(rate.Add(decimal.NewFromInt(1))).Mul(rate) chargePrice = (tenementChargeTotal.Sub(tenementTaxTotal)).Div(tenementConsumptionTotal) cargoTotal = tenementChargeTotal case model.TAX_METHOD_EXCLUSIVE: tenementTaxTotal = tenementChargeTotal.Mul(rate) chargePrice = tenementChargeTotal.Div(tenementConsumptionTotal) cargoTotal = tenementChargeTotal.Add(tenementTaxTotal) default: return decimal.Zero, make([]*model.InvoiceCargo, 0), exceptions.NewIllegalArgumentsError("不支持的税率计算方式。") } cargos := []*model.InvoiceCargo{ { Name: "电费", Unit: "千瓦时", Quantity: tenementConsumptionTotal, Price: chargePrice.RoundBank(2), Total: tenementChargeTotal.RoundBank(2), TaxRate: rate.RoundBank(2), Tax: tenementTaxTotal.RoundBank(2), }, } return cargoTotal.RoundBank(2), cargos, nil } // 利用用户提供的内容对发票数据进行试计算 func (is _InvoiceSerivce) TestCalculateInvoice(pid, tid string, method int16, rate decimal.NullDecimal, covers []string) (decimal.Decimal, []*model.InvoiceCargo, error) { is.log.Info("试计算发票票面数据", zap.String("Park", pid), zap.String("Tenement", tid), zap.Int16("Method", method), logger.DecimalField("Rate", rate.Decimal)) park, err := repository.ParkRepository.RetrieveParkDetail(pid) if err != nil || park == nil { is.log.Error("试计算发票票面数据失败,未能获取到指定园区的信息", zap.Error(err)) return decimal.Zero, nil, exceptions.NewNotFoundError("指定的园区不存在。") } if !rate.Valid && !park.TaxRate.Valid { is.log.Error("试计算发票票面数据失败,必须要设定发票税率") return decimal.Zero, nil, exceptions.NewIllegalArgumentsError("必须要设定发票税率。") } taxRate := park.TaxRate.Decimal if rate.Valid { taxRate = rate.Decimal } reports, err := repository.InvoiceRepository.GetSimplifiedTenementCharges(tid, covers) if err != nil { is.log.Error("试计算发票票面数据失败,未能获取到指定商户的核算记录", zap.Error(err)) return decimal.Zero, nil, err } return is.CalculateInvoiceAmount(method, taxRate, reports) } // 记录一个新的发票信息 func (is _InvoiceSerivce) SaveInvoice(pid, tid, invoiceNo string, invoiceType *string, method int16, rate decimal.NullDecimal, covers []string) error { is.log.Info("记录一个新的发票信息", zap.String("Park", pid), zap.String("Tenement", tid), zap.String("InvoiceNo", invoiceNo)) park, err := repository.ParkRepository.RetrieveParkDetail(pid) if err != nil || park == nil { is.log.Error("记录一个新的发票信息失败,未能获取到指定园区的信息", zap.Error(err)) return exceptions.NewNotFoundError("指定的园区不存在。") } if !rate.Valid && park.TaxRate.Valid { is.log.Error("记录一个新的发票信息失败,必须要设定发票税率") return exceptions.NewIllegalArgumentsError("必须要设定发票税率。") } taxRate := park.TaxRate.Decimal if rate.Valid { taxRate = rate.Decimal } reports, err := repository.InvoiceRepository.GetSimplifiedTenementCharges(tid, covers) if err != nil { is.log.Error("记录一个新的发票信息失败,未能获取到指定商户的核算记录", zap.Error(err)) return exceptions.NewUnsuccessQueryError("未能获取到指定商户的核算记录。") } total, cargos, err := is.CalculateInvoiceAmount(method, taxRate, reports) if err != nil { is.log.Error("记录一个新的发票信息失败,未能计算发票票面数据", zap.Error(err)) return exceptions.NewUnsuccessCalculateError("未能计算发票票面数据。") } issuedAt := types.Now() err = repository.InvoiceRepository.Create(pid, tid, invoiceNo, invoiceType, total, issuedAt, method, taxRate, &cargos, &covers) if err != nil { is.log.Error("记录一个新的发票信息失败,未能保存发票信息", zap.Error(err)) return exceptions.NewUnsuccessCreateError("未能保存发票信息。") } return nil } // 删除指定的发票信息 func (is _InvoiceSerivce) DeleteInvoice(invoiceNo string) error { is.log.Info("删除指定的发票信息", zap.String("InvoiceNo", invoiceNo)) invoice, err := repository.InvoiceRepository.GetInvoiceDetail(invoiceNo) if err != nil || invoice == nil { is.log.Error("删除指定的发票信息失败,未能获取到指定发票的信息", zap.Error(err)) return exceptions.NewNotFoundError("指定的发票信息不存在。") } ctx, cancel := global.TimeoutContext() defer cancel() tx, err := global.DB.Begin(ctx) if err != nil { is.log.Error("删除指定的发票信息失败,未能开启事务", zap.Error(err)) return exceptions.NewUnsuccessDBTransactionError("未能开启事务。") } err = repository.InvoiceRepository.Delete(tx, ctx, invoiceNo) if err != nil { is.log.Error("删除指定的发票信息失败,未能删除发票信息", zap.Error(err)) tx.Rollback(ctx) return exceptions.NewUnsuccessDeleteError("未能删除发票信息。") } err = repository.InvoiceRepository.DeleteInvoiceTenementRelation(tx, ctx, invoiceNo) if err != nil { is.log.Error("删除指定的发票信息失败,未能删除发票与商户核算记录之间的关联", zap.Error(err)) tx.Rollback(ctx) return exceptions.NewUnsuccessDeleteError("未能删除发票与商户核算记录之间的关联。") } err = tx.Commit(ctx) if err != nil { is.log.Error("删除指定的发票信息失败,未能提交事务", zap.Error(err)) tx.Rollback(ctx) return exceptions.NewUnsuccessDBTransactionError("未能提交事务。") } return nil }