mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
new subject for monetary balances
This commit is contained in:
@@ -52,6 +52,17 @@ type MinuteInfo struct {
|
||||
Price float64
|
||||
}
|
||||
|
||||
func (incr *Increment) Clone() *Increment {
|
||||
return &Increment{
|
||||
Duration: incr.Duration,
|
||||
Cost: incr.Cost,
|
||||
BalanceUuid: incr.BalanceUuid,
|
||||
BalanceType: incr.BalanceType,
|
||||
BalanceRateInterval: incr.BalanceRateInterval,
|
||||
MinuteInfo: incr.MinuteInfo,
|
||||
}
|
||||
}
|
||||
|
||||
type Increments []*Increment
|
||||
|
||||
func (incs Increments) GetTotalCost() float64 {
|
||||
@@ -184,13 +195,47 @@ func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) {
|
||||
return
|
||||
}
|
||||
|
||||
// Split the interval at the given increment start
|
||||
func (ts *TimeSpan) SplitByIncrement(index int, increment *Increment) *TimeSpan {
|
||||
timeStart := ts.GetTimeStartForIncrement(index, increment)
|
||||
// Split the timespan at the given increment start
|
||||
func (ts *TimeSpan) SplitByIncrement(index int) *TimeSpan {
|
||||
if index <= 0 || index >= len(ts.Increments) {
|
||||
return nil
|
||||
}
|
||||
timeStart := ts.GetTimeStartForIncrement(index)
|
||||
newTs := &TimeSpan{RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd}
|
||||
newTs.CallDuration = ts.CallDuration
|
||||
ts.TimeEnd = timeStart
|
||||
ts.Increments = ts.Increments[0:index]
|
||||
newTs.Increments = ts.Increments[index:]
|
||||
ts.Increments = ts.Increments[:index]
|
||||
ts.SetNewCallDuration(newTs)
|
||||
return newTs
|
||||
}
|
||||
|
||||
// Split the timespan at the given second
|
||||
func (ts *TimeSpan) SplitByDuration(duration time.Duration) *TimeSpan {
|
||||
if duration <= 0 || duration >= ts.GetDuration() {
|
||||
return nil
|
||||
}
|
||||
timeStart := ts.TimeStart.Add(duration)
|
||||
newTs := &TimeSpan{RateInterval: ts.RateInterval, TimeStart: timeStart, TimeEnd: ts.TimeEnd}
|
||||
newTs.CallDuration = ts.CallDuration
|
||||
ts.TimeEnd = timeStart
|
||||
// split the increment
|
||||
for incrIndex, incr := range ts.Increments {
|
||||
if duration-incr.Duration >= 0 {
|
||||
duration -= incr.Duration
|
||||
} else {
|
||||
|
||||
splitIncrement := ts.Increments[incrIndex].Clone()
|
||||
splitIncrement.Duration -= duration
|
||||
ts.Increments[incrIndex].Duration = duration
|
||||
newTs.Increments = Increments{splitIncrement}
|
||||
if incrIndex < len(ts.Increments)-1 {
|
||||
newTs.Increments = append(newTs.Increments, ts.Increments[incrIndex+1:]...)
|
||||
}
|
||||
ts.Increments = ts.Increments[:incrIndex+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
ts.SetNewCallDuration(newTs)
|
||||
return newTs
|
||||
}
|
||||
@@ -230,8 +275,8 @@ func (ts *TimeSpan) SetNewCallDuration(nts *TimeSpan) {
|
||||
}
|
||||
|
||||
// returns a time for the specified second in the time span
|
||||
func (ts *TimeSpan) GetTimeStartForIncrement(index int, increment *Increment) time.Time {
|
||||
return ts.TimeStart.Add(time.Duration(int64(index) * increment.Duration.Nanoseconds()))
|
||||
func (ts *TimeSpan) GetTimeStartForIncrement(index int) time.Time {
|
||||
return ts.TimeStart.Add(time.Duration(int64(index) * ts.Increments[0].Duration.Nanoseconds()))
|
||||
}
|
||||
|
||||
func (ts *TimeSpan) RoundToDuration(duration time.Duration) {
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package engine
|
||||
|
||||
import (
|
||||
//"github.com/cgrates/cgrates/utils"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -450,7 +450,6 @@ func TestTimespanExpandingPastEnd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestTimespanExpandingCallDuration(t *testing.T) {
|
||||
timespans := []*TimeSpan{
|
||||
&TimeSpan{
|
||||
@@ -468,11 +467,11 @@ func TestTimespanExpandingCallDuration(t *testing.T) {
|
||||
cd := &CallDescriptor{}
|
||||
timespans = cd.roundTimeSpansToIncrement(timespans)
|
||||
|
||||
if timespans[0].CallDuration != time.Minute {
|
||||
if len(timespans) != 1 || timespans[0].GetDuration() != time.Minute {
|
||||
t.Error("Error setting call duration: ", timespans[0])
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestTimespanExpandingRoundingPastEnd(t *testing.T) {
|
||||
timespans := []*TimeSpan{
|
||||
&TimeSpan{
|
||||
@@ -626,8 +625,7 @@ func TestTimespanCreateSecondsSlice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestTimespanCreateSecondsFract(t *testing.T) {
|
||||
func TestTimespanCreateIncrements(t *testing.T) {
|
||||
ts := &TimeSpan{
|
||||
TimeStart: time.Date(2013, 9, 10, 14, 30, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2013, 9, 10, 14, 30, 30, 100000000, time.UTC),
|
||||
@@ -635,15 +633,18 @@ func TestTimespanCreateSecondsFract(t *testing.T) {
|
||||
RoundingMethod: utils.ROUNDING_MIDDLE,
|
||||
RoundingDecimals: 2,
|
||||
Rates: RateGroups{
|
||||
&Rate{Value: 2.0},
|
||||
&Rate{
|
||||
Value: 2.0,
|
||||
RateIncrement: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ts.createIncrementsSlice()
|
||||
if len(ts.Increments) != 31 {
|
||||
t.Error("Error creating second slice: ", ts.Increments)
|
||||
if len(ts.Increments) != 3 {
|
||||
t.Error("Error creating increment slice: ", len(ts.Increments))
|
||||
}
|
||||
if len(ts.Increments) < 31 || ts.Increments[30].Cost != 0.2 {
|
||||
if len(ts.Increments) < 3 || ts.Increments[2].Cost != 20 {
|
||||
t.Error("Wrong second slice: ", ts.Increments)
|
||||
}
|
||||
}
|
||||
@@ -651,16 +652,130 @@ func TestTimespanCreateSecondsFract(t *testing.T) {
|
||||
func TestTimespanSplitByIncrement(t *testing.T) {
|
||||
ts := &TimeSpan{
|
||||
TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2013, 9, 19, 18, 30, 30, 0, time.UTC),
|
||||
CallDuration: 50 * time.Second,
|
||||
TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC),
|
||||
CallDuration: 60 * time.Second,
|
||||
RateInterval: &RateInterval{
|
||||
RoundingMethod: utils.ROUNDING_MIDDLE,
|
||||
RoundingDecimals: 2,
|
||||
Rates: RateGroups{
|
||||
&Rate{
|
||||
Value: 2.0,
|
||||
RateIncrement: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
i := &Increment{Duration: time.Second}
|
||||
newTs := ts.SplitByIncrement(5, i)
|
||||
if ts.GetDuration() != 5*time.Second || newTs.GetDuration() != 25*time.Second {
|
||||
t.Error("Error spliting by second: ", ts.GetDuration(), newTs.GetDuration())
|
||||
ts.createIncrementsSlice()
|
||||
if len(ts.Increments) != 6 {
|
||||
t.Error("Error creating increment slice: ", len(ts.Increments))
|
||||
}
|
||||
if ts.CallDuration != 25*time.Second || newTs.CallDuration != 50*time.Second {
|
||||
t.Error("Error spliting by second at setting call duration: ", ts.GetDuration(), newTs.GetDuration())
|
||||
newTs := ts.SplitByIncrement(5)
|
||||
if ts.GetDuration() != 50*time.Second || newTs.GetDuration() != 10*time.Second {
|
||||
t.Error("Error spliting by increment: ", ts.GetDuration(), newTs.GetDuration())
|
||||
}
|
||||
if ts.CallDuration != 50*time.Second || newTs.CallDuration != 60*time.Second {
|
||||
t.Error("Error spliting by increment at setting call duration: ", ts.CallDuration, newTs.CallDuration)
|
||||
}
|
||||
if len(ts.Increments) != 5 || len(newTs.Increments) != 1 {
|
||||
t.Error("Error spliting increments: ", ts.Increments, newTs.Increments)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimespanSplitByIncrementStart(t *testing.T) {
|
||||
ts := &TimeSpan{
|
||||
TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC),
|
||||
CallDuration: 60 * time.Second,
|
||||
RateInterval: &RateInterval{
|
||||
RoundingMethod: utils.ROUNDING_MIDDLE,
|
||||
RoundingDecimals: 2,
|
||||
Rates: RateGroups{
|
||||
&Rate{
|
||||
Value: 2.0,
|
||||
RateIncrement: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ts.createIncrementsSlice()
|
||||
if len(ts.Increments) != 6 {
|
||||
t.Error("Error creating increment slice: ", len(ts.Increments))
|
||||
}
|
||||
newTs := ts.SplitByIncrement(0)
|
||||
if ts.GetDuration() != 60*time.Second || newTs != nil {
|
||||
t.Error("Error spliting by increment: ", ts.GetDuration())
|
||||
}
|
||||
if ts.CallDuration != 60*time.Second {
|
||||
t.Error("Error spliting by incrementat setting call duration: ", ts.CallDuration)
|
||||
}
|
||||
if len(ts.Increments) != 6 {
|
||||
t.Error("Error spliting increments: ", ts.Increments)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimespanSplitByIncrementEnd(t *testing.T) {
|
||||
ts := &TimeSpan{
|
||||
TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC),
|
||||
CallDuration: 60 * time.Second,
|
||||
RateInterval: &RateInterval{
|
||||
RoundingMethod: utils.ROUNDING_MIDDLE,
|
||||
RoundingDecimals: 2,
|
||||
Rates: RateGroups{
|
||||
&Rate{
|
||||
Value: 2.0,
|
||||
RateIncrement: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ts.createIncrementsSlice()
|
||||
if len(ts.Increments) != 6 {
|
||||
t.Error("Error creating increment slice: ", len(ts.Increments))
|
||||
}
|
||||
newTs := ts.SplitByIncrement(6)
|
||||
if ts.GetDuration() != 60*time.Second || newTs != nil {
|
||||
t.Error("Error spliting by increment: ", ts.GetDuration())
|
||||
}
|
||||
if ts.CallDuration != 60*time.Second {
|
||||
t.Error("Error spliting by increment at setting call duration: ", ts.CallDuration)
|
||||
}
|
||||
if len(ts.Increments) != 6 {
|
||||
t.Error("Error spliting increments: ", ts.Increments)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimespanSplitByDuration(t *testing.T) {
|
||||
ts := &TimeSpan{
|
||||
TimeStart: time.Date(2013, 9, 19, 18, 30, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2013, 9, 19, 18, 31, 00, 0, time.UTC),
|
||||
CallDuration: 60 * time.Second,
|
||||
RateInterval: &RateInterval{
|
||||
RoundingMethod: utils.ROUNDING_MIDDLE,
|
||||
RoundingDecimals: 2,
|
||||
Rates: RateGroups{
|
||||
&Rate{
|
||||
Value: 2.0,
|
||||
RateIncrement: 10 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ts.createIncrementsSlice()
|
||||
if len(ts.Increments) != 6 {
|
||||
t.Error("Error creating increment slice: ", len(ts.Increments))
|
||||
}
|
||||
newTs := ts.SplitByDuration(46 * time.Second)
|
||||
if ts.GetDuration() != 46*time.Second || newTs.GetDuration() != 14*time.Second {
|
||||
t.Error("Error spliting by duration: ", ts.GetDuration(), newTs.GetDuration())
|
||||
}
|
||||
if ts.CallDuration != 46*time.Second || newTs.CallDuration != 60*time.Second {
|
||||
t.Error("Error spliting by duration at setting call duration: ", ts.CallDuration, newTs.CallDuration)
|
||||
}
|
||||
if len(ts.Increments) != 5 || len(newTs.Increments) != 2 {
|
||||
t.Error("Error spliting increments: ", ts.Increments, newTs.Increments)
|
||||
}
|
||||
if ts.Increments[4].Duration != 6*time.Second || newTs.Increments[0].Duration != 4*time.Second {
|
||||
t.Error("Error spliting increment: ", ts.Increments[4], newTs.Increments[0])
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -82,7 +82,7 @@ func (ub *UserBalance) getSecondsForPrefix(cd *CallDescriptor) (seconds, credit
|
||||
continue
|
||||
}
|
||||
if cc.Cost > 0 && cc.GetDuration() > 0 {
|
||||
// TODO: fix this
|
||||
// TODO: improve this
|
||||
secondCost := cc.Cost / cc.GetDuration().Seconds()
|
||||
credit -= s * secondCost
|
||||
}
|
||||
@@ -181,9 +181,9 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
|
||||
for tsIndex := 0; tsIndex < len(cc.Timespans); tsIndex++ {
|
||||
ts := cc.Timespans[tsIndex]
|
||||
ts.createIncrementsSlice()
|
||||
tsWasSplited := false
|
||||
tsWasSplit := false
|
||||
for incrementIndex, increment := range ts.Increments {
|
||||
if tsWasSplited {
|
||||
if tsWasSplit {
|
||||
break
|
||||
}
|
||||
paid := false
|
||||
@@ -208,7 +208,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
|
||||
newTs := ts
|
||||
if incrementIndex != 0 {
|
||||
// if increment it's not at the begining we must split the timespan
|
||||
newTs = ts.SplitByIncrement(incrementIndex, increment)
|
||||
newTs = ts.SplitByIncrement(incrementIndex)
|
||||
}
|
||||
newTs.RoundToDuration(time.Minute)
|
||||
newTs.RateInterval = &RateInterval{
|
||||
@@ -236,7 +236,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
|
||||
cc.Timespans = append(cc.Timespans, nil)
|
||||
copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:])
|
||||
cc.Timespans[tsIndex] = newTs
|
||||
tsWasSplited = true
|
||||
tsWasSplit = true
|
||||
}
|
||||
|
||||
var newTimespans []*TimeSpan
|
||||
@@ -260,7 +260,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
|
||||
// newTs.SplitByIncrement()
|
||||
// get the new rate
|
||||
cd := cc.CreateCallDescriptor()
|
||||
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment)
|
||||
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex)
|
||||
cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd
|
||||
cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration
|
||||
newCC, err := b.GetCost(cd)
|
||||
@@ -269,25 +269,76 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
|
||||
continue
|
||||
}
|
||||
//debit new callcost
|
||||
var paidTs []*TimeSpan
|
||||
for _, nts := range newCC.Timespans {
|
||||
for _, nIncrement := range nts.Increments {
|
||||
paidTs = append(paidTs, nts)
|
||||
for nIdx, nInc := range nts.Increments {
|
||||
// debit minutes and money
|
||||
_ = nIncrement
|
||||
amount := nInc.Cost
|
||||
if b.Value >= amount {
|
||||
b.Value -= amount
|
||||
nInc.BalanceUuid = b.Uuid
|
||||
if count {
|
||||
ub.countUnits(&Action{BalanceId: CREDIT, Direction: newCC.Direction, Balance: &Balance{Value: amount, DestinationId: newCC.Destination}})
|
||||
}
|
||||
} else {
|
||||
nts.SplitByIncrement(nIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
// calculate overlaped timespans
|
||||
var paidDuration time.Duration
|
||||
for _, pts := range paidTs {
|
||||
paidDuration += pts.GetDuration()
|
||||
}
|
||||
if paidDuration > 0 {
|
||||
// split from current increment
|
||||
newTs := ts.SplitByIncrement(incrementIndex)
|
||||
remainingTs := []*TimeSpan{newTs}
|
||||
|
||||
for tsi := tsIndex + 1; tsi < len(cc.Timespans); tsi++ {
|
||||
remainingTs = append(remainingTs, cc.Timespans[tsi])
|
||||
}
|
||||
for remainingIndex, rts := range remainingTs {
|
||||
if paidDuration >= rts.GetDuration() {
|
||||
paidDuration -= rts.GetDuration()
|
||||
} else {
|
||||
if paidDuration > 0 {
|
||||
// this ts was not fully paid
|
||||
fragment := rts.SplitByDuration(paidDuration)
|
||||
paidTs = append(paidTs, fragment)
|
||||
}
|
||||
// delete from tsIndex to current
|
||||
cc.Timespans = append(cc.Timespans[:tsIndex], cc.Timespans[remainingIndex:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// append the timpespans to outer timespans
|
||||
for _, pts := range paidTs {
|
||||
tsIndex++
|
||||
cc.Timespans = append(cc.Timespans, nil)
|
||||
copy(cc.Timespans[tsIndex+1:], cc.Timespans[tsIndex:])
|
||||
cc.Timespans[tsIndex] = pts
|
||||
}
|
||||
paid = true
|
||||
tsWasSplit = true
|
||||
}
|
||||
}
|
||||
if paid {
|
||||
continue
|
||||
} else {
|
||||
// Split if some increments were processed by minutes
|
||||
if incrementIndex > 0 && ts.Increments[incrementIndex-1].MinuteInfo != nil {
|
||||
newTs := ts.SplitByIncrement(incrementIndex, increment)
|
||||
idx := tsIndex + 1
|
||||
cc.Timespans = append(cc.Timespans, nil)
|
||||
copy(cc.Timespans[idx+1:], cc.Timespans[idx:])
|
||||
cc.Timespans[idx] = newTs
|
||||
newTs.createIncrementsSlice()
|
||||
tsWasSplited = true
|
||||
newTs := ts.SplitByIncrement(incrementIndex)
|
||||
if newTs != nil {
|
||||
idx := tsIndex + 1
|
||||
cc.Timespans = append(cc.Timespans, nil)
|
||||
copy(cc.Timespans[idx+1:], cc.Timespans[idx:])
|
||||
cc.Timespans[idx] = newTs
|
||||
newTs.createIncrementsSlice()
|
||||
tsWasSplit = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -308,7 +359,7 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
|
||||
} else {
|
||||
// get the new rate
|
||||
cd := cc.CreateCallDescriptor()
|
||||
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex, increment)
|
||||
cd.TimeStart = ts.GetTimeStartForIncrement(incrementIndex)
|
||||
cd.TimeEnd = cc.Timespans[len(cc.Timespans)-1].TimeEnd
|
||||
cd.CallDuration = cc.Timespans[len(cc.Timespans)-1].CallDuration
|
||||
newCC, err := b.GetCost(cd)
|
||||
@@ -326,11 +377,11 @@ func (ub *UserBalance) debitCreditBalance(cc *CallCost, count bool) error {
|
||||
}
|
||||
if !paid {
|
||||
// no balance was attached to this increment: cut the rest of increments/timespans
|
||||
ts.SplitByIncrement(incrementIndex, increment)
|
||||
if len(ts.Increments) == 0 {
|
||||
// if there are no increments left in the ts leav it out
|
||||
if incrementIndex == 0 {
|
||||
// if we are right at the begining in the ts leave it out
|
||||
cc.Timespans = cc.Timespans[:tsIndex]
|
||||
} else {
|
||||
ts.SplitByIncrement(incrementIndex)
|
||||
cc.Timespans = cc.Timespans[:tsIndex+1]
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user