diff --git a/engine/balances.go b/engine/balances.go index 685d7baca..e42d90c2e 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -918,6 +918,7 @@ func (b *Balance) debit(cd *CallDescriptor, ub *Account, moneyBalances Balances, Value: b.Value, DestinationID: cc.Destination, Consumed: amount, + Factor: bFactor, Category: cd.Category, ToR: tor, RateInterval: nil, @@ -1088,6 +1089,7 @@ func (b *Balance) debit(cd *CallDescriptor, ub *Account, moneyBalances Balances, Value: b.Value, DestinationID: cc.Destination, Consumed: amount, + Factor: bFactor, Category: cc.Category, ToR: cc.ToR, RateInterval: ts.RateInterval, diff --git a/engine/calldesc.go b/engine/calldesc.go index b09997cd1..9504a8294 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -877,8 +877,9 @@ func (cd *CallDescriptor) refundIncrements(fltrS *FilterS) (acnt *Account, err e utils.Logger.Warning(fmt.Sprintf("Could not get the balnce: <%s> to be refunded for account: <%s>", increment.BalanceInfo.Unit.UUID, increment.BalanceInfo.AccountID)) continue } - balance.AddValue(float64(increment.Duration.Nanoseconds())) - account.countUnits(-float64(increment.Duration.Nanoseconds()), unitType, cc, balance, fltrS) + refundAmount := float64(increment.Duration.Nanoseconds()) * increment.BalanceInfo.Unit.Factor + balance.AddValue(refundAmount) + account.countUnits(-refundAmount, unitType, cc, balance, fltrS) } // check money too if increment.BalanceInfo.Monetary != nil && increment.BalanceInfo.Monetary.UUID != "" { diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index f9585e7c9..f6e6257f9 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -1652,13 +1652,39 @@ func TestCDRefundIncrements(t *testing.T) { } dm.SetAccount(ub) increments := Increments{ - &Increment{Cost: 2, BalanceInfo: &DebitInfo{ - Monetary: &MonetaryInfo{UUID: "moneya"}, AccountID: ub.ID}}, - &Increment{Cost: 2, Duration: 3 * time.Second, BalanceInfo: &DebitInfo{ - Unit: &UnitInfo{UUID: "minutea"}, - Monetary: &MonetaryInfo{UUID: "moneya"}, AccountID: ub.ID}}, - &Increment{Duration: 4 * time.Second, BalanceInfo: &DebitInfo{ - Unit: &UnitInfo{UUID: "minuteb"}, AccountID: ub.ID}}, + &Increment{ + Cost: 2, + BalanceInfo: &DebitInfo{ + Monetary: &MonetaryInfo{ + UUID: "moneya", + }, + AccountID: ub.ID, + }, + }, + &Increment{ + Cost: 2, + Duration: 3 * time.Second, + BalanceInfo: &DebitInfo{ + Unit: &UnitInfo{ + UUID: "minutea", + Factor: 1, + }, + Monetary: &MonetaryInfo{ + UUID: "moneya", + }, + AccountID: ub.ID, + }, + }, + &Increment{ + Duration: 4 * time.Second, + BalanceInfo: &DebitInfo{ + Unit: &UnitInfo{ + UUID: "minuteb", + Factor: 1, + }, + AccountID: ub.ID, + }, + }, } cd := &CallDescriptor{ToR: utils.MetaVoice, Increments: increments} cd.RefundIncrements(nil) diff --git a/engine/eventcost.go b/engine/eventcost.go index e80860e58..cca9fefc9 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -81,6 +81,7 @@ func (ec *EventCost) newChargingIncrement(incr *Increment, rf RatingMatchedFilte cIt = &ChargingIncrement{ Usage: incr.Duration, Cost: incr.Cost, + BalanceFactor: 1, CompressFactor: incr.CompressFactor, } if incr.BalanceInfo == nil { @@ -92,6 +93,7 @@ func (ec *EventCost) newChargingIncrement(incr *Increment, rf RatingMatchedFilte rateID := utils.MetaRounding //AccountingID if incr.BalanceInfo.Unit != nil { + cIt.BalanceFactor = incr.BalanceInfo.Unit.Factor // 2 balances work-around ecUUID := utils.MetaNone // populate no matter what due to Unit not nil if incr.BalanceInfo.Monetary != nil { @@ -374,7 +376,10 @@ func (ec *EventCost) AsRefundIncrements(tor string) (cd *CallDescriptor) { if blncSmry.Type == utils.MetaMonetary { cd.Increments[iIdx].BalanceInfo.Monetary = &MonetaryInfo{UUID: blncSmry.UUID} } else if utils.NonMonetaryBalances.Has(blncSmry.Type) { - cd.Increments[iIdx].BalanceInfo.Unit = &UnitInfo{UUID: blncSmry.UUID} + cd.Increments[iIdx].BalanceInfo.Unit = &UnitInfo{ + UUID: blncSmry.UUID, + Factor: cIcrm.BalanceFactor, + } } if ec.Accounting[cIcrm.AccountingID].ExtraChargeID == utils.MetaNone || ec.Accounting[cIcrm.AccountingID].ExtraChargeID == utils.EmptyString { @@ -387,7 +392,10 @@ func (ec *EventCost) AsRefundIncrements(tor string) (cd *CallDescriptor) { if extraSmry.Type == utils.MetaMonetary { cd.Increments[iIdx].BalanceInfo.Monetary = &MonetaryInfo{UUID: extraSmry.UUID} } else if utils.NonMonetaryBalances.Has(blncSmry.Type) { - cd.Increments[iIdx].BalanceInfo.Unit = &UnitInfo{UUID: extraSmry.UUID} + cd.Increments[iIdx].BalanceInfo.Unit = &UnitInfo{ + UUID: extraSmry.UUID, + Factor: cIcrm.BalanceFactor, + } } } iIdx++ @@ -478,7 +486,11 @@ func (ec *EventCost) newIntervalFromCharge(cInc *ChargingIncrement) (incr *Incre if cBC.ExtraChargeID != "" { // have both monetary and data // Work around, enforce logic with 2 balances for *voice/*monetary combination // so we can stay compatible with CallCost - incr.BalanceInfo.Unit = &UnitInfo{UUID: cBC.BalanceUUID, Consumed: cBC.Units} + incr.BalanceInfo.Unit = &UnitInfo{ + UUID: cBC.BalanceUUID, + Consumed: cBC.Units, + Factor: cInc.BalanceFactor, + } incr.BalanceInfo.Unit.RateInterval = ec.rateIntervalForRatingID(cBC.RatingID) if cBC.ExtraChargeID != utils.MetaNone { cBC = ec.Accounting[cBC.ExtraChargeID] // overwrite original balance so we can process it in one place diff --git a/engine/eventcost_test.go b/engine/eventcost_test.go index 4e9f518a8..df563032d 100644 --- a/engine/eventcost_test.go +++ b/engine/eventcost_test.go @@ -520,22 +520,26 @@ func TestNewEventCostFromCallCost(t *testing.T) { { Usage: 0, Cost: 0.1, + BalanceFactor: 1, AccountingID: "44e97dec-8a7e-43d0-8b0a-736d46b5613e", CompressFactor: 1, }, { Usage: time.Second, Cost: 0, + BalanceFactor: 1, AccountingID: "a555cde8-4bd0-408a-afbc-c3ba64888927", CompressFactor: 30, }, { Usage: time.Second, Cost: 0.005, + BalanceFactor: 1, AccountingID: "906bfd0f-035c-40a3-93a8-46f71627983e", CompressFactor: 30, }, { + BalanceFactor: 1, Cost: -0.1, AccountingID: "44e97dec-8a7e-43d0-8b0a-e34a152", CompressFactor: 1, @@ -4141,6 +4145,7 @@ func TestECnewChargingIncrementMissingBalanceInfo(t *testing.T) { Usage: incr.Duration, Cost: incr.Cost, CompressFactor: incr.CompressFactor, + BalanceFactor: 1, } rcv := ec.newChargingIncrement(incr, rf, false, false) @@ -4190,6 +4195,7 @@ func TestECnewChargingIncrementNoUnitInfo(t *testing.T) { Cost: incr.Cost, CompressFactor: incr.CompressFactor, AccountingID: utils.MetaPause, + BalanceFactor: 1, } expEC := &EventCost{ Accounting: Accounting{ diff --git a/engine/libeventcost.go b/engine/libeventcost.go index 77544200f..69e978d10 100644 --- a/engine/libeventcost.go +++ b/engine/libeventcost.go @@ -128,6 +128,7 @@ func (cIl *ChargingInterval) Clone() (cln *ChargingInterval) { type ChargingIncrement struct { Usage time.Duration Cost float64 + BalanceFactor float64 AccountingID string CompressFactor int } @@ -135,6 +136,7 @@ type ChargingIncrement struct { // Equals returns if the structure has the same value func (cIt *ChargingIncrement) Equals(oCIt *ChargingIncrement) bool { return cIt.Usage == oCIt.Usage && + cIt.BalanceFactor == oCIt.BalanceFactor && cIt.Cost == oCIt.Cost && cIt.AccountingID == oCIt.AccountingID && cIt.CompressFactor == oCIt.CompressFactor @@ -143,6 +145,7 @@ func (cIt *ChargingIncrement) Equals(oCIt *ChargingIncrement) bool { // PartiallyEquals ignores the CompressFactor when comparing func (cIt *ChargingIncrement) PartiallyEquals(oCIt *ChargingIncrement) bool { return cIt.Usage == oCIt.Usage && + cIt.BalanceFactor == oCIt.BalanceFactor && cIt.Cost == oCIt.Cost && cIt.AccountingID == oCIt.AccountingID } @@ -161,7 +164,7 @@ func (cIt *ChargingIncrement) TotalUsage() time.Duration { // TotalCost returns the cost of the increment func (cIt *ChargingIncrement) TotalCost() float64 { - return cIt.Cost * float64(cIt.CompressFactor) + return cIt.Cost * cIt.BalanceFactor * float64(cIt.CompressFactor) } // FieldAsInterface func to help EventCost FieldAsInterface @@ -176,6 +179,8 @@ func (cIt *ChargingIncrement) FieldAsInterface(fldPath []string) (val any, err e return cIt.Usage, nil case utils.Cost: return cIt.Cost, nil + case utils.BalanceFactor: + return cIt.BalanceFactor, nil case utils.AccountingID: return cIt.AccountingID, nil case utils.CompressFactor: @@ -636,6 +641,7 @@ func NewFreeEventCost(cgrID, runID, account string, tStart time.Time, usage time Increments: []*ChargingIncrement{ { Usage: usage, + BalanceFactor: 1, AccountingID: utils.MetaPause, CompressFactor: 1, }, diff --git a/engine/libeventcost_test.go b/engine/libeventcost_test.go index 2b13278cd..9bbf8878a 100644 --- a/engine/libeventcost_test.go +++ b/engine/libeventcost_test.go @@ -404,6 +404,7 @@ func TestChargingIncrementTotalCost(t *testing.T) { ch1 := &ChargingIncrement{ AccountingID: "Acc1", CompressFactor: 2, + BalanceFactor: 1, Cost: 2.345, Usage: 2 * time.Second, } diff --git a/engine/timespans.go b/engine/timespans.go index e25b10526..a95618401 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -108,6 +108,7 @@ type UnitInfo struct { Value float64 DestinationID string Consumed float64 + Factor float64 Category string ToR string RateInterval *RateInterval @@ -128,6 +129,7 @@ func (ui *UnitInfo) Equal(other *UnitInfo) bool { return ui.UUID == other.UUID && ui.DestinationID == other.DestinationID && ui.Consumed == other.Consumed && + ui.Factor == other.Factor && ui.ToR == other.ToR && ui.Category == other.Category && reflect.DeepEqual(ui.RateInterval, other.RateInterval) diff --git a/utils/consts.go b/utils/consts.go index 8ced5bc78..3f5b8e7ea 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -598,6 +598,7 @@ const ( TimingID = "TimingID" RatesID = "RatesID" RatingFiltersID = "RatingFiltersID" + BalanceFactor = "BalanceFactor" AccountingID = "AccountingID" MetaSessionS = "*sessions" MetaDefault = "*default"