/* Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments Copyright (C) ITsysCOM GmbH This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package engine import ( "fmt" "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) func NewChargerService(dm *DataManager, filterS *FilterS, cfg *config.CGRConfig, connMgr *ConnManager) *ChargerService { return &ChargerService{dm: dm, filterS: filterS, cfg: cfg, connMgr: connMgr} } // ChargerService is performing charging type ChargerService struct { dm *DataManager filterS *FilterS cfg *config.CGRConfig connMgr *ConnManager } // Shutdown is called to shutdown the service func (cS *ChargerService) Shutdown() { utils.Logger.Info(fmt.Sprintf("<%s> shutdown initialized", utils.ChargerS)) utils.Logger.Info(fmt.Sprintf("<%s> shutdown complete", utils.ChargerS)) } // matchingChargingProfilesForEvent returns ordered list of matching chargers which are active by the time of the function call func (cS *ChargerService) matchingChargerProfilesForEvent(tnt string, cgrEv *utils.CGREvent) (cPs ChargerProfiles, err error) { evNm := utils.MapStorage{ utils.MetaReq: cgrEv.Event, utils.MetaOpts: cgrEv.APIOpts, } cpIDs, err := MatchingItemIDsForEvent(evNm, cS.cfg.ChargerSCfg().StringIndexedFields, cS.cfg.ChargerSCfg().PrefixIndexedFields, cS.cfg.ChargerSCfg().SuffixIndexedFields, cS.cfg.ChargerSCfg().ExistsIndexedFields, cS.dm, utils.CacheChargerFilterIndexes, tnt, cS.cfg.ChargerSCfg().IndexedSelects, cS.cfg.ChargerSCfg().NestedFields, ) if err != nil { return nil, err } matchingCPs := make(map[string]*ChargerProfile) for cpID := range cpIDs { cP, err := cS.dm.GetChargerProfile(tnt, cpID, true, true, utils.NonTransactional) if err != nil { if err == utils.ErrNotFound { continue } return nil, err } if cP.ActivationInterval != nil && cgrEv.Time != nil && !cP.ActivationInterval.IsActiveAtTime(*cgrEv.Time) { // not active continue } if pass, err := cS.filterS.Pass(tnt, cP.FilterIDs, evNm); err != nil { return nil, err } else if !pass { continue } matchingCPs[cpID] = cP } if len(matchingCPs) == 0 { return nil, utils.ErrNotFound } cPs = make(ChargerProfiles, len(matchingCPs)) i := 0 for _, cP := range matchingCPs { cPs[i] = cP i++ } cPs.Sort() return } // ChrgSProcessEventReply is the reply to processEvent type ChrgSProcessEventReply struct { ChargerSProfile string AttributeSProfiles []string AlteredFields []string CGREvent *utils.CGREvent } func (cS *ChargerService) processEvent(tnt string, cgrEv *utils.CGREvent) (rply []*ChrgSProcessEventReply, err error) { var cPs ChargerProfiles if cPs, err = cS.matchingChargerProfilesForEvent(tnt, cgrEv); err != nil { return nil, err } rply = make([]*ChrgSProcessEventReply, len(cPs)) for i, cP := range cPs { clonedEv := cgrEv.Clone() clonedEv.Tenant = tnt clonedEv.Event[utils.RunID] = cP.RunID clonedEv.APIOpts[utils.MetaSubsys] = utils.MetaChargers rply[i] = &ChrgSProcessEventReply{ ChargerSProfile: cP.ID, CGREvent: clonedEv, AlteredFields: []string{utils.MetaReqRunID}, } if len(cP.AttributeIDs) == 1 && cP.AttributeIDs[0] == utils.MetaNone { continue // AttributeS disabled } clonedEv.APIOpts[utils.OptsAttributesProfileIDs] = cP.AttributeIDs ctx, has := clonedEv.APIOpts[utils.OptsContext] clonedEv.APIOpts[utils.OptsContext] = utils.FirstNonEmpty( utils.IfaceAsString(ctx), utils.MetaChargers) var evReply AttrSProcessEventReply if err = cS.connMgr.Call(context.TODO(), cS.cfg.ChargerSCfg().AttributeSConns, utils.AttributeSv1ProcessEvent, clonedEv, &evReply); err != nil { if err.Error() != utils.ErrNotFound.Error() { return nil, err } err = nil } rply[i].AttributeSProfiles = evReply.MatchedProfiles if len(evReply.AlteredFields) != 0 { rply[i].AlteredFields = append(rply[i].AlteredFields, evReply.AlteredFields...) rply[i].CGREvent = evReply.CGREvent } if !has && utils.IfaceAsString(rply[i].CGREvent.APIOpts[utils.OptsContext]) == utils.MetaChargers { delete(rply[i].CGREvent.APIOpts, utils.OptsContext) } } return } // V1ProcessEvent will process the event received via API and return list of events forked func (cS *ChargerService) V1ProcessEvent(ctx *context.Context, args *utils.CGREvent, reply *[]*ChrgSProcessEventReply) (err error) { if args == nil || args.Event == nil { return utils.NewErrMandatoryIeMissing("Event") } tnt := args.Tenant if tnt == utils.EmptyString { tnt = cS.cfg.GeneralCfg().DefaultTenant } rply, err := cS.processEvent(tnt, args) if err != nil { if err != utils.ErrNotFound { err = utils.NewErrServerError(err) } return err } *reply = rply return } // V1GetChargersForEvent exposes the list of ordered matching ChargingProfiles for an event func (cS *ChargerService) V1GetChargersForEvent(ctx *context.Context, args *utils.CGREvent, rply *ChargerProfiles) (err error) { tnt := args.Tenant if tnt == utils.EmptyString { tnt = cS.cfg.GeneralCfg().DefaultTenant } cPs, err := cS.matchingChargerProfilesForEvent(tnt, args) if err != nil { if err != utils.ErrNotFound { err = utils.NewErrServerError(err) } return err } *rply = cPs return }