electricity_bill_calc_service/service/invoice.go

181 lines
8.0 KiB
Go

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
}