From 6cd7edd8a2110e0ff2124d9cc3a3d0c4ae4a0f1f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 2 Oct 2013 20:09:42 +0300 Subject: [PATCH] refound money --- engine/balances.go | 13 ++++++-- engine/calldesc.go | 9 ++++++ engine/loader_csv.go | 2 +- engine/loader_csv_test.go | 4 +-- engine/responder.go | 16 ++++++++++ engine/timespans.go | 14 +++++++-- engine/userbalance.go | 29 ++++++++++++++--- engine/userbalance_test.go | 50 +++++++++++++++--------------- sessionmanager/fssessionmanager.go | 32 ++++++++++--------- 9 files changed, 118 insertions(+), 51 deletions(-) diff --git a/engine/balances.go b/engine/balances.go index 4065de810..0c7ea1c50 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -26,7 +26,7 @@ import ( // Can hold different units as seconds or monetary type Balance struct { - Id string + Uuid string Value float64 ExpirationDate time.Time Weight float64 @@ -51,7 +51,7 @@ func (b *Balance) IsExpired() bool { func (b *Balance) Clone() *Balance { return &Balance{ - Id: b.Id, + Uuid: b.Uuid, Value: b.Value, DestinationId: b.DestinationId, ExpirationDate: b.ExpirationDate, @@ -138,3 +138,12 @@ func (bc BalanceChain) Clone() BalanceChain { } return newChain } + +func (bc BalanceChain) GetBalance(uuid string) *Balance { + for _, balance := range bc { + if balance.Uuid == uuid { + return balance + } + } + return nil +} diff --git a/engine/calldesc.go b/engine/calldesc.go index 728f1d454..a5ab3c594 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -108,6 +108,7 @@ type CallDescriptor struct { Amount float64 FallbackSubject string // the subject to check for destination if not found on primary subject RatingPlans []*RatingPlan + Increments Increments userBalance *UserBalance } @@ -408,6 +409,14 @@ func (cd *CallDescriptor) MaxDebit(startTime time.Time) (cc *CallCost, err error return cd.Debit() } +func (cd *CallDescriptor) RefoundIncrements() (left float64, err error) { + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + defer storageGetter.SetUserBalance(userBalance) + userBalance.refoundIncrements(cd.Increments, true) + } + return 0.0, err +} + /* Interface method used to add/substract an amount of cents from user's money balance. The amount filed has to be filled in call descriptor. diff --git a/engine/loader_csv.go b/engine/loader_csv.go index b38bd96a4..2168661b2 100644 --- a/engine/loader_csv.go +++ b/engine/loader_csv.go @@ -381,7 +381,7 @@ func (csvr *CSVReader) LoadActions() (err error) { Weight: weight, ExpirationString: record[5], Balance: &Balance{ - Id: utils.GenUUID(), + Uuid: utils.GenUUID(), Value: units, Weight: minutesWeight, SpecialPrice: value, diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 158c48c84..f2c6beacb 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -552,7 +552,7 @@ func TestLoadActions(t *testing.T) { ExpirationString: UNLIMITED, Weight: 10, Balance: &Balance{ - Id: as[0].Balance.Id, + Uuid: as[0].Balance.Uuid, Value: 10, Weight: 10, }, @@ -565,7 +565,7 @@ func TestLoadActions(t *testing.T) { ExpirationString: UNLIMITED, Weight: 10, Balance: &Balance{ - Id: as[1].Balance.Id, + Uuid: as[1].Balance.Uuid, Value: 100, Weight: 10, SpecialPriceType: PRICE_ABSOLUTE, diff --git a/engine/responder.go b/engine/responder.go index 631194729..2a164d66b 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -76,6 +76,18 @@ func (rs *Responder) MaxDebit(arg CallDescriptor, reply *CallCost) (err error) { return } +func (rs *Responder) RefoundIncrements(arg CallDescriptor, reply *float64) (err error) { + if rs.Bal != nil { + *reply, err = rs.callMethod(&arg, "Responder.RefoundIncrements") + } else { + r, e := AccLock.Guard(arg.GetUserBalanceKey(), func() (float64, error) { + return arg.RefoundIncrements() + }) + *reply, err = r, e + } + return +} + func (rs *Responder) DebitCents(arg CallDescriptor, reply *float64) (err error) { if rs.Bal != nil { *reply, err = rs.callMethod(&arg, "Responder.DebitCents") @@ -340,6 +352,7 @@ type Connector interface { GetCost(CallDescriptor, *CallCost) error Debit(CallDescriptor, *CallCost) error MaxDebit(CallDescriptor, *CallCost) error + RefoundIncrements(Increments, *float64) error DebitCents(CallDescriptor, *float64) error DebitSeconds(CallDescriptor, *float64) error GetMaxSessionTime(CallDescriptor, *float64) error @@ -360,6 +373,9 @@ func (rcc *RPCClientConnector) Debit(cd CallDescriptor, cc *CallCost) error { func (rcc *RPCClientConnector) MaxDebit(cd CallDescriptor, cc *CallCost) error { return rcc.Client.Call("Responder.MaxDebit", cd, cc) } +func (rcc *RPCClientConnector) RefoundIncrements(cd CallDescriptor, resp *float64) error { + return rcc.Client.Call("Responder.RefoundIncrements", cd, resp) +} func (rcc *RPCClientConnector) DebitCents(cd CallDescriptor, resp *float64) error { return rcc.Client.Call("Responder.DebitCents", cd, resp) } diff --git a/engine/timespans.go b/engine/timespans.go index 904de5b9c..2d47da876 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -33,13 +33,13 @@ type TimeSpan struct { RateInterval *RateInterval CallDuration time.Duration // the call duration so far till TimeEnd overlapped bool // mark a timespan as overlapped by an expanded one - Increments []*Increment + Increments Increments } type Increment struct { Duration time.Duration Cost float64 - BalanceId string + BalanceUuid string BalanceType string BalanceRateInterval *RateInterval MinuteInfo *MinuteInfo @@ -52,6 +52,16 @@ type MinuteInfo struct { Price float64 } +type Increments []*Increment + +func (incs Increments) GetTotalCost() float64 { + cost := 0.0 + for _, increment := range incs { + cost += increment.Cost + } + return cost +} + // Returns the duration of the timespan func (ts *TimeSpan) GetDuration() time.Duration { return ts.TimeEnd.Sub(ts.TimeStart) diff --git a/engine/userbalance.go b/engine/userbalance.go index 0d33201f8..d4221a5ee 100644 --- a/engine/userbalance.go +++ b/engine/userbalance.go @@ -108,8 +108,8 @@ func (ub *UserBalance) debitBalanceAction(a *Action) error { if a == nil { return errors.New("nil minute action!") } - if a.Balance.Id == "" { - a.Balance.Id = utils.GenUUID() + if a.Balance.Uuid == "" { + a.Balance.Uuid = utils.GenUUID() } if ub.BalanceMap == nil { ub.BalanceMap = make(map[string]BalanceChain, 0) @@ -204,7 +204,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { amount := increment.Duration.Seconds() if b.Value >= amount { b.Value -= amount - increment.BalanceId = b.Id + increment.BalanceUuid = b.Uuid increment.MinuteInfo = &MinuteInfo{b.DestinationId, amount, 0} paid = true if count { @@ -259,7 +259,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { } cc.Timespans = newTimespans b.Value -= amount - newTs.Increments[0].BalanceId = b.Id + newTs.Increments[0].BalanceUuid = b.Uuid newTs.Increments[0].MinuteInfo = &MinuteInfo{b.DestinationId, amount, 0} paid = true if count { @@ -309,7 +309,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { amount := increment.Cost if b.Value >= amount { b.Value -= amount - increment.BalanceId = b.Id + increment.BalanceUuid = b.Uuid paid = true if count { ub.countUnits(&Action{BalanceId: CREDIT, Direction: cc.Direction, Balance: &Balance{Value: amount, DestinationId: cc.Destination}}) @@ -337,6 +337,25 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error { return nil } +func (ub *UserBalance) refoundIncrements(increments Increments, count bool) { + for _, increment := range increments { + var balance *Balance + for _, balanceChain := range ub.BalanceMap { + if balance = balanceChain.GetBalance(increment.BalanceUuid); balance != nil { + break + } + } + if balance != nil { + balance.Value += increment.Cost + if count { + ub.countUnits(&Action{BalanceId: increment.BalanceType, Direction: OUTBOUND, Balance: &Balance{Value: increment.Cost}}) + } + } else { + // TODO: where should put the money? + } + } +} + /* Debits some amount of user's specified balance. Returns the remaining credit in user's balance. */ diff --git a/engine/userbalance_test.go b/engine/userbalance_test.go index 421e10040..71dcea440 100644 --- a/engine/userbalance_test.go +++ b/engine/userbalance_test.go @@ -46,7 +46,7 @@ func populateTestActionsForTriggers() { } func TestBalanceStoreRestore(t *testing.T) { - b := &Balance{Value: 14, Weight: 1, Id: "test", ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)} + b := &Balance{Value: 14, Weight: 1, Uuid: "test", ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)} marsh := NewCodecMsgpackMarshaler() output, err := marsh.Marshal(b) if err != nil { @@ -188,7 +188,7 @@ func TestDebitNegativeMoneyBalance(t *testing.T) { */ func TestDebitCreditZeroSecond(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -206,7 +206,7 @@ func TestDebitCreditZeroSecond(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" { + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } if rifsBalance.BalanceMap[MINUTES+OUTBOUND][0].Value != 0 || @@ -216,7 +216,7 @@ func TestDebitCreditZeroSecond(t *testing.T) { } func TestDebitCreditZeroMinute(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -237,7 +237,7 @@ func TestDebitCreditZeroMinute(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -248,8 +248,8 @@ func TestDebitCreditZeroMinute(t *testing.T) { } } func TestDebitCreditZeroMixedMinute(t *testing.T) { - b1 := &Balance{Id: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Id: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testm", Value: 70, Weight: 5, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b2 := &Balance{Uuid: "tests", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -270,8 +270,8 @@ func TestDebitCreditZeroMixedMinute(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "tests" || - cc.Timespans[1].Increments[0].BalanceId != "testm" { + if cc.Timespans[0].Increments[0].BalanceUuid != "tests" || + cc.Timespans[1].Increments[0].BalanceUuid != "testm" { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0], cc.Timespans[1].Increments[0]) } if rifsBalance.BalanceMap[MINUTES+OUTBOUND][1].Value != 0 || @@ -282,7 +282,7 @@ func TestDebitCreditZeroMixedMinute(t *testing.T) { } func TestDebitCreditNoCredit(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -308,7 +308,7 @@ func TestDebitCreditNoCredit(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -322,7 +322,7 @@ func TestDebitCreditNoCredit(t *testing.T) { } func TestDebitCreditHasCredit(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -343,13 +343,13 @@ func TestDebitCreditHasCredit(t *testing.T) { } rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "moneya", Value: 50}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -364,7 +364,7 @@ func TestDebitCreditHasCredit(t *testing.T) { } func TestDebitCreditSplitMinutesMoney(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 10, Weight: 10, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -379,13 +379,13 @@ func TestDebitCreditSplitMinutesMoney(t *testing.T) { } rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ MINUTES + OUTBOUND: BalanceChain{b1}, - CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "moneya", Value: 50}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "moneya", Value: 50}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != 10*time.Second { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -400,7 +400,7 @@ func TestDebitCreditSplitMinutesMoney(t *testing.T) { } func TestDebitCreditMoreTimespans(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 150, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -426,7 +426,7 @@ func TestDebitCreditMoreTimespans(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -437,8 +437,8 @@ func TestDebitCreditMoreTimespans(t *testing.T) { } func TestDebitCreditMoreTimespansMixed(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} - b2 := &Balance{Id: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: ZEROSECOND} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b2 := &Balance{Uuid: "testa", Value: 150, Weight: 5, DestinationId: "NAT", RateSubject: ZEROSECOND} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -464,7 +464,7 @@ func TestDebitCreditMoreTimespansMixed(t *testing.T) { if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "testb" || + if cc.Timespans[0].Increments[0].BalanceUuid != "testb" || cc.Timespans[0].Increments[0].Duration != time.Minute { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } @@ -476,7 +476,7 @@ func TestDebitCreditMoreTimespansMixed(t *testing.T) { } func TestDebitCreditNoConectFeeCredit(t *testing.T) { - b1 := &Balance{Id: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} + b1 := &Balance{Uuid: "testb", Value: 70, Weight: 10, DestinationId: "NAT", RateSubject: ZEROMINUTE} cc := &CallCost{ Direction: OUTBOUND, Destination: "0723045326", @@ -529,14 +529,14 @@ func TestDebitCreditMoneyOnly(t *testing.T) { }, } rifsBalance := &UserBalance{Id: "other", BalanceMap: map[string]BalanceChain{ - CREDIT + OUTBOUND: BalanceChain{&Balance{Id: "money", Value: 50}}, + CREDIT + OUTBOUND: BalanceChain{&Balance{Uuid: "money", Value: 50}}, }} err := rifsBalance.debitCreditBalance(cc, false) if err != nil { t.Error("Error debiting balance: ", err) } - if cc.Timespans[0].Increments[0].BalanceId != "money" || + if cc.Timespans[0].Increments[0].BalanceUuid != "money" || cc.Timespans[0].Increments[0].Duration != 10*time.Second { t.Error("Error setting balance id to increment: ", cc.Timespans[0].Increments[0]) } diff --git a/sessionmanager/fssessionmanager.go b/sessionmanager/fssessionmanager.go index 1e22acfd1..da76ec7d9 100644 --- a/sessionmanager/fssessionmanager.go +++ b/sessionmanager/fssessionmanager.go @@ -245,30 +245,34 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { hangupTime = time.Now() } end := lastCC.Timespans[len(lastCC.Timespans)-1].TimeEnd - refoundDuration := end.Sub(hangupTime).Seconds() - cost := 0.0 + refoundDuration := end.Sub(hangupTime) + var refoundIncrements []*engine.Increment engine.Logger.Info(fmt.Sprintf("Refund duration: %v", refoundDuration)) for i := len(lastCC.Timespans) - 1; i >= 0; i-- { ts := lastCC.Timespans[i] - tsDuration := ts.GetDuration().Seconds() + tsDuration := ts.GetDuration() if refoundDuration <= tsDuration { - // find procentage - procentage := (refoundDuration * 100) / tsDuration - tmpCost := (procentage * ts.Cost) / 100 - ts.Cost -= tmpCost - cost += tmpCost - // set the end time to now - ts.TimeEnd = hangupTime + lastRefoundedIncrementIndex := 0 + var lastRefoundedIncrement *engine.Increment + for incrementIndex, increment := range ts.Increments { + if increment.Duration <= refoundDuration { + refoundIncrements = append(refoundIncrements, increment) + refoundDuration -= increment.Duration + lastRefoundedIncrementIndex = incrementIndex + lastRefoundedIncrement = increment + } + } + ts.SplitByIncrement(lastRefoundedIncrementIndex, lastRefoundedIncrement) break // do not go to other timespans } else { - cost += ts.Cost - // remove the timestamp entirely + refoundIncrements = append(refoundIncrements, ts.Increments...) + // remove the timespan entirely lastCC.Timespans = lastCC.Timespans[:i] // continue to the next timespan with what is left to refound refoundDuration -= tsDuration } } - if cost > 0 { + if len(refoundIncrements) > 0 { cd := &engine.CallDescriptor{ Direction: lastCC.Direction, Tenant: lastCC.Tenant, @@ -276,7 +280,7 @@ func (sm *FSSessionManager) OnChannelHangupComplete(ev Event) { Subject: lastCC.Subject, Account: lastCC.Account, Destination: lastCC.Destination, - Amount: -cost, + Increments: refoundIncrements, // FallbackSubject: lastCC.FallbackSubject, // TODO: check how to best add it } var response float64