/**
  * @param array $tree
  * @param array $orders
  * @param array $params configuration parameters ordered desc (from up to down)
  * @param array $percents
  *
  * @return array
  */
 public function calc($tree, $orders, $params, $percents)
 {
     $result = [];
     $mapTreeExp = $this->_expandTree($tree);
     $mapRankById = $this->_rankQualifier->qualifyCustomers($tree, $params);
     foreach ($orders as $order) {
         $custId = $order[Cfg::E_SALE_ORDER_A_CUSTOMER_ID];
         $orderId = $order[PvSale::ATTR_SALE_ID];
         $pv = $order[PvSale::ATTR_TOTAL];
         $path = $mapTreeExp[$custId][Snap::ATTR_PATH];
         $parents = $this->_toolDownlineTree->getParentsFromPathReversed($path);
         $gen = 1;
         foreach ($parents as $parentId) {
             if (isset($mapRankById[$parentId])) {
                 $parentRank = $mapRankById[$parentId];
                 if (isset($percents[$parentRank][$gen])) {
                     $percent = $percents[$parentRank][$gen];
                     $bonus = $pv * $percent;
                     $bonus = $this->_toolFormat->roundBonus($bonus);
                     $result[$parentId][$orderId] = $bonus;
                 }
             }
             $gen++;
         }
     }
     return $result;
 }
 public function calcParams($tree, $qData, $gvMaxLevels, $psaaLevel)
 {
     $treeExpanded = $this->_expandTree($tree);
     $mapByDepth = $this->_mapByTreeDepthDesc($treeExpanded);
     $mapTeams = $this->_mapByTeams($tree);
     $mapById = $this->_mapById($tree);
     $result = [];
     foreach ($mapByDepth as $depth => $level) {
         foreach ($level as $custId) {
             /* init result entry for the customer if entry is not exist */
             if (!isset($result[$custId])) {
                 $result[$custId] = $this->_initEntry();
                 $result[$custId][EntityQual::ATTR_COMPRESS_ID] = $mapById[$custId][Compress::ATTR_ID];
             }
             /* process PV */
             $pv = $qData[$custId];
             $result[$custId][EntityQual::ATTR_PV] = $pv;
             /* process GV */
             $path = $treeExpanded[$custId][Snap::ATTR_PATH];
             $parents = $this->_toolDownlineTree->getParentsFromPathReversed($path);
             $gen = 1;
             foreach ($parents as $parentId) {
                 if ($gen > $gvMaxLevels) {
                     break;
                 }
                 if (!isset($result[$parentId])) {
                     $result[$parentId] = $this->_initEntry();
                     $result[$parentId][EntityQual::ATTR_COMPRESS_ID] = $mapById[$parentId][Compress::ATTR_ID];
                 }
                 $result[$parentId][EntityQual::ATTR_GV] += $pv;
                 $gen++;
             }
             /* process PSAA */
             if (isset($mapTeams[$custId])) {
                 $psaa = 0;
                 $team = $mapTeams[$custId];
                 foreach ($team as $memberId) {
                     $mPv = $result[$memberId][EntityQual::ATTR_PV];
                     if ($mPv > $psaaLevel) {
                         $psaa++;
                     }
                 }
                 $result[$custId][EntityQual::ATTR_PSAA] = $psaa;
             }
         }
     }
     return $result;
 }
 /**
  * @param Request\QualifyByUserData $req
  *
  * @return Response\QualifyByUserData
  */
 public function qualifyByUserData(Request\QualifyByUserData $req)
 {
     $result = new Response\QualifyByUserData();
     /* parse request */
     $calcId = $req->getCalcId();
     $treeFlat = $req->getFlatTree();
     $qualifier = $req->getQualifier();
     $skipExpand = (bool) $req->getSkipTreeExpand();
     $this->_logger->info("'QualifyByUserData' operation is started.");
     $treeCompressed = [];
     if ($skipExpand) {
         $treeExpanded = $treeFlat;
     } else {
         $treeExpanded = $this->_toolDownlineTree->expandMinimal($treeFlat, ESnap::ATTR_PARENT_ID);
     }
     $mapById = $this->_mapById($treeExpanded);
     $mapDepth = $this->_mapByTreeDepthDesc($treeExpanded);
     $mapTeams = $this->_mapByTeams($treeExpanded);
     foreach ($mapDepth as $depth => $levelCustomers) {
         foreach ($levelCustomers as $custId) {
             $custData = $mapById[$custId];
             $ref = isset($custData[Customer::ATTR_HUMAN_REF]) ? $custData[Customer::ATTR_HUMAN_REF] : '';
             if ($qualifier->isQualified($custData)) {
                 $this->_logger->info("Customer #{$custId} ({$ref}) is qualified and added to compressed tree.");
                 $treeCompressed[$custId] = $custData;
             } else {
                 $this->_logger->info("Customer #{$custId} ({$ref}) is not qualified.");
                 if (isset($mapTeams[$custId])) {
                     $this->_logger->info("Customer #{$custId} ({$ref}) has own front team.");
                     /* Lookup for the closest qualified parent */
                     $path = $treeExpanded[$custId][ESnap::ATTR_PATH];
                     $parents = $this->_toolDownlineTree->getParentsFromPathReversed($path);
                     $foundParentId = null;
                     foreach ($parents as $newParentId) {
                         $parentData = $mapById[$newParentId];
                         if ($qualifier->isQualified($parentData)) {
                             $foundParentId = $newParentId;
                             break;
                         }
                     }
                     /* Change parent for all siblings of the unqualified customer. */
                     $team = $mapTeams[$custId];
                     foreach ($team as $memberId) {
                         if (isset($treeCompressed[$memberId])) {
                             /* if null set customer own id to indicate root node */
                             $treeCompressed[$memberId][ESnap::ATTR_PARENT_ID] = is_null($foundParentId) ? $memberId : $foundParentId;
                         }
                     }
                 }
             }
         }
     }
     unset($mapCustomer);
     unset($mapPv);
     unset($mapDepth);
     unset($mapTeams);
     /* save compressed tree */
     $def = $this->_manTrans->begin();
     try {
         foreach ($treeCompressed as $custId => $item) {
             $data = [ECompress::ATTR_CALC_ID => $calcId, ECompress::ATTR_CUSTOMER_ID => $custId, ECompress::ATTR_PARENT_ID => $item[ESnap::ATTR_PARENT_ID]];
             $this->_repoBonusCompress->create($data);
         }
         $this->_manTrans->commit($def);
     } finally {
         $this->_manTrans->end($def);
     }
     $result->markSucceed();
     $this->_logger->info("'QualifyByUserData' operation is completed.");
     return $result;
 }
 /**
  * Process Downline Tree snapshot, customer data and PV related transactions and compose data for compressed tree.
  *
  * @param $treeSnap array downline tree snapshot: [ $custId => [$customer_id, $parent_id, $depth, $path], ...].
  * @param $customers array [ [$custId, $humanRef, $countryCode], ...]
  * @param $trans array of the PV related transactions.
  *
  * @return array data to save into prxgt_bon_hyb_compress
  */
 public function compressPtc($treeSnap, $customers, $trans)
 {
     $qLevels = $this->_toolScheme->getQualificationLevels();
     $forcedIds = $this->_toolScheme->getForcedQualificationCustomersIds();
     $this->_logger->info("PTC Compression parameters:" . " qualification levels=" . var_export($qLevels, true) . ", forced customers: " . var_export($forcedIds, true));
     /* array with results: [$customerId => [$pvCompressed, $parentCompressed], ... ]*/
     $compressedTree = [];
     $mapCustomer = $this->_mapById($customers, Customer::ATTR_CUSTOMER_ID);
     $mapPv = $this->_mapByPv($trans, Account::ATTR_CUST_ID, Transaction::ATTR_VALUE);
     $mapDepth = $this->_mapByTreeDepthDesc($treeSnap, Snap::ATTR_CUSTOMER_ID, Snap::ATTR_DEPTH);
     $mapTeams = $this->_mapByTeams($treeSnap, Snap::ATTR_CUSTOMER_ID, Snap::ATTR_PARENT_ID);
     foreach ($mapDepth as $depth => $levelCustomers) {
         foreach ($levelCustomers as $custId) {
             $pv = isset($mapPv[$custId]) ? $mapPv[$custId] : 0;
             $parentId = $treeSnap[$custId][Snap::ATTR_PARENT_ID];
             $custData = $mapCustomer[$custId];
             $scheme = $this->_toolScheme->getSchemeByCustomer($custData);
             $level = $qLevels[$scheme];
             // qualification level for current customer
             if ($pv >= $level || in_array($custId, $forcedIds)) {
                 if (isset($compressedTree[$custId])) {
                     $pvExist = $compressedTree[$custId][0];
                     $pvNew = $pv + $pvExist;
                     $compressedTree[$custId] = [$pvNew, $parentId];
                 } else {
                     $compressedTree[$custId] = [$pv, $parentId];
                 }
             } else {
                 /* move PV up to the closest qualified parent (current customer's level is used for qualification) */
                 $path = $treeSnap[$custId][Snap::ATTR_PATH];
                 $parents = $this->_toolDownlineTree->getParentsFromPathReversed($path);
                 $foundParentId = null;
                 foreach ($parents as $newParentId) {
                     $pvParent = isset($mapPv[$newParentId]) ? $mapPv[$newParentId] : 0;
                     if ($pvParent >= $level || in_array($newParentId, $forcedIds)) {
                         $foundParentId = $newParentId;
                         break;
                     }
                 }
                 unset($parents);
                 /* add PV to this parent */
                 if (!is_null($foundParentId) && $pv > 0) {
                     if (isset($compressedTree[$foundParentId])) {
                         $pvExist = $compressedTree[$foundParentId][0];
                         $pvNew = $pv + $pvExist;
                         $compressedTree[$foundParentId][0] = $pvNew;
                     } else {
                         $compressedTree[$foundParentId][0] = $pv;
                     }
                     $this->_logger->debug("{$pv} PV are transferred from customer #{$custId} to his qualified parent #{$foundParentId} .");
                 }
                 /* change parent for all siblings of the unqualified customer */
                 if (isset($mapTeams[$custId])) {
                     $team = $mapTeams[$custId];
                     foreach ($team as $memberId) {
                         if (isset($compressedTree[$memberId])) {
                             /* if null set customer own id to indicate root node */
                             $compressedTree[$memberId][1] = is_null($foundParentId) ? $memberId : $foundParentId;
                         }
                     }
                 }
             }
         }
     }
     unset($mapCustomer);
     unset($mapPv);
     unset($mapDepth);
     unset($mapTeams);
     /* compose compressed snapshot data */
     $data = $this->_composeSnapUpdates($compressedTree);
     /* add compressed PV data */
     $result = $this->_populateCompressedSnapWithPv($data, $compressedTree);
     return $result;
 }
 public function calcParams($tree, $qData, $cfgParams, $gvMaxLevels)
 {
     $treeExpanded = $this->_expandTree($tree);
     $mapByDepth = $this->_mapByTreeDepthDesc($treeExpanded);
     $mapById = $this->_mapById($tree);
     /* GV tree contains GV by legs for every customer: [$custId=>[$legCustId=>$gv, ...], ...] */
     $gvTree = [];
     foreach ($mapByDepth as $depth => $level) {
         foreach ($level as $custId) {
             /* init node if not exist */
             if (!isset($gvTree[$custId])) {
                 $gvTree[$custId] = [];
             }
             $pv = $qData[$custId];
             $path = $treeExpanded[$custId][Snap::ATTR_PATH];
             $parents = $this->_toolDownlineTree->getParentsFromPathReversed($path);
             $lvl = 1;
             $legId = $custId;
             foreach ($parents as $parentId) {
                 /* break on max level for GV calculation is exceeded */
                 if ($lvl > $gvMaxLevels) {
                     break;
                 }
                 /* init node if not exist */
                 if (!isset($gvTree[$parentId])) {
                     $gvTree[$parentId] = [];
                 }
                 /* add current PV to the parent on the current leg */
                 if (isset($gvTree[$parentId][$legId])) {
                     $gvTree[$parentId][$legId] += $pv;
                 } else {
                     $gvTree[$parentId][$legId] = $pv;
                 }
                 /* increase levels counter and switch current leg id */
                 $lvl++;
                 $legId = $parentId;
             }
         }
     }
     /* process intermediary GV values and calculate final results */
     $result = [];
     foreach ($gvTree as $custId => $legs) {
         /* skip nodes without legs */
         if (count($legs) == 0) {
             continue;
         }
         /* qualify max rank; for all ranks from min to max */
         $rankIdQualified = null;
         $gvQualified = 0;
         foreach ($cfgParams as $rankId => $params) {
             $maxPercent = $params[Param::ATTR_LEG_MAX_PERCENT];
             $gvRequired = $params[Param::ATTR_GV];
             $gvMax = $gvRequired * $maxPercent;
             $gvTotal = 0;
             foreach ($legs as $gvLeg) {
                 $gvTotal += $gvLeg > $gvMax ? $gvMax : $gvLeg;
             }
             /* total GV should not be less then required  */
             if ($gvTotal >= $gvRequired) {
                 $rankIdQualified = $rankId;
                 $gvQualified = $gvTotal;
             } else {
                 break;
             }
         }
         /* add values to results */
         if (!is_null($rankIdQualified)) {
             $compressId = $mapById[$custId][Compress::ATTR_ID];
             $result[$custId] = [EntityQual::ATTR_COMPRESS_ID => $compressId, EntityQual::ATTR_RANK_ID => $rankIdQualified, EntityQual::ATTR_GV => $gvQualified];
         }
     }
     return $result;
 }