func (s *Session) Refund(lastCC *engine.CallCost, hangupTime time.Time) error { end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd refundDuration := end.Sub(hangupTime) //engine.Logger.Debug(fmt.Sprintf("HANGUPTIME: %s REFUNDDURATION: %s", hangupTime.String(), refundDuration.String())) var refundIncrements engine.Increments for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] tsDuration := ts.GetDuration() if refundDuration <= tsDuration { lastRefundedIncrementIndex := -1 for j := len(ts.Increments) - 1; j >= 0; j-- { increment := ts.Increments[j] if increment.Duration <= refundDuration { refundIncrements = append(refundIncrements, increment) refundDuration -= increment.Duration lastRefundedIncrementIndex = j } else { break //increment duration is larger, cannot refund increment } } if lastRefundedIncrementIndex == 0 { lastCC.Timespans[i] = nil lastCC.Timespans = lastCC.Timespans[:i] } else { ts.SplitByIncrement(lastRefundedIncrementIndex) } break // do not go to other timespans } else { refundIncrements = append(refundIncrements, ts.Increments...) // remove the timespan entirely lastCC.Timespans[i] = nil lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refund refundDuration -= tsDuration } } // show only what was actualy refunded (stopped in timespan) // engine.Logger.Info(fmt.Sprintf("Refund duration: %v", initialRefundDuration-refundDuration)) if len(refundIncrements) > 0 { cd := &engine.CallDescriptor{ Direction: lastCC.Direction, Tenant: lastCC.Tenant, Category: lastCC.Category, Subject: lastCC.Subject, Account: lastCC.Account, Destination: lastCC.Destination, Increments: refundIncrements, } var response float64 err := s.sessionManager.Rater().RefundIncrements(cd, &response) if err != nil { return err } } //engine.Logger.Debug(fmt.Sprintf("REFUND INCR: %s", utils.ToJSON(refundIncrements))) lastCC.Cost -= refundIncrements.GetTotalCost() lastCC.Timespans.Compress() return nil }
func TestTpBalanceCounter(t *testing.T) { if !*testIntegration { return } tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC) cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Destination: "+49", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(20) * time.Second), } var cc engine.CallCost if err := tpRPC.Call("Responder.Debit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != 20*time.Second { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } var acnt *engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} if err := tpRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error("Got error on ApierV2.GetAccount: ", err.Error()) } else if acnt.UnitCounters[utils.MONETARY][1].Counters[0].Value != 20.0 { t.Errorf("Calling ApierV2.GetBalance received: %s", utils.ToIJSON(acnt)) } }
func TestTpZeroNegativeCost(t *testing.T) { tStart := time.Date(2016, 3, 31, 0, 0, 0, 0, time.UTC) cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "free", Account: "1013", Destination: "+4915", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(20) * time.Second), } var cc engine.CallCost if err := tpRPC.Call("Responder.Debit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != 20*time.Second { t.Errorf("Calling Responder.MaxDebit got callcost: %v", utils.ToIJSON(cc)) } var acnt engine.Account attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1013"} if err := tpRPC.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { t.Error("Got error on ApierV2.GetAccount: ", err.Error()) } else if acnt.BalanceMap[utils.VOICE][0].Value != 100.0 { t.Errorf("Calling ApierV2.GetAccount received: %s", utils.ToIJSON(acnt)) } }
func (apier *ApierV1) GetDataCost(attrs AttrGetDataCost, reply *engine.DataCost) error { usageAsDuration := time.Duration(attrs.Usage) * time.Second // Convert to seconds to match the loaded rates cd := &engine.CallDescriptor{ Direction: attrs.Direction, Category: attrs.Category, Tenant: attrs.Tenant, Account: attrs.Account, Subject: attrs.Subject, TimeStart: attrs.StartTime, TimeEnd: attrs.StartTime.Add(usageAsDuration), DurationIndex: usageAsDuration, TOR: utils.DATA, } var cc engine.CallCost if err := apier.Responder.GetCost(cd, &cc); err != nil { return utils.NewErrServerError(err) } if dc, err := cc.ToDataCost(); err != nil { return utils.NewErrServerError(err) } else if dc != nil { *reply = *dc } return nil }
// the debit loop method (to be stoped by sending somenthing on stopDebit channel) func (s *Session) debitLoop(runIdx int) { nextCd := *s.sessionRuns[runIdx].callDescriptor index := 0.0 debitPeriod := s.sessionManager.GetDebitPeriod() for { select { case <-s.stopDebit: return default: } if index > 0 { // first time use the session start time nextCd.TimeStart = nextCd.TimeEnd } nextCd.TimeEnd = nextCd.TimeStart.Add(debitPeriod) nextCd.LoopIndex = index nextCd.DurationIndex += debitPeriod // first presumed duration cc := new(engine.CallCost) if err := s.sessionManager.MaxDebit(&nextCd, cc); err != nil { engine.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err)) s.sessionManager.DisconnectSession(s.uuid, SYSTEM_ERROR, "") return } if cc.GetDuration() == 0 { s.sessionManager.DisconnectSession(s.uuid, INSUFFICIENT_FUNDS, nextCd.Destination) return } if cc.GetDuration() <= cfg.FSMinDurLowBalance && len(cfg.FSLowBalanceAnnFile) != 0 { if _, err := fsock.FS.SendApiCmd(fmt.Sprintf("uuid_broadcast %s %s aleg\n\n", s.uuid, cfg.FSLowBalanceAnnFile)); err != nil { engine.Logger.Err(fmt.Sprintf("<SessionManager> Could not send uuid_broadcast to freeswitch: %s", err.Error())) } } s.sessionRuns[runIdx].callCosts = append(s.sessionRuns[runIdx].callCosts, cc) nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd // update call duration with real debited duration nextCd.DurationIndex -= debitPeriod nextCd.DurationIndex += nextCd.GetDuration() time.Sleep(cc.GetDuration()) index++ } }
// Processes one time events (eg: SMS) func (smg *SMGeneric) ChargeEvent(gev SMGenericEvent) (maxUsage time.Duration, err error) { cacheKey := "ChargeEvent" + gev.GetCgrId(smg.timezone) if item, err := smg.responseCache.Get(cacheKey); err == nil && item != nil { return item.Value.(time.Duration), item.Err } defer smg.responseCache.Cache(cacheKey, &cache.CacheItem{Value: maxUsage, Err: err}) var sessionRuns []*engine.SessionRun if err = smg.rater.Call("Responder.GetSessionRuns", gev.AsStoredCdr(smg.cgrCfg, smg.timezone), &sessionRuns); err != nil { return } else if len(sessionRuns) == 0 { return } var maxDurInit bool // Avoid differences between default 0 and received 0 for _, sR := range sessionRuns { cc := new(engine.CallCost) if err = smg.rater.Call("Responder.MaxDebit", sR.CallDescriptor, cc); err != nil { utils.Logger.Err(fmt.Sprintf("<SMGeneric> Could not Debit CD: %+v, RunID: %s, error: %s", sR.CallDescriptor, sR.DerivedCharger.RunID, err.Error())) break } sR.CallCosts = append(sR.CallCosts, cc) // Save it so we can revert on issues if ccDur := cc.GetDuration(); ccDur == 0 { err = utils.ErrInsufficientCredit break } else if !maxDurInit || ccDur < maxUsage { maxUsage = ccDur } } if err != nil { // Refund the ones already taken since we have error on one of the debits for _, sR := range sessionRuns { if len(sR.CallCosts) == 0 { continue } cc := sR.CallCosts[0] if len(sR.CallCosts) > 1 { for _, ccSR := range sR.CallCosts { cc.Merge(ccSR) } } // collect increments var refundIncrements engine.Increments cc.Timespans.Decompress() for _, ts := range cc.Timespans { refundIncrements = append(refundIncrements, ts.Increments...) } // refund cc if len(refundIncrements) > 0 { cd := cc.CreateCallDescriptor() cd.Increments = refundIncrements cd.CgrID = sR.CallDescriptor.CgrID cd.RunID = sR.CallDescriptor.RunID cd.Increments.Compress() //utils.Logger.Info(fmt.Sprintf("Refunding session run callcost: %s", utils.ToJSON(cd))) var response float64 err = smg.rater.Call("Responder.RefundIncrements", cd, &response) if err != nil { return } } } return } var withErrors bool for _, sR := range sessionRuns { if len(sR.CallCosts) == 0 { continue } cc := sR.CallCosts[0] if len(sR.CallCosts) > 1 { for _, ccSR := range sR.CallCosts[1:] { cc.Merge(ccSR) } } cc.Round() roundIncrements := cc.GetRoundIncrements() if len(roundIncrements) != 0 { cd := cc.CreateCallDescriptor() cd.Increments = roundIncrements var response float64 if errRefund := smg.rater.Call("Responder.RefundRounding", cd, &response); errRefund != nil { utils.Logger.Err(fmt.Sprintf("<SM> ERROR failed to refund rounding: %v", errRefund)) } } var reply string smCost := &engine.SMCost{ CGRID: gev.GetCgrId(smg.timezone), CostSource: utils.SESSION_MANAGER_SOURCE, RunID: sR.DerivedCharger.RunID, OriginHost: gev.GetOriginatorIP(utils.META_DEFAULT), OriginID: gev.GetUUID(), CostDetails: cc, } if errStore := smg.cdrsrv.Call("CdrsV1.StoreSMCost", engine.AttrCDRSStoreSMCost{Cost: smCost, CheckDuplicate: true}, &reply); errStore != nil && !strings.HasSuffix(errStore.Error(), utils.ErrExists.Error()) { withErrors = true utils.Logger.Err(fmt.Sprintf("<SMGeneric> Could not save CC: %+v, RunID: %s error: %s", cc, sR.DerivedCharger.RunID, errStore.Error())) } } if withErrors { err = ErrPartiallyExecuted return } return }
// Check call costs func TestTutLocalMaxDebit(t *testing.T) { if !*testLocal { return } tStart := time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(20) * time.Second), } var cc engine.CallCost if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() == 20 { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1003", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(200) * time.Second), } if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.MaxDebit: ", err.Error()) } else if cc.GetDuration() == 200 { t.Errorf("Calling Responder.MaxDebit got duration: %v", cc.GetDuration()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1007", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(120) * time.Second), } if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() == 120 { t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1004", Account: "1004", Destination: "1007", DurationIndex: 0, TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(120) * time.Second), } if err := tutLocalRpc.Call("Responder.MaxDebit", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.GetDuration() != time.Duration(62)*time.Second { // We have as strategy *dsconnect t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc.GetDuration()) } var maxTime float64 if err := tutLocalRpc.Call("Responder.GetMaxSessionTime", cd, &maxTime); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if maxTime != 62000000000 { // We have as strategy *dsconnect t.Errorf("Calling Responder.GetMaxSessionTime got maxTime: %f", maxTime) } }
// Check call costs func TestTutLocalGetCosts(t *testing.T) { if !*testLocal { return } tStart, _ := utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ := utils.ParseDate("2014-08-04T13:00:20Z") cd := engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } var cc engine.CallCost if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.6 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } // Make sure that the same cost is returned via users aliasing cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: utils.USERS, Subject: utils.USERS, Account: utils.USERS, Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, ExtraFields: map[string]string{"Uuid": "388539dfd4f5cefee8f488b78c6c244b9e19138e"}, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.6 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:01:25Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1002", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.6417 { // 0.01 first minute, 0.04 25 seconds with RT_20CNT t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:00:20Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1003", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:01:25Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1003", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1.3 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:00:20Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1004", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart, _ = utils.ParseDate("2014-08-04T13:00:00Z") tEnd, _ = utils.ParseDate("2014-08-04T13:01:25Z") cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1004", DurationIndex: 0, TimeStart: tStart, TimeEnd: tEnd, } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 1.3 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } tStart = time.Date(2014, 8, 4, 13, 0, 0, 0, time.UTC) cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(50) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.5 { t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1001", Account: "1001", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(70) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.62 { t.Errorf("Calling Responder.GetCost got callcost: %v", cc.Cost) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1002", Account: "1002", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(50) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.5 { t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } cd = engine.CallDescriptor{ Direction: "*out", Category: "call", Tenant: "cgrates.org", Subject: "1002", Account: "1002", Destination: "1007", TimeStart: tStart, TimeEnd: tStart.Add(time.Duration(70) * time.Second), } if err := tutLocalRpc.Call("Responder.GetCost", cd, &cc); err != nil { t.Error("Got error on Responder.GetCost: ", err.Error()) } else if cc.Cost != 0.7 { // In case of *disconnect strategy, it will not be applied so we can go on negative costs t.Errorf("Calling Responder.GetCost got callcost: %s", cc.AsJSON()) } }
// the debit loop method (to be stoped by sending somenthing on stopDebit channel) func (s *Session) debitLoop(runIdx int) { nextCd := s.sessionRuns[runIdx].CallDescriptor nextCd.CgrID = s.eventStart.GetCgrId(s.sessionManager.Timezone()) index := 0.0 debitPeriod := s.sessionManager.DebitInterval() for { select { case <-s.stopDebit: return default: } if index > 0 { // first time use the session start time nextCd.TimeStart = nextCd.TimeEnd } nextCd.TimeEnd = nextCd.TimeStart.Add(debitPeriod) nextCd.LoopIndex = index nextCd.DurationIndex += debitPeriod // first presumed duration cc := new(engine.CallCost) if err := s.sessionManager.Rater().Call("Responder.MaxDebit", nextCd, cc); err != nil { utils.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err)) if err.Error() == utils.ErrUnauthorizedDestination.Error() { s.sessionManager.DisconnectSession(s.eventStart, s.connId, UNAUTHORIZED_DESTINATION) return } s.sessionManager.DisconnectSession(s.eventStart, s.connId, SYSTEM_ERROR) return } if cc.GetDuration() == 0 { s.sessionManager.DisconnectSession(s.eventStart, s.connId, INSUFFICIENT_FUNDS) return } if s.warnMinDur != time.Duration(0) && cc.GetDuration() <= s.warnMinDur { s.sessionManager.WarnSessionMinDuration(s.eventStart.GetUUID(), s.connId) } s.sessionRuns[runIdx].CallCosts = append(s.sessionRuns[runIdx].CallCosts, cc) nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd // update call duration with real debited duration nextCd.DurationIndex -= debitPeriod nextCd.DurationIndex += cc.GetDuration() nextCd.MaxCostSoFar += cc.Cost time.Sleep(cc.GetDuration()) index++ } }
// the debit loop method (to be stoped by sending somenthing on stopDebit channel) func (s *Session) debitLoop(runIdx int) { nextCd := s.sessionRuns[runIdx].CallDescriptor nextCd.CgrId = s.eventStart.GetCgrId("") index := 0.0 debitPeriod := s.sessionManager.DebitInterval() for { select { case <-s.stopDebit: return default: } if index > 0 { // first time use the session start time nextCd.TimeStart = nextCd.TimeEnd } nextCd.TimeEnd = nextCd.TimeStart.Add(debitPeriod) nextCd.LoopIndex = index //engine.Logger.Debug(fmt.Sprintf("NEXTCD: %s", utils.ToJSON(nextCd))) nextCd.DurationIndex += debitPeriod // first presumed duration cc := new(engine.CallCost) if err := s.sessionManager.Rater().MaxDebit(nextCd, cc); err != nil { engine.Logger.Err(fmt.Sprintf("Could not complete debit opperation: %v", err)) s.sessionManager.DisconnectSession(s.eventStart, s.connId, SYSTEM_ERROR) return } if cc.GetDuration() == 0 { s.sessionManager.DisconnectSession(s.eventStart, s.connId, INSUFFICIENT_FUNDS) return } if s.warnMinDur != time.Duration(0) && cc.GetDuration() <= s.warnMinDur { s.sessionManager.WarnSessionMinDuration(s.eventStart.GetUUID(), s.connId) } s.sessionRuns[runIdx].CallCosts = append(s.sessionRuns[runIdx].CallCosts, cc) //engine.Logger.Debug(fmt.Sprintf("CALLCOST: %s", utils.ToJSON(cc))) nextCd.TimeEnd = cc.GetEndTime() // set debited timeEnd //engine.Logger.Debug(fmt.Sprintf("NEXTCD: %s DURATION: %s", utils.ToJSON(nextCd), nextCd.GetDuration().String())) // update call duration with real debited duration nextCd.DurationIndex -= debitPeriod nextCd.DurationIndex += cc.GetDuration() nextCd.MaxCostSoFar += cc.Cost time.Sleep(cc.GetDuration()) index++ } }
func (s *Session) Refund(lastCC *engine.CallCost, hangupTime time.Time) error { end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd refundDuration := end.Sub(hangupTime) var refundIncrements engine.Increments for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] tsDuration := ts.GetDuration() if refundDuration <= tsDuration { lastRefundedIncrementIndex := -1 for j := len(ts.Increments) - 1; j >= 0; j-- { increment := ts.Increments[j] if increment.Duration <= refundDuration { refundIncrements = append(refundIncrements, increment) refundDuration -= increment.Duration lastRefundedIncrementIndex = j } else { break //increment duration is larger, cannot refund increment } } if lastRefundedIncrementIndex == 0 { lastCC.Timespans[i] = nil lastCC.Timespans = lastCC.Timespans[:i] } else { ts.SplitByIncrement(lastRefundedIncrementIndex) ts.Cost = ts.CalculateCost() } break // do not go to other timespans } else { refundIncrements = append(refundIncrements, ts.Increments...) // remove the timespan entirely lastCC.Timespans[i] = nil lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refund refundDuration -= tsDuration } } // show only what was actualy refunded (stopped in timespan) // utils.Logger.Info(fmt.Sprintf("Refund duration: %v", initialRefundDuration-refundDuration)) if len(refundIncrements) > 0 { cd := &engine.CallDescriptor{ CgrID: s.eventStart.GetCgrId(s.sessionManager.Timezone()), Direction: lastCC.Direction, Tenant: lastCC.Tenant, Category: lastCC.Category, Subject: lastCC.Subject, Account: lastCC.Account, Destination: lastCC.Destination, TOR: lastCC.TOR, Increments: refundIncrements, } cd.Increments.Compress() //utils.Logger.Info(fmt.Sprintf("Refunding duration %v with cd: %+v", refundDuration, cd)) var response float64 err := s.sessionManager.Rater().Call("Responder.RefundIncrements", cd, &response) if err != nil { return err } } lastCC.Cost -= refundIncrements.GetTotalCost() lastCC.UpdateRatedUsage() lastCC.Timespans.Compress() return nil }