public function calcBalances($currentBalances, $transactions)
 {
     $result = [];
     foreach ($transactions as $one) {
         $accDebit = $one[Transaction::ATTR_DEBIT_ACC_ID];
         $accCredit = $one[Transaction::ATTR_CREDIT_ACC_ID];
         $timestamp = $one[Transaction::ATTR_DATE_APPLIED];
         $date = $this->_toolPeriod->getPeriodCurrent($timestamp, IPeriod::TYPE_DAY);
         $changeValue = $one[Transaction::ATTR_VALUE];
         /**
          * process debit account
          */
         /* get calculated balance on the date*/
         if (isset($result[$accDebit][$date])) {
             /* there is data for this account on this date */
             $data = $result[$accDebit][$date];
         } else {
             /* there is NO data for this account on this date */
             $data = [Balance::ATTR_ACCOUNT_ID => $accDebit, Balance::ATTR_DATE => $date, Balance::ATTR_BALANCE_OPEN => 0, Balance::ATTR_TOTAL_DEBIT => 0, Balance::ATTR_TOTAL_CREDIT => 0, Balance::ATTR_BALANCE_CLOSE => 0];
             /* we need to update opening balance */
             if (isset($result[$accDebit])) {
                 $last = end($result[$accDebit]);
                 $data[Balance::ATTR_BALANCE_OPEN] = $last[Balance::ATTR_BALANCE_CLOSE];
             } elseif (isset($currentBalances[$accDebit])) {
                 $data[Balance::ATTR_BALANCE_OPEN] = $currentBalances[$accDebit][Balance::ATTR_BALANCE_CLOSE];
             }
         }
         /* change debit related values */
         $data[Balance::ATTR_TOTAL_DEBIT] += $changeValue;
         $data[Balance::ATTR_BALANCE_CLOSE] -= $changeValue;
         $result[$accDebit][$date] = $data;
         /**
          * process credit account
          */
         /* get calculated balance on the date*/
         if (isset($result[$accCredit][$date])) {
             /* there is data for this account on this date */
             $data = $result[$accCredit][$date];
         } else {
             /* there is NO data for this account on this date */
             $data = [Balance::ATTR_ACCOUNT_ID => $accCredit, Balance::ATTR_DATE => $date, Balance::ATTR_BALANCE_OPEN => 0, Balance::ATTR_TOTAL_DEBIT => 0, Balance::ATTR_TOTAL_CREDIT => 0, Balance::ATTR_BALANCE_CLOSE => 0];
             /* we need to update opening balance */
             if (isset($result[$accCredit])) {
                 $last = end($result[$accCredit]);
                 $data[Balance::ATTR_BALANCE_OPEN] = $last[Balance::ATTR_BALANCE_CLOSE];
             } elseif (isset($currentBalances[$accCredit])) {
                 $data[Balance::ATTR_BALANCE_OPEN] = $currentBalances[$accCredit][Balance::ATTR_BALANCE_CLOSE];
             }
         }
         /* change credit related values */
         $data[Balance::ATTR_TOTAL_CREDIT] += $changeValue;
         $data[Balance::ATTR_BALANCE_CLOSE] += $changeValue;
         $result[$accCredit][$date] = $data;
     }
     return $result;
 }
 /**
  * @param Request\GetForWriteOff $request
  *
  * @return Response\GetForWriteOff
  */
 public function getForWriteOff(Request\GetForWriteOff $request)
 {
     $result = new Response\GetForWriteOff();
     $this->_logger->info("'Get latest period for Write Off calculation' operation is started.");
     /* get the last Write Off period data */
     $calcWriteOffCode = Cfg::CODE_TYPE_CALC_PV_WRITE_OFF;
     $calcWriteOffId = $this->_subDb->getCalcIdByCode($calcWriteOffCode);
     $respWriteOffLastPeriod = $this->_subDb->getLastPeriodData($calcWriteOffId);
     $periodWriteOffData = $respWriteOffLastPeriod->getPeriodData();
     if (is_null($periodWriteOffData)) {
         $this->_logger->info("There is no period for PV Write Off calculation  yet.");
         /* calc period for PV Write Off */
         $tsFirstPv = $this->_subDb->getFirstDateForPvTransactions();
         if ($tsFirstPv === false) {
             $this->_logger->info("There is no PV transactions yet. Nothing to do.");
             $result->setHasNoPvTransactionsYet();
         } else {
             $this->_logger->info("First PV transaction was performed at '{$tsFirstPv}'.");
             $periodMonth = $this->_toolPeriod->getPeriodCurrent($tsFirstPv, ToolPeriod::TYPE_MONTH);
             $dsBegin = $this->_toolPeriod->getPeriodFirstDate($periodMonth);
             $dsEnd = $this->_toolPeriod->getPeriodLastDate($periodMonth);
             $periodWriteOffData = $this->_subDb->addNewPeriodAndCalc($calcWriteOffId, $dsBegin, $dsEnd);
             $result->setPeriodData($periodWriteOffData->getData(Sub\Db::DATA_PERIOD));
             $result->setCalcData($periodWriteOffData->getData(Sub\Db::DATA_CALC));
             $result->markSucceed();
         }
     } else {
         $result->setPeriodData($periodWriteOffData);
         $periodId = $periodWriteOffData->getId();
         $this->_logger->info("There is registered period #{$periodId} for '{$calcWriteOffCode}' calculation.");
         $calcData = $respWriteOffLastPeriod->getCalcData();
         if ($calcData === false) {
             $this->_logger->info("There is no calculation data for existing period. Use existing period data.");
             $result->markSucceed();
         } else {
             if ($calcData && $calcData->getState() == Cfg::CALC_STATE_COMPLETE) {
                 $this->_logger->info("There is complete calculation for existing period. Create new period.");
                 $periodEnd = $periodWriteOffData->getDstampEnd();
                 /* calculate new period bounds */
                 $periodNext = $this->_toolPeriod->getPeriodNext($periodEnd, ToolPeriod::TYPE_MONTH);
                 $dsNextBegin = $this->_toolPeriod->getPeriodFirstDate($periodNext);
                 $dsNextEnd = $this->_toolPeriod->getPeriodLastDate($periodNext);
                 $periodWriteOffData = $this->_subDb->addNewPeriodAndCalc($calcWriteOffId, $dsNextBegin, $dsNextEnd);
                 $result->setPeriodData($periodWriteOffData->getData(Sub\Db::DATA_PERIOD));
                 $result->setCalcData($periodWriteOffData->getData(Sub\Db::DATA_CALC));
                 $result->markSucceed();
             } else {
                 $this->_logger->info("There is no complete calculation for existing period. Use existing period data.");
                 $result->setCalcData($calcData);
                 $result->markSucceed();
             }
         }
     }
     $this->_logger->info("'Get latest period for Write Off calculation' operation is completed.");
     return $result;
 }
 /**
  * @param string $dateBegin datestamp (YYYYMMDD) for the date when the first customer should be created.
  * @param bool $switchDateOnNewCustomer 'true' - create customers day by day, 'false' - create all customers
  * in one day.
  */
 protected function _createDownlineCustomers($dateBegin = self::DATE_PERIOD_BEGIN, $switchDateOnNewCustomer = true)
 {
     $dtToday = $dateBegin;
     foreach ($this->DEFAULT_DWNL_TREE as $customerRef => $parentRef) {
         $customerMageId = $this->_mapCustomerMageIdByIndex[$customerRef];
         /* get magento customer data */
         $request = new CustomerAddRequest();
         $request->setCustomerId($customerMageId);
         $request->setParentId($this->_mapCustomerMageIdByIndex[$parentRef]);
         $request->setReference($this->_mapCustomerMageIdByIndex[$customerRef]);
         $request->setCountryCode(self::DEFAULT_DOWNLINE_COUNTRY_CODE);
         $request->setDate($this->_toolPeriod->getTimestampFrom($dtToday));
         /* Create customer per day or all customers in the same day. */
         if ($switchDateOnNewCustomer) {
             $dtToday = $this->_toolPeriod->getPeriodNext($dtToday);
         }
         $response = $this->_callDownlineCustomer->add($request);
         if ($response->isSucceed()) {
             $path = $response->getData(Customer::ATTR_PATH);
             $depth = $response->getData(Customer::ATTR_DEPTH);
             $this->_logger->debug("New customer #{$customerMageId} is added to path '{$path}' on depth {$depth} at '{$dtToday}'.");
         } else {
             $this->_logger->error("Cannot add new customer #{$customerMageId} to downline tree.");
         }
     }
 }
 public function getLastDate(Request\GetLastDate $request)
 {
     $result = new Response\GetLastDate();
     $assetTypeId = $request->getAssetTypeId();
     $assetTypeCode = $request->getAssetTypeCode();
     if (is_null($assetTypeId)) {
         $assetTypeId = $this->_repoTypeAsset->getIdByCode($assetTypeCode);
     }
     /* get the maximal date for balance */
     $balanceMaxDate = $this->_repoMod->getBalanceMaxDate($assetTypeId);
     if ($balanceMaxDate) {
         /* there is balance data */
         $dayBefore = $this->_toolPeriod->getPeriodPrev($balanceMaxDate, IPeriod::TYPE_DAY);
         $result->setData([Response\GetLastDate::LAST_DATE => $dayBefore]);
         $result->markSucceed();
     } else {
         /* there is no balance data yet, get transaction with minimal date */
         $transactionMinDate = $this->_repoMod->getTransactionMinDateApplied($assetTypeId);
         if ($transactionMinDate) {
             $period = $this->_toolPeriod->getPeriodCurrent($transactionMinDate);
             $dayBefore = $this->_toolPeriod->getPeriodPrev($period, IPeriod::TYPE_DAY);
             $result->setData([Response\GetLastDate::LAST_DATE => $dayBefore]);
             $result->markSucceed();
         }
     }
     return $result;
 }
 /**
  *
  * Get PV related period data if no period yet exist.
  *
  * @param \Praxigento\BonusBase\Service\Period\Response\GetForPvBasedCalc $result
  * @param string $periodType
  * @param int $calcTypeId
  * @return \Praxigento\BonusBase\Service\Period\Response\GetForPvBasedCalc
  */
 public function getNewPeriodDataForPv(\Praxigento\BonusBase\Service\Period\Response\GetForPvBasedCalc $result, $periodType, $calcTypeId)
 {
     /* we should lookup for first PV transaction and calculate first period range */
     $firstDate = $this->_repoService->getFirstDateForPvTransactions();
     if ($firstDate === false) {
         $this->_logger->warning("There is no PV transactions yet. Nothing to do.");
         $result->setErrorCode($result::ERR_HAS_NO_PV_TRANSACTIONS_YET);
     } else {
         $this->_logger->info("First PV transaction was performed at '{$firstDate}'.");
         $periodMonth = $this->_toolPeriod->getPeriodCurrent($firstDate, $periodType);
         $dsBegin = $this->_toolPeriod->getPeriodFirstDate($periodMonth);
         $dsEnd = $this->_toolPeriod->getPeriodLastDate($periodMonth);
         /* create new period for given calculation type */
         $period = new EPeriod();
         $period->setCalcTypeId($calcTypeId);
         $period->setDstampBegin($dsBegin);
         $period->setDstampEnd($dsEnd);
         $periodId = $this->_repoPeriod->create($period);
         $period->setId($periodId);
         /* create related calculation */
         $calc = new ECalculation();
         $calc->setPeriodId($periodId);
         $dateStarted = $this->_toolDate->getUtcNowForDb();
         $calc->setDateStarted($dateStarted);
         $calc->setState(Cfg::CALC_STATE_STARTED);
         $calcId = $this->_repoCalc->create($calc);
         $calc->setId($calcId);
         /* place newly created objects into the response */
         $result->setPeriodData($period);
         $result->setCalcData($calc);
     }
     return $result;
 }
 /**
  * SELECT
  * SUM(pps.total)
  * FROM `prxgt_pv_sale` AS `pps`
  * WHERE (pps.date_paid >= '2016-01-01 08:00:00')
  * AND (pps.date_paid <= '2017-01-01 07:59:59')
  *
  * @param string $dsFrom
  * @param string $dsTo
  */
 function getSalesOrdersPvForPeriod($dsFrom, $dsTo)
 {
     $tsFrom = $this->_toolPeriod->getTimestampFrom($dsFrom);
     $tsTo = $this->_toolPeriod->getTimestampTo($dsTo);
     /* aliases and tables */
     $asSummary = 'summary';
     $asPv = 'pps';
     $tblPv = $this->_resource->getTableName(PvSale::ENTITY_NAME);
     // SELECT FROM prxgt_pv_sale pps
     $query = $this->_conn->select();
     $query->from([$asPv => $tblPv], [$asSummary => 'SUM(' . PvSale::ATTR_TOTAL . ')']);
     // where
     $whereFrom = $asPv . '.' . PvSale::ATTR_DATE_PAID . '>=' . $this->_conn->quote($tsFrom);
     $whereTo = $asPv . '.' . PvSale::ATTR_DATE_PAID . '<=' . $this->_conn->quote($tsTo);
     $query->where("{$whereFrom} AND {$whereTo}");
     // $sql = (string)$query;
     $result = $this->_conn->fetchOne($query);
     return $result;
 }
 /**
  * @param Request\PvWriteOff $request
  *
  * @return Response\PvWriteOff
  */
 public function pvWriteOff(Request\PvWriteOff $request)
 {
     $result = new Response\PvWriteOff();
     $datePerformed = $request->getDatePerformed();
     $this->_logger->info("'PV Write Off' calculation is started.");
     $reqGetPeriod = new PeriodGetForWriteOffRequest();
     $respGetPeriod = $this->_callPeriod->getForWriteOff($reqGetPeriod);
     if ($respGetPeriod->isSucceed()) {
         if ($respGetPeriod->hasNoPvTransactionsYet()) {
             $this->_logger->info("There is no PV transactions yet. Nothing to calculate.");
             $result->markSucceed();
         } else {
             $def = $this->_manTrans->begin();
             try {
                 /* working vars */
                 $periodData = $respGetPeriod->getPeriodData();
                 $periodId = $periodData[Period::ATTR_ID];
                 $calcData = $respGetPeriod->getCalcData();
                 $calcId = $calcData[Calculation::ATTR_ID];
                 $periodBegin = $periodData[Period::ATTR_DSTAMP_BEGIN];
                 $periodEnd = $periodData[Period::ATTR_DSTAMP_END];
                 $this->_logger->info("Processing period #{$periodId} ({$periodBegin}-{$periodEnd}), calculation #{$calcId}.");
                 $transData = $this->_subDb->getDataForWriteOff($calcId, $periodBegin, $periodEnd);
                 $updates = $this->_subCalc->pvWriteOff($transData);
                 $dateApplied = $this->_toolPeriod->getTimestampTo($periodEnd);
                 $operId = $this->_subDb->saveOperationPvWriteOff($updates, $datePerformed, $dateApplied);
                 $this->_subDb->saveLogPvWriteOff($transData, $operId, $calcId);
                 $this->_subDb->markCalcComplete($calcId);
                 $this->_manTrans->commit($def);
                 $result->setPeriodId($periodId);
                 $result->setCalcId($calcId);
                 $result->markSucceed();
             } finally {
                 $this->_manTrans->end($def);
             }
         }
     }
     $this->_logMemoryUsage();
     $this->_logger->info("'PV Write Off' calculation is completed.");
     return $result;
 }
 /**
  * SELECT
  * pps.sale_id,
  * pps.date_paid,
  * sfo.base_grand_total,
  * ce.entity_id
  * FROM prxgt_pv_sale pps
  * LEFT JOIN sales_flat_order sfo
  * ON pps.sale_id = sfo.entity_id
  * LEFT JOIN customer_entity ce
  * ON sfo.customer_id = ce.entity_id
  * WHERE pps.date_paid >= '2016-01-01 00:00:00'
  * AND pps.date_paid <= '2016-01-31 23:59:59'
  *
  * @param $dsBegin - '20160101'
  * @param $dsEnd - '20160131'
  *
  * @return array [ $custId => [$orderId=>[$amount], ... ], ... ]
  */
 public function getSaleOrdersForRebate($dsBegin, $dsEnd)
 {
     $result = [];
     /* aliases and tables */
     $asPvSale = 'pps';
     $asMageSale = 'sfo';
     $asMageCust = 'ce';
     $tblPvSale = $this->_resource->getTableName(PvSale::ENTITY_NAME);
     $tblMageSale = $this->_resource->getTableName(Cfg::ENTITY_MAGE_SALES_ORDER);
     $tblMageCust = $this->_resource->getTableName(Cfg::ENTITY_MAGE_CUSTOMER);
     // FROM prxgt_pv_sale pps
     $query = $this->_conn->select();
     $cols = [PvSale::ATTR_SALE_ID, PvSale::ATTR_DATE_PAID];
     $query->from([$asPvSale => $tblPvSale], $cols);
     // LEFT JOIN sales_flat_order sfo ON pps.sale_id = sfo.entity_id
     $on = "{$asPvSale}." . PvSale::ATTR_SALE_ID . "={$asMageSale}." . Cfg::E_COMMON_A_ENTITY_ID;
     $cols = [Cfg::E_SALE_ORDER_A_BASE_GRAND_TOTAL];
     $query->joinLeft([$asMageSale => $tblMageSale], $on, $cols);
     // LEFT JOIN customer_entity ce ON sfo.customer_id = ce.entity_id
     $on = "{$asMageSale}." . Cfg::E_SALE_ORDER_A_CUSTOMER_ID . "={$asMageCust}." . Cfg::E_CUSTOMER_A_ENTITY_ID;
     $cols = [Cfg::E_CUSTOMER_A_ENTITY_ID];
     $query->joinLeft([$asMageCust => $tblMageCust], $on, $cols);
     // where
     $from = $this->_toolPeriod->getTimestampFrom($dsBegin);
     $to = $this->_toolPeriod->getTimestampTo($dsEnd);
     $whereFrom = PvSale::ATTR_DATE_PAID . '>=' . $this->_conn->quote($from);
     $whereTo = PvSale::ATTR_DATE_PAID . '<=' . $this->_conn->quote($to);
     $wherePv = PvSale::ATTR_TOTAL . ">0";
     $query->where("{$whereFrom} AND {$whereTo} AND {$wherePv}");
     // $sql = (string)$query;
     $data = $this->_conn->fetchAll($query);
     foreach ($data as $item) {
         $custId = $item[Cfg::E_CUSTOMER_A_ENTITY_ID];
         $saleId = $item[PvSale::ATTR_SALE_ID];
         $amount = $item[Cfg::E_SALE_ORDER_A_BASE_GRAND_TOTAL];
         $result[$custId][$saleId] = $amount;
     }
     return $result;
 }
 /**
  * SELECT
  * `pps`.`sale_id`,
  * `sfo`.`customer_id`
  * FROM `prxgt_pv_sale` AS `pps`
  * LEFT JOIN `sales_flat_order` AS `sfo`
  * ON pps.sale_id = sfo.entity_id
  * WHERE (pps.date_paid >= '2016-01-01 08:00:00'
  * AND pps.date_paid <= '2017-01-01 07:59:59')
  *
  * @param string $dsFrom
  * @param string $dsTo
  *
  * @return array
  */
 function getSalesOrdersForPeriod($dsFrom, $dsTo)
 {
     $tsFrom = $this->_toolPeriod->getTimestampFrom($dsFrom);
     $tsTo = $this->_toolPeriod->getTimestampTo($dsTo);
     /* aliases and tables */
     $asPv = 'pps';
     $asOrder = 'sfo';
     $tblPv = $this->_resource->getTableName(PvSale::ENTITY_NAME);
     $tblOrder = $this->_resource->getTableName(Cfg::ENTITY_MAGE_SALES_ORDER);
     // SELECT FROM prxgt_pv_sale pps
     $query = $this->_conn->select();
     $query->from([$asPv => $tblPv], [PvSale::ATTR_SALE_ID, PvSale::ATTR_TOTAL]);
     // LEFT JOIN sales_flat_order sfo ON pps.sale_id = sfo.entity_id
     $on = "{$asPv}." . PvSale::ATTR_SALE_ID . "={$asOrder}." . Cfg::E_SALE_ORDER_A_ENTITY_ID;
     $cols = [Cfg::E_SALE_ORDER_A_CUSTOMER_ID];
     $query->joinLeft([$asOrder => $tblOrder], $on, $cols);
     // where
     $whereFrom = $asPv . '.' . PvSale::ATTR_DATE_PAID . '>=' . $this->_conn->quote($tsFrom);
     $whereTo = $asPv . '.' . PvSale::ATTR_DATE_PAID . '<=' . $this->_conn->quote($tsTo);
     $query->where("{$whereFrom} AND {$whereTo}");
     // $sql = (string)$query;
     $result = $this->_conn->fetchAll($query);
     return $result;
 }
 /**
  * Calculate the last date for existing downline snap or the "yesterday" for the first change log entry.
  *
  * @param Request\GetLastDate $request
  *
  * @return Response\GetLastDate
  */
 public function getLastDate(Request\GetLastDate $request)
 {
     $result = new Response\GetLastDate();
     $this->_logger->info("'Get Last Data' operation is requested.");
     /* get the maximal date for existing snapshot */
     $snapMaxDate = $this->_repoSnap->getMaxDatestamp();
     if ($snapMaxDate) {
         /* there is snapshots data */
         $result->setData([Response\GetLastDate::LAST_DATE => $snapMaxDate]);
         $result->markSucceed();
     } else {
         /* there is no snapshot data yet, get change log minimal date  */
         $changelogMinDate = $this->_repoChange->getChangelogMinDate();
         if ($changelogMinDate) {
             $period = $this->_toolPeriod->getPeriodCurrent($changelogMinDate);
             $dayBefore = $this->_toolPeriod->getPeriodPrev($period);
             $this->_logger->info("The last date for downline snapshot is '{$dayBefore}'.");
             $result->setData([Response\GetLastDate::LAST_DATE => $dayBefore]);
             $result->markSucceed();
         }
     }
     $this->_logger->info("'Get Last Data' operation is completed.");
     return $result;
 }
 /**
  * Calculate downline snapshots by date basing on the last snapshot and change log.
  *
  * We use $currentState array to trace actual state during the changes. Target updates are placed in the $result.
  *
  * @param $currentState
  * @param $changes
  *
  * @return array
  */
 public function calcSnapshots($currentState, $changes)
 {
     $result = [];
     foreach ($changes as $downCustomer) {
         $customerId = $downCustomer[Change::ATTR_CUSTOMER_ID];
         $parentId = $downCustomer[Change::ATTR_PARENT_ID];
         $tsChanged = $downCustomer[Change::ATTR_DATE_CHANGED];
         $dsChanged = $this->_toolPeriod->getPeriodCurrent($tsChanged);
         /* $currentState contains actual state that is updated with changes */
         if (isset($currentState[$customerId])) {
             /* this is update of the existing customer */
             /* write down existing state */
             $currCustomer = $currentState[$customerId];
             $currDepth = $currCustomer[Snap::ATTR_DEPTH];
             $currPath = $currCustomer[Snap::ATTR_PATH];
             /* write down new state */
             if ($customerId == $parentId) {
                 /* this is root node customer */
                 $newDepth = Cfg::INIT_DEPTH;
                 $newPath = Cfg::DTPS;
             } else {
                 /* this is NOT root node customer */
                 $newParent = $currentState[$parentId];
                 $newDepth = $newParent[Snap::ATTR_DEPTH] + 1;
                 $newPath = $newParent[Snap::ATTR_PATH] . $parentId . Cfg::DTPS;
             }
             $customer = [Snap::ATTR_DATE => $dsChanged, Snap::ATTR_CUSTOMER_ID => $customerId, Snap::ATTR_PARENT_ID => $parentId, Snap::ATTR_DEPTH => $newDepth, Snap::ATTR_PATH => $newPath];
             /* we need to update downline's depths & paths for changed customer */
             /* TODO slow code, add ndx if too much slow */
             $key = $currPath . $customerId . Cfg::DTPS;
             $depthDelta = $newDepth - $currDepth;
             $pathReplace = $newPath . $customerId . Cfg::DTPS;
             foreach ($currentState as $downCustomer) {
                 $downPath = $downCustomer[Snap::ATTR_PATH];
                 if (false !== strrpos($downPath, $key, -strlen($downPath))) {
                     /* this is customer from downlilne, we need to change depth & path */
                     $downCustId = $downCustomer[Snap::ATTR_CUSTOMER_ID];
                     $downParentId = $downCustomer[Snap::ATTR_PARENT_ID];
                     $downNewDepth = $downCustomer[Snap::ATTR_DEPTH] + $depthDelta;
                     $downNewPath = str_replace($key, $pathReplace, $downCustomer[Snap::ATTR_PATH]);
                     $downCustomer[Snap::ATTR_DEPTH] = $downNewDepth;
                     $downCustomer[Snap::ATTR_PATH] = $downNewPath;
                     /* add to result updates */
                     $result[$dsChanged][$downCustId] = [Snap::ATTR_DATE => $dsChanged, Snap::ATTR_CUSTOMER_ID => $downCustId, Snap::ATTR_PARENT_ID => $downParentId, Snap::ATTR_DEPTH => $downNewDepth, Snap::ATTR_PATH => $downNewPath];
                 }
             }
         } else {
             /* there is no data for this customer, this is new customer; just add new customer to results */
             if ($customerId == $parentId) {
                 /* this is root node customer */
                 $customer = [Snap::ATTR_DATE => $dsChanged, Snap::ATTR_CUSTOMER_ID => $customerId, Snap::ATTR_PARENT_ID => $customerId, Snap::ATTR_DEPTH => Cfg::INIT_DEPTH, Snap::ATTR_PATH => Cfg::DTPS];
             } else {
                 /* this is NOT root node customer */
                 $parent = $currentState[$parentId];
                 $customer = [Snap::ATTR_DATE => $dsChanged, Snap::ATTR_CUSTOMER_ID => $customerId, Snap::ATTR_PARENT_ID => $parentId, Snap::ATTR_DEPTH => $parent[Snap::ATTR_DEPTH] + 1, Snap::ATTR_PATH => $parent[Snap::ATTR_PATH] . $parentId . Cfg::DTPS];
             }
         }
         $currentState[$customerId] = $customer;
         $result[$dsChanged][$customerId] = $customer;
     }
     return $result;
 }