181 lines
8.0 KiB
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
|
|
}
|