diff --git a/accounts/concretebalance.go b/accounts/concretebalance.go index f159dc6a0..65bb9ec78 100644 --- a/accounts/concretebalance.go +++ b/accounts/concretebalance.go @@ -19,6 +19,8 @@ along with this program. If not, see package accounts import ( + "fmt" + "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" "github.com/ericlagergren/decimal" @@ -61,10 +63,10 @@ type concreteBalance struct { } // debitAbstracts implements the balanceOperator interface -func (cB *concreteBalance) debitAbstracts(usage *decimal.Big, +func (cB *concreteBalance) debitAbstracts(aUnits *decimal.Big, cgrEv *utils.CGREvent) (ec *utils.EventCharges, err error) { evNm := cgrEv.AsDataProvider() - + fmt.Printf("debitAbstracts, aUnits: %s, ev: %+v\n", aUnits, cgrEv) // pass the general balance filters var pass bool if pass, err = cB.fltrS.Pass(cgrEv.Tenant, cB.blnCfg.FilterIDs, evNm); err != nil { @@ -79,14 +81,17 @@ func (cB *concreteBalance) debitAbstracts(usage *decimal.Big, cB.fltrS, cgrEv.Tenant, evNm); err != nil { return } - - return maxDebitAbstractsFromConcretes(usage, + fmt.Printf("costIcrm: %+v\n", costIcrm) + if ec, err = maxDebitAbstractsFromConcretes(aUnits, cB.acntID, []*concreteBalance{cB}, cB.connMgr, cgrEv, cB.attrSConns, cB.blnCfg.AttributeIDs, cB.rateSConns, cB.blnCfg.RateProfileIDs, - costIcrm) - + costIcrm); err != nil { + return + } + fmt.Printf("received ec: %s\n", utils.ToIJSON(ec)) + return } // debitConcretes implements the balanceOperator interface @@ -139,6 +144,7 @@ func (cB *concreteBalance) debitConcretes(cUnits *decimal.Big, if dbted.Cmp(decimal.New(0, 0)) == 0 { return // no event cost for 0 debit } + // EventCharges ec = utils.NewEventCharges() ec.Concretes = &utils.Decimal{dbted} // UnitFactors @@ -155,5 +161,18 @@ func (cB *concreteBalance) debitConcretes(cUnits *decimal.Big, BalanceLimit: blncLmt, UnitFactorID: ufID, } + ec.ChargingIntervals = []*utils.ChargingInterval{ + { + Increments: []*utils.ChargingIncrement{ + { + Units: &utils.Decimal{dbted}, + AccountChargeID: acntID, + CompressFactor: 1, + }, + }, + CompressFactor: 1, + }, + } + return } diff --git a/accounts/libaccounts.go b/accounts/libaccounts.go index 0dd103b0d..731405b15 100644 --- a/accounts/libaccounts.go +++ b/accounts/libaccounts.go @@ -225,9 +225,7 @@ func maxDebitAbstractsFromConcretes(aUnits *decimal.Big, connMgr *engine.ConnManager, cgrEv *utils.CGREvent, attrSConns, attributeIDs, rateSConns, rpIDs []string, costIcrm *utils.CostIncrement) (ec *utils.EventCharges, err error) { - // Init EventCharges - ec = utils.NewEventCharges() calculateCost := costIcrm.RecurrentFee.Cmp(decimal.New(-1, 0)) == 0 && costIcrm.FixedFee == nil //var attrIDs []string // will be populated if attributes are processed successfully // process AttributeS if needed @@ -301,7 +299,9 @@ func maxDebitAbstractsFromConcretes(aUnits *decimal.Big, } else { // debit for the usage succeeded aPaid = new(decimal.Big).Copy(aUnits) paidConcrtUnts = cloneUnitsFromConcretes(cncrtBlncs) + ec = utils.NewEventCharges() ec.Merge(ecDbt) + fmt.Printf("ecDbt: %s\n", utils.ToIJSON(ecDbt)) if i == 0 { // no estimation done, covering full break } @@ -323,6 +323,7 @@ func maxDebitAbstractsFromConcretes(aUnits *decimal.Big, if aPaid == nil { // since we are erroring, we restore the concerete balances aPaid = decimal.New(0, 0) + ec = utils.NewEventCharges() } ec.Abstracts = &utils.Decimal{aPaid} restoreUnitsFromClones(cncrtBlncs, paidConcrtUnts) diff --git a/utils/accountprofile.go b/utils/accountprofile.go index dc9ed4f3e..19904678f 100644 --- a/utils/accountprofile.go +++ b/utils/accountprofile.go @@ -163,6 +163,29 @@ type UnitFactor struct { Factor *Decimal } +// Equals compares two UnitFactors +func (uF *UnitFactor) Equals(nUf *UnitFactor) (eq bool) { + if uF.FilterIDs == nil && nUf.FilterIDs != nil || + uF.FilterIDs != nil && nUf.FilterIDs == nil || + len(uF.FilterIDs) != len(nUf.FilterIDs) { + return + } + for i := range uF.FilterIDs { + if uF.FilterIDs[i] != nUf.FilterIDs[i] { + return + } + } + if uF.Factor == nil && nUf.Factor != nil || + uF.Factor != nil && nUf.Factor == nil { + return + } + if uF.Factor == nil && nUf == nil { + return true + } + return uF.Factor.Compare(nUf.Factor) == 0 +} + +// TenantID returns the combined Tenant:ID func (aP *AccountProfile) TenantID() string { return ConcatenatedKey(aP.Tenant, aP.ID) } diff --git a/utils/eventcharges.go b/utils/eventcharges.go index 7be63882b..ce29f33a8 100644 --- a/utils/eventcharges.go +++ b/utils/eventcharges.go @@ -58,7 +58,74 @@ func (ec *EventCharges) Merge(eCs ...*EventCharges) { } else { // initial ec.Concretes = nEc.Concretes } + ec.AppendChargingIntervals(ec.ChargingIntervals...) + } +} +// SyncIDs will repopulate Accounting, UnitFactors and Rating IDs if they equal the references in ec +func (ec *EventCharges) SyncIDs(eCs ...*EventCharges) { + for _, nEc := range eCs { + for _, cIl := range nEc.ChargingIntervals { + for _, cIcrm := range cIl.Increments { + + nEcAcntChrg := nEc.Accounting[cIcrm.AccountChargeID] + + // UnitFactors + if nEcAcntChrg.UnitFactorID != EmptyString { + if uFctID := ec.UnitFactorID(nEc.UnitFactors[nEcAcntChrg.UnitFactorID]); uFctID != EmptyString && + uFctID != nEcAcntChrg.UnitFactorID { + nEc.UnitFactors[uFctID] = ec.UnitFactors[uFctID] + delete(nEc.UnitFactors, nEcAcntChrg.UnitFactorID) + nEcAcntChrg.UnitFactorID = uFctID + } + } + + // Rating + if nEcAcntChrg.RatingID != EmptyString { + if rtID := ec.RatingID(nEc.Rating[nEcAcntChrg.RatingID]); rtID != EmptyString && + rtID != nEcAcntChrg.RatingID { + nEc.Rating[rtID] = ec.Rating[rtID] + delete(nEc.Rating, nEcAcntChrg.RatingID) + nEcAcntChrg.RatingID = rtID + } + } + + // AccountCharges + for i, chrgID := range nEc.Accounting[cIcrm.AccountChargeID].JoinedChargeIDs { + if acntChrgID := ec.AccountChargeID(nEc.Accounting[chrgID]); acntChrgID != chrgID { + // matched the AccountChargeID with an existing one in reference ec, replace in nEc + nEc.Accounting[acntChrgID] = ec.Accounting[acntChrgID] + delete(nEc.Accounting, chrgID) + nEc.Accounting[cIcrm.AccountChargeID].JoinedChargeIDs[i] = acntChrgID + } + } + if acntChrgID := ec.AccountChargeID(nEcAcntChrg); acntChrgID != EmptyString && + acntChrgID != cIcrm.AccountChargeID { + // matched the AccountChargeID with an existing one in reference ec, replace in nEc + nEc.Accounting[acntChrgID] = ec.Accounting[cIcrm.AccountChargeID] + delete(nEc.Accounting, cIcrm.AccountChargeID) + cIcrm.AccountChargeID = acntChrgID + } + + } + } + } +} + +// AppendChargingIntervals will add new charging intervals to the existing. +// if possible, the existing last one in ec will be compressed +func (ec *EventCharges) AppendChargingIntervals(cIls ...*ChargingInterval) { + for i, cIl := range cIls { + if i == 0 && len(ec.ChargingIntervals) == 0 { + ec.ChargingIntervals = []*ChargingInterval{cIl} + continue + } + + if ec.ChargingIntervals[len(ec.ChargingIntervals)-1].CompressEquals(cIl) { + ec.ChargingIntervals[len(ec.ChargingIntervals)-1].CompressFactor += 1 + continue + } + ec.ChargingIntervals = append(ec.ChargingIntervals, cIl) } } @@ -83,6 +150,36 @@ func (ec *EventCharges) AsExtEventCharges() (eEc *ExtEventCharges, err error) { return } +// UnitFactorID returns the ID of the matching UnitFactor within ec.UnitFactors +func (ec *EventCharges) UnitFactorID(uF *UnitFactor) (ufID string) { + for ecUfID, ecUf := range ec.UnitFactors { + if ecUf.Equals(uF) { + return ecUfID + } + } + return +} + +// RatingID returns the ID of the matching RateSInterval within ec.Rating +func (ec *EventCharges) RatingID(rIl *RateSInterval) (rID string) { + for ecID, ecRtIl := range ec.Rating { + if ecRtIl.Equals(rIl) { + return ecID + } + } + return +} + +// AccountChargeID returns the ID of the matching AccountCharge within ec.Accounting +func (ec *EventCharges) AccountChargeID(ac *AccountCharge) (acID string) { + for ecID, ecAc := range ec.Accounting { + if ecAc.Equals(ac) { + return ecID + } + } + return +} + // ExtEventCharges is a generic EventCharges used in APIs type ExtEventCharges struct { Abstracts *float64 @@ -94,10 +191,15 @@ type ChargingInterval struct { CompressFactor int } +// CompressEquals compares two ChargingIntervals for aproximate equality, ignoring compress field +func (cIl *ChargingInterval) CompressEquals(nCil *ChargingInterval) (eq bool) { + return +} + // ChargingIncrement represents one unit charged inside an interval type ChargingIncrement struct { - Units *Decimal - AccountChargeID string // Account charging information + Units *Decimal // Can differ from AccountCharge due to JoinedCharging + AccountChargeID string // Account charging information CompressFactor int } @@ -112,3 +214,8 @@ type AccountCharge struct { RatingID string // identificator in cost increments JoinedChargeIDs []string // identificator of extra account charges } + +// Equals compares two AccountCharges +func (ac *AccountCharge) Equals(nAc *AccountCharge) (eq bool) { + return +} diff --git a/utils/librates.go b/utils/librates.go index 653a62ae9..4694804fa 100644 --- a/utils/librates.go +++ b/utils/librates.go @@ -140,6 +140,11 @@ type RateSIncrement struct { cost *decimal.Big // unexported total increment cost } +// Equals compares two RateSIntervals +func (rIl *RateSInterval) Equals(nRil *RateSInterval) (eq bool) { + return // ToDo +} + // RateProfileCost is the cost returned by RateS at cost queries type RateProfileCost struct { ID string // RateProfileID