diff --git a/engine/chargers.go b/engine/chargers.go index f1de985df..7c39d4299 100644 --- a/engine/chargers.go +++ b/engine/chargers.go @@ -116,3 +116,7 @@ func (cS *ChargerService) V1ProcessEvent(args *utils.CGREvent, return } + +func (cpp *ChargerProfile) TenantID() string { + return utils.ConcatenatedKey(cpp.Tenant, cpp.ID) +} diff --git a/engine/model_helpers.go b/engine/model_helpers.go index b64b8155c..8280420f0 100644 --- a/engine/model_helpers.go +++ b/engine/model_helpers.go @@ -2112,7 +2112,6 @@ func APItoModelStats(st *utils.TPStats) (mdls TpStatsS) { for _, val := range st.Metrics { if val.Parameters != "" { paramSlice = append(paramSlice, val.Parameters) - } } for i, val := range utils.StringMapFromSlice(paramSlice).Slice() { @@ -2836,3 +2835,108 @@ func APItoAttributeProfile(tpTH *utils.TPAttributeProfile, timezone string) (th } return th, nil } + +type TPChargers []*TPCharger + +func (tps TPChargers) AsTPChargers() (result []*utils.TPChargerProfile) { + mst := make(map[string]*utils.TPChargerProfile) + for _, tp := range tps { + tpCPP, found := mst[tp.ID] + if !found { + tpCPP = &utils.TPChargerProfile{ + TPid: tp.Tpid, + Tenant: tp.Tenant, + ID: tp.ID, + } + } + if tp.Weight != 0 { + tpCPP.Weight = tp.Weight + } + if len(tp.ActivationInterval) != 0 { + tpCPP.ActivationInterval = new(utils.TPActivationInterval) + aiSplt := strings.Split(tp.ActivationInterval, utils.INFIELD_SEP) + if len(aiSplt) == 2 { + tpCPP.ActivationInterval.ActivationTime = aiSplt[0] + tpCPP.ActivationInterval.ExpiryTime = aiSplt[1] + } else if len(aiSplt) == 1 { + tpCPP.ActivationInterval.ActivationTime = aiSplt[0] + } + } + if tp.FilterIDs != "" { + filterSplit := strings.Split(tp.FilterIDs, utils.INFIELD_SEP) + tpCPP.FilterIDs = append(tpCPP.FilterIDs, filterSplit...) + } + if tp.RunID != "" { + tpCPP.RunID = tp.RunID + } + if tp.AttributeIDs != "" { + attributeSplit := strings.Split(tp.AttributeIDs, utils.INFIELD_SEP) + tpCPP.AttributeIDs = append(tpCPP.AttributeIDs, attributeSplit...) + } + mst[tp.ID] = tpCPP + } + result = make([]*utils.TPChargerProfile, len(mst)) + i := 0 + for _, tp := range mst { + result[i] = tp + i++ + } + return +} + +func APItoModelTPCharger(tpCPP *utils.TPChargerProfile) (mdls TPChargers) { + if tpCPP != nil { + mdl := &TPCharger{ + Tenant: tpCPP.Tenant, + Tpid: tpCPP.TPid, + ID: tpCPP.ID, + } + mdl.Weight = tpCPP.Weight + mdl.RunID = tpCPP.RunID + for i, val := range tpCPP.AttributeIDs { + if i != 0 { + mdl.AttributeIDs += utils.INFIELD_SEP + } + mdl.AttributeIDs += val + } + for i, val := range tpCPP.FilterIDs { + if i != 0 { + mdl.FilterIDs += utils.INFIELD_SEP + } + mdl.FilterIDs += val + } + if tpCPP.ActivationInterval != nil { + if tpCPP.ActivationInterval.ActivationTime != "" { + mdl.ActivationInterval = tpCPP.ActivationInterval.ActivationTime + } + if tpCPP.ActivationInterval.ExpiryTime != "" { + mdl.ActivationInterval += utils.INFIELD_SEP + tpCPP.ActivationInterval.ExpiryTime + } + } + mdls = append(mdls, mdl) + } + return +} + +func APItoChargerProfile(tpCPP *utils.TPChargerProfile, timezone string) (cpp *ChargerProfile, err error) { + cpp = &ChargerProfile{ + Tenant: tpCPP.Tenant, + ID: tpCPP.ID, + Weight: tpCPP.Weight, + RunID: tpCPP.RunID, + FilterIDs: []string{}, + AttributeIDs: []string{}, + } + for _, fli := range tpCPP.FilterIDs { + cpp.FilterIDs = append(cpp.FilterIDs, fli) + } + for _, attribute := range tpCPP.AttributeIDs { + cpp.AttributeIDs = append(cpp.AttributeIDs, attribute) + } + if tpCPP.ActivationInterval != nil { + if cpp.ActivationInterval, err = tpCPP.ActivationInterval.AsActivationInterval(timezone); err != nil { + return nil, err + } + } + return cpp, nil +} diff --git a/engine/model_helpers_test.go b/engine/model_helpers_test.go index 58dbe6bf4..4307a8771 100644 --- a/engine/model_helpers_test.go +++ b/engine/model_helpers_test.go @@ -1329,3 +1329,100 @@ func TestModelAsTPAttribute(t *testing.T) { t.Errorf("Expecting : %+v, received: %+v", utils.ToJSON(expected), utils.ToJSON(rcv[0])) } } + +func TestAPItoChargerProfile(t *testing.T) { + tpCPP := &utils.TPChargerProfile{ + TPid: "TP1", + Tenant: "cgrates.org", + ID: "Charger1", + FilterIDs: []string{"FLTR_ACNT_dan", "FLTR_DST_DE"}, + RunID: "*rated", + ActivationInterval: &utils.TPActivationInterval{ + ActivationTime: "2014-07-14T14:35:00Z", + ExpiryTime: "", + }, + AttributeIDs: []string{"ATTR1", "ATTR2"}, + Weight: 20, + } + + expected := &ChargerProfile{ + Tenant: "cgrates.org", + ID: "Charger1", + FilterIDs: []string{"FLTR_ACNT_dan", "FLTR_DST_DE"}, + ActivationInterval: &utils.ActivationInterval{ + ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC), + }, + RunID: "*rated", + AttributeIDs: []string{"ATTR1", "ATTR2"}, + Weight: 20, + } + if rcv, err := APItoChargerProfile(tpCPP, "UTC"); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(expected, rcv) { + t.Errorf("Expecting : %+v, received: %+v", utils.ToJSON(expected), utils.ToJSON(rcv)) + } +} + +func TestAPItoModelTPCharger(t *testing.T) { + tpCharger := &utils.TPChargerProfile{ + TPid: "TP1", + Tenant: "cgrates.org", + ID: "Charger1", + FilterIDs: []string{"FLTR_ACNT_dan", "FLTR_DST_DE"}, + RunID: "*rated", + ActivationInterval: &utils.TPActivationInterval{ + ActivationTime: "2014-07-14T14:35:00Z", + ExpiryTime: "", + }, + AttributeIDs: []string{"ATTR1", "ATTR2"}, + Weight: 20, + } + expected := TPChargers{ + &TPCharger{ + Tpid: "TP1", + Tenant: "cgrates.org", + ID: "Charger1", + FilterIDs: "FLTR_ACNT_dan;FLTR_DST_DE", + RunID: "*rated", + AttributeIDs: "ATTR1;ATTR2", + ActivationInterval: "2014-07-14T14:35:00Z", + Weight: 20, + }, + } + rcv := APItoModelTPCharger(tpCharger) + if !reflect.DeepEqual(expected, rcv) { + t.Errorf("Expecting : %+v, received: %+v", utils.ToJSON(expected), utils.ToJSON(rcv)) + } +} + +func TestModelAsTPChargers(t *testing.T) { + models := TPChargers{ + &TPCharger{ + Tpid: "TP1", + Tenant: "cgrates.org", + ID: "Charger1", + FilterIDs: "FLTR_ACNT_dan;FLTR_DST_DE", + RunID: "*rated", + AttributeIDs: "ATTR1;ATTR2", + ActivationInterval: "2014-07-14T14:35:00Z", + Weight: 20, + }, + } + expected := &utils.TPChargerProfile{ + TPid: "TP1", + Tenant: "cgrates.org", + ID: "Charger1", + FilterIDs: []string{"FLTR_ACNT_dan", "FLTR_DST_DE"}, + RunID: "*rated", + ActivationInterval: &utils.TPActivationInterval{ + ActivationTime: "2014-07-14T14:35:00Z", + ExpiryTime: "", + }, + AttributeIDs: []string{"ATTR1", "ATTR2"}, + Weight: 20, + } + rcv := models.AsTPChargers() + if !reflect.DeepEqual(expected, rcv[0]) { + t.Errorf("Expecting : %+v, received: %+v", utils.ToJSON(expected), utils.ToJSON(rcv[0])) + } +} diff --git a/engine/storage_csv.go b/engine/storage_csv.go index 8b7919fa8..c8ce54a18 100644 --- a/engine/storage_csv.go +++ b/engine/storage_csv.go @@ -759,7 +759,7 @@ func (csvs *CSVStorage) GetTPAttributes(tpid, id string) ([]*utils.TPAttributePr return nil, err } if attributeProfile, err := csvLoad(TPAttribute{}, record); err != nil { - log.Print("error loading tpAliasProfile: ", err) + log.Print("error loading tpAttributeProfile: ", err) return nil, err } else { attributeProfile := attributeProfile.(TPAttribute) @@ -771,31 +771,31 @@ func (csvs *CSVStorage) GetTPAttributes(tpid, id string) ([]*utils.TPAttributePr } func (csvs *CSVStorage) GetTPChargers(tpid, id string) ([]*utils.TPChargerProfile, error) { - // csvReader, fp, err := csvs.readerFunc(csvs.chargerProfilesFn, csvs.sep, getColumnCount(TPCharger{})) - // if err != nil { - // //log.Print("Could not load AttributeProfile file: ", err) - // // allow writing of the other values - // return nil, nil - // } - // if fp != nil { - // defer fp.Close() - // } - // var tpAls TPAttributes - // for record, err := csvReader.Read(); err != io.EOF; record, err = csvReader.Read() { - // if err != nil { - // log.Printf("bad line in %s, %s\n", csvs.chargerProfilesFn, err.Error()) - // return nil, err - // } - // if attributeProfile, err := csvLoad(TPAttribute{}, record); err != nil { - // log.Print("error loading tpAliasProfile: ", err) - // return nil, err - // } else { - // attributeProfile := attributeProfile.(TPAttribute) - // attributeProfile.Tpid = tpid - // tpAls = append(tpAls, &attributeProfile) - // } - // } - return nil, nil + csvReader, fp, err := csvs.readerFunc(csvs.chargerProfilesFn, csvs.sep, getColumnCount(TPCharger{})) + if err != nil { + //log.Print("Could not load AttributeProfile file: ", err) + // allow writing of the other values + return nil, nil + } + if fp != nil { + defer fp.Close() + } + var tpCPPs TPChargers + for record, err := csvReader.Read(); err != io.EOF; record, err = csvReader.Read() { + if err != nil { + log.Printf("bad line in %s, %s\n", csvs.chargerProfilesFn, err.Error()) + return nil, err + } + if cpp, err := csvLoad(TPCharger{}, record); err != nil { + log.Print("error loading tpChargerProfile: ", err) + return nil, err + } else { + cpp := cpp.(TPCharger) + cpp.Tpid = tpid + tpCPPs = append(tpCPPs, &cpp) + } + } + return tpCPPs.AsTPChargers(), nil } func (csvs *CSVStorage) GetTpIds(colName string) ([]string, error) { diff --git a/engine/storage_sql.go b/engine/storage_sql.go index f86b83c56..b4eb15d29 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -738,6 +738,28 @@ func (self *SQLStorage) SetTPAttributes(tpAttrs []*utils.TPAttributeProfile) err return nil } +func (self *SQLStorage) SetTPChargers(tpCPPs []*utils.TPChargerProfile) error { + if len(tpCPPs) == 0 { + return nil + } + tx := self.db.Begin() + for _, cpp := range tpCPPs { + // Remove previous + if err := tx.Where(&TPCharger{Tpid: cpp.TPid, ID: cpp.ID}).Delete(TPCharger{}).Error; err != nil { + tx.Rollback() + return err + } + for _, mst := range APItoModelTPCharger(cpp) { + if err := tx.Save(&mst).Error; err != nil { + tx.Rollback() + return err + } + } + } + tx.Commit() + return nil +} + func (self *SQLStorage) SetSMCost(smc *SMCost) error { if smc.CostDetails == nil { return nil @@ -1624,7 +1646,19 @@ func (self *SQLStorage) GetTPAttributes(tpid, id string) ([]*utils.TPAttributePr } func (self *SQLStorage) GetTPChargers(tpid, id string) ([]*utils.TPChargerProfile, error) { - return nil, nil + var cpps TPChargers + q := self.db.Where("tpid = ?", tpid) + if len(id) != 0 { + q = q.Where("id = ?", id) + } + if err := q.Find(&cpps).Error; err != nil { + return nil, err + } + arls := cpps.AsTPChargers() + if len(arls) == 0 { + return arls, utils.ErrNotFound + } + return arls, nil } // GetVersions returns slice of all versions or a specific version if tag is specified diff --git a/engine/tp_reader.go b/engine/tp_reader.go index 6e12f0efe..6d1409bf8 100644 --- a/engine/tp_reader.go +++ b/engine/tp_reader.go @@ -58,11 +58,13 @@ type TpReader struct { filters map[utils.TenantID]*utils.TPFilterProfile sppProfiles map[utils.TenantID]*utils.TPSupplierProfile attributeProfiles map[utils.TenantID]*utils.TPAttributeProfile + chargerProfiles map[utils.TenantID]*utils.TPChargerProfile resources []*utils.TenantID // IDs of resources which need creation based on resourceProfiles statQueues []*utils.TenantID // IDs of statQueues which need creation based on statQueueProfiles thresholds []*utils.TenantID // IDs of thresholds which need creation based on thresholdProfiles suppliers []*utils.TenantID // IDs of suppliers which need creation based on sppProfiles attrTntID []*utils.TenantID // IDs of suppliers which need creation based on attributeProfiles + chargers []*utils.TenantID // IDs of chargers which need creation based on chargerProfiles revDests, revAliases, acntActionPlans map[string][]string @@ -138,6 +140,7 @@ func (tpr *TpReader) Init() { tpr.thProfiles = make(map[utils.TenantID]*utils.TPThreshold) tpr.sppProfiles = make(map[utils.TenantID]*utils.TPSupplierProfile) tpr.attributeProfiles = make(map[utils.TenantID]*utils.TPAttributeProfile) + tpr.chargerProfiles = make(map[utils.TenantID]*utils.TPChargerProfile) tpr.filters = make(map[utils.TenantID]*utils.TPFilterProfile) tpr.revDests = make(map[string][]string) tpr.revAliases = make(map[string][]string) @@ -1738,6 +1741,30 @@ func (tpr *TpReader) LoadAttributeProfiles() error { return tpr.LoadAttributeProfilesFiltered("") } +func (tpr *TpReader) LoadChargerProfilesFiltered(tag string) (err error) { + rls, err := tpr.lr.GetTPChargers(tpr.tpid, tag) + if err != nil { + return err + } + mapChargerProfile := make(map[utils.TenantID]*utils.TPChargerProfile) + for _, rl := range rls { + mapChargerProfile[utils.TenantID{Tenant: rl.Tenant, ID: rl.ID}] = rl + } + tpr.chargerProfiles = mapChargerProfile + for tntID, _ := range mapChargerProfile { + if has, err := tpr.dm.HasData(utils.ChargerProfilePrefix, tntID.ID, tntID.Tenant); err != nil { + return err + } else if !has { + tpr.chargers = append(tpr.chargers, &utils.TenantID{Tenant: tntID.Tenant, ID: tntID.ID}) + } + } + return nil +} + +func (tpr *TpReader) LoadChargerProfiles() error { + return tpr.LoadChargerProfilesFiltered("") +} + func (tpr *TpReader) LoadAll() (err error) { if err = tpr.LoadDestinations(); err != nil && err.Error() != utils.NotFoundCaps { return @@ -1805,6 +1832,9 @@ func (tpr *TpReader) LoadAll() (err error) { if err = tpr.LoadAttributeProfiles(); err != nil && err.Error() != utils.NotFoundCaps { return } + if err = tpr.LoadChargerProfiles(); err != nil && err.Error() != utils.NotFoundCaps { + return + } return nil } @@ -2175,6 +2205,23 @@ func (tpr *TpReader) WriteToDatabase(flush, verbose, disable_reverse bool) (err } } + if verbose { + log.Print("ChargerProfiles:") + } + for _, tpTH := range tpr.chargerProfiles { + + th, err := APItoChargerProfile(tpTH, tpr.timezone) + if err != nil { + return err + } + if err = tpr.dm.SetChargerProfile(th, true); err != nil { + return err + } + if verbose { + log.Print("\t", th.TenantID()) + } + } + if verbose { log.Print("Timings:") } @@ -2284,6 +2331,8 @@ func (tpr *TpReader) ShowStatistics() { log.Print("SupplierProfiles: ", len(tpr.sppProfiles)) // Attribute profiles log.Print("AttributeProfiles: ", len(tpr.attributeProfiles)) + // Charger profiles + log.Print("ChargerProfiles: ", len(tpr.chargerProfiles)) } // Returns the identities loaded for a specific category, useful for cache reloads @@ -2457,6 +2506,14 @@ func (tpr *TpReader) GetLoadedIds(categ string) ([]string, error) { i++ } return keys, nil + case utils.ChargerProfilePrefix: + keys := make([]string, len(tpr.chargerProfiles)) + i := 0 + for k, _ := range tpr.chargerProfiles { + keys[i] = k.TenantID() + i++ + } + return keys, nil } return nil, errors.New("Unsupported load category") } @@ -2722,6 +2779,31 @@ func (tpr *TpReader) RemoveFromDatabase(verbose, disable_reverse bool) (err erro log.Print("\t", tpTH.Tenant) } } + + if verbose { + log.Print("AttributeProfiles:") + } + for _, tpTH := range tpr.attributeProfiles { + if err = tpr.dm.RemoveAttributeProfile(tpTH.Tenant, tpTH.ID, tpTH.Contexts, utils.NonTransactional, false); err != nil { + return err + } + if verbose { + log.Print("\t", tpTH.Tenant) + } + } + + if verbose { + log.Print("ChargerProfiles:") + } + for _, tpTH := range tpr.chargerProfiles { + if err = tpr.dm.RemoveChargerProfile(tpTH.Tenant, tpTH.ID, utils.NonTransactional, false); err != nil { + return err + } + if verbose { + log.Print("\t", tpTH.Tenant) + } + } + if verbose { log.Print("Timings:") } diff --git a/engine/tpexporter.go b/engine/tpexporter.go index 432808b54..c8b424cae 100644 --- a/engine/tpexporter.go +++ b/engine/tpexporter.go @@ -295,6 +295,17 @@ func (self *TPExporter) Run() error { } } + storDataChargers, err := self.storDb.GetTPChargers(self.tpID, "") + if err != nil && err.Error() != utils.ErrNotFound.Error() { + return err + } + for _, sd := range storDataChargers { + sdModels := APItoModelTPCharger(sd) + for _, sdModel := range sdModels { + toExportMap[utils.ChargersCsv] = append(toExportMap[utils.ChargersCsv], sdModel) + } + } + storDataUsers, err := self.storDb.GetTPUsers(&utils.TPUsers{TPid: self.tpID}) if err != nil && err.Error() != utils.ErrNotFound.Error() { return err