vendor/launchpad/backend/src/Base/Service/StatsService.php line 39

Open in your IDE?
  1. <?php
  2. namespace LaunchPad\Bundle\LaunchPadBundle\Base\Service;
  3. use Doctrine\ORM\QueryBuilder;
  4. use LaunchPad\Bundle\LaunchPadBundle\Base\Entity\Payment\TopUpRequest;
  5. use LaunchPad\Bundle\LaunchPadBundle\Base\Entity\Transaction\Transaction;
  6. use Symfony\Component\HttpFoundation\Response;
  7. class StatsService extends BaseService
  8. {
  9.     public function getUserStats()
  10.     {
  11.         return [
  12.             'total' => $this->getTotalUsers(),
  13.             'lastWeek' => $this->getLastWeekUsers(),
  14.             'lastMonth' => $this->getLastMonthUsers(),
  15.             'lastYear' => $this->getLastYearUsers()
  16.         ];
  17.     }
  18.     /**
  19.      * Get user trend stats
  20.      *
  21.      * @return array
  22.      * @throws \Exception
  23.      */
  24.     public function getUserTrendStats()
  25.     {
  26.         $resultYearly $this->getUserRepository()
  27.             ->createQueryBuilder('u')
  28.             ->select('DATE_FORMAT(u.created, \'%b\') as MonthOfYear, count(u) as Total')
  29.             ->where('u.created >= :end')
  30.             ->setParameter('end', new \DateTime('-1 year'))
  31.             ->groupBy'MonthOfYear')
  32.             ->getQuery()
  33.             ->getArrayResult();
  34.         $result = array(
  35.             "labels" => array(),
  36.             "data"   => array(),
  37.         );
  38.         foreach($resultYearly as $item) {
  39.             $result['labels'][] = $item['MonthOfYear'];
  40.             $result['data'][] = $item['Total'];
  41.         }
  42.         return $result;
  43.     }
  44.     public function getTransactionTrendStats()
  45.     {
  46.         $resultYearly $this->getTransactionRepository()
  47.             ->createQueryBuilder('u')
  48.             ->select('DATE_FORMAT(u.created, \'%b\') as MonthOfYear, count(u) as Total')
  49.             ->where('u.created >= :end')
  50.             ->setParameter('end', new \DateTime('-1 year'))
  51.             ->groupBy'MonthOfYear')
  52.             ->getQuery()
  53.             ->getArrayResult();
  54.         $result = array(
  55.             "labels" => array(),
  56.             "data"   => array(),
  57.         );
  58.         // TODO: Move to specific function
  59.         $months = [];
  60.         $start    = (new \DateTime('-11 months'))->modify('first day of this month');
  61.         $end      = (new \DateTime())->modify('first day of next month');
  62.         $interval = \DateInterval::createFromDateString('1 month');
  63.         $period   = new \DatePeriod($start$interval$end);
  64.         foreach ($period as $dt) {
  65.             $months[] = $dt->format("M");
  66.         }
  67.         $result['labels'] = $months;
  68.         $result['data'] = array_fill(0count($months), 0);
  69.         foreach($resultYearly as $yearSpending){
  70.             foreach($result['labels'] as $index => $label) {
  71.                 if($label == $yearSpending["MonthOfYear"]) {
  72.                     $result['data'][$index] =  $yearSpending["Total"];
  73.                 }
  74.             }
  75.         }
  76.         return $result;
  77.     }
  78.     /**
  79.      * Get total users
  80.      *
  81.      * @return int|mixed
  82.      * @throws \Doctrine\ORM\NoResultException
  83.      * @throws \Doctrine\ORM\NonUniqueResultException
  84.      */
  85.     public function getTotalUsers()
  86.     {
  87.         $totalUsers $this->getUserRepository()
  88.             ->createQueryBuilder('u')
  89.             ->select('count(u.id) as Total')
  90.             ->getQuery()
  91.             ->getSingleScalarResult();
  92.         return $totalUsers ?? 0;
  93.     }
  94.     /**
  95.      * Get last week users
  96.      *
  97.      * @return int|mixed
  98.      * @throws \Doctrine\ORM\NoResultException
  99.      * @throws \Doctrine\ORM\NonUniqueResultException
  100.      */
  101.     public function getLastWeekUsers()
  102.     {
  103.         $totalUsers $this->getUserRepository()
  104.             ->createQueryBuilder('u')
  105.             ->select('count(u.id) as Total')
  106.             ->where('u.created >= :end')
  107.             ->setParameter('end', new \DateTime('-7 days'))
  108.             ->getQuery()
  109.             ->getSingleScalarResult();
  110.         return $totalUsers ?? 0;
  111.     }
  112.     /**
  113.      * Get last month users
  114.      *
  115.      * @return int|mixed
  116.      * @throws \Doctrine\ORM\NoResultException
  117.      * @throws \Doctrine\ORM\NonUniqueResultException
  118.      */
  119.     public function getLastMonthUsers()
  120.     {
  121.         $totalUsers $this->getUserRepository()
  122.             ->createQueryBuilder('u')
  123.             ->select('count(u.id) as Total')
  124.             ->where('u.created >= :end')
  125.             ->setParameter('end', new \DateTime('-1 month'))
  126.             ->getQuery()
  127.             ->getSingleScalarResult();
  128.         return $totalUsers ?? 0;
  129.     }
  130.     /**
  131.      * Get last year users
  132.      *
  133.      * @return int|mixed
  134.      * @throws \Doctrine\ORM\NoResultException
  135.      * @throws \Doctrine\ORM\NonUniqueResultException
  136.      */
  137.     public function getLastYearUsers()
  138.     {
  139.         $totalUsers $this->getUserRepository()
  140.             ->createQueryBuilder('u')
  141.             ->select('count(u.id) as Total')
  142.             ->where('u.created >= :end')
  143.             ->setParameter('end', new \DateTime('-1 year'))
  144.             ->getQuery()
  145.             ->getSingleScalarResult();
  146.         return $totalUsers ?? 0;
  147.     }
  148.     /**
  149.      * Get totals
  150.      *
  151.      * @param $filters
  152.      * @return array
  153.      * @throws \Doctrine\ORM\NonUniqueResultException
  154.      * @throws \Doctrine\ORM\NoResultException
  155.      */
  156.     public function getTotals($filters)
  157.     {
  158.         $fromDate null;
  159.         $toDate null;
  160.         foreach($filters as $filter) {
  161.             if(!$filter['value']) continue;
  162.             if($filter['name'] == 'fromDate') {
  163.                 $fromDate = \DateTime::createFromFormat('d/m/Y H:i:s'$filter['value'])->format('Y-m-d H:i:s');
  164.             }
  165.             if($filter['name'] == 'toDate') {
  166.                 $toDate = \DateTime::createFromFormat('d/m/Y H:i:s'$filter['value'])->format('Y-m-d H:i:s');
  167.             }
  168.         }
  169.         return array_merge(
  170.             $this->getCardStatistics($fromDate$toDate),
  171.             $this->getTransactionStatistics($fromDate$toDate)
  172.         );
  173.     }
  174.     /**
  175.      * @param $filters
  176.      * @return Response
  177.      * @throws \Doctrine\ORM\NonUniqueResultException
  178.      * @throws \Doctrine\ORM\NoResultException
  179.      */
  180.     public function downloadTotals($filters)
  181.     {
  182.         $totals $this->getTotals($filters);
  183.         // Create csv file
  184.         $output fopen('php://temp''r+');
  185.         foreach ($totals as $label => $value) {
  186.             fputcsv($output, [$this->getLabel($label), $value]);
  187.         }
  188.         rewind($output);
  189.         $csvData '';
  190.         while ($line fgets($output)) {
  191.             $csvData .= $line;
  192.         }
  193.         $csvData .= fgets($output);
  194.         $response = new Response();
  195.         $response->setContent($csvData);
  196.         $response->headers->set('Access-Control-Expose-Headers''Content-Disposition');
  197.         $response->headers->set('Content-Type''text/csv');
  198.         $response->headers->set('Content-Disposition''attachment; filename="report-totals-' date('Y-m-d-H-i-s') . '.csv"');
  199.         return $response;
  200.     }
  201.     /**
  202.      * Get card statistics
  203.      *
  204.      * @param null $fromDate
  205.      * @param null $toDate
  206.      * @return array
  207.      * @throws \Doctrine\ORM\NonUniqueResultException
  208.      */
  209.     private function getCardStatistics($fromDate null$toDate null)
  210.     {
  211.         return [
  212.             'orderedCards' => $this->getOrderedCards($fromDate$toDate),
  213.             'activatedCards' => $this->getActivatedCards($fromDate$toDate),
  214.             'loadedCards' => $this->getLoadedCards($fromDate$toDate),
  215.             'uniqueTransactingCards' => $this->getUniqueTransactingCardCount($fromDate$toDate)
  216.         ];
  217.     }
  218.     /**
  219.      * Get transaction statistics
  220.      *
  221.      * @param null $fromDate
  222.      * @param null $toDate
  223.      * @return array
  224.      * @throws \Doctrine\ORM\NoResultException
  225.      * @throws \Doctrine\ORM\NonUniqueResultException
  226.      */
  227.     private function getTransactionStatistics($fromDate null$toDate null)
  228.     {
  229.         return array_merge(
  230.             $this->getPosTransactionStats($fromDate$toDate),
  231.             $this->getAtmTransactionStats($fromDate$toDate),
  232.             $this->getLoadStats($fromDate$toDate)
  233.         );
  234.     }
  235.     /**
  236.      * Get ordered cards
  237.      *
  238.      * @param $fromDate
  239.      * @param $toDate
  240.      * @return mixed
  241.      * @throws \Doctrine\ORM\NonUniqueResultException
  242.      */
  243.     private function getOrderedCards($fromDate$toDate)
  244.     {
  245.         $query $this->getPaymentDeviceRepository()
  246.             ->createQueryBuilder('x')
  247.             ->select('COUNT(x.id) as orderedCards');
  248.         $this->appendDateConditions($query$fromDate$toDate'created');
  249.         return $query->getQuery()->getSingleScalarResult();
  250.     }
  251.     /**
  252.      * Get activated cards
  253.      *
  254.      * @param $fromDate
  255.      * @param $toDate
  256.      * @return mixed
  257.      * @throws \Doctrine\ORM\NonUniqueResultException
  258.      */
  259.     private function getActivatedCards($fromDate$toDate)
  260.     {
  261.         $query $this->getPaymentDeviceRepository()
  262.             ->createQueryBuilder('x')
  263.             ->select('COUNT(x.id) as activatedCards')
  264.             ->where('x.activated IS NOT NULL');
  265.         $this->appendDateConditions($query$fromDate$toDate'activated');
  266.         return $query->getQuery()->getSingleScalarResult();
  267.     }
  268.     /**
  269.      * Get loaded cards
  270.      *
  271.      * @param $fromDate
  272.      * @param $toDate
  273.      * @return mixed
  274.      * @throws \Doctrine\ORM\NonUniqueResultException
  275.      */
  276.     private function getLoadedCards($fromDate$toDate)
  277.     {
  278.         // TODO: Extract public token in GpsWebService and use that to check for unique card!
  279.         $query $this->getGpsServiceCallRepository()
  280.             ->createQueryBuilder('x')
  281.             ->select('COUNT(DISTINCT IDENTITY(x.user)) as loadedCards')
  282.             ->where('x.success = true');
  283.         $this->appendDateConditions($query$fromDate$toDate'created');
  284.         return $query->getQuery()->getSingleScalarResult();
  285.     }
  286.     /**
  287.      * Get unique transacting card count
  288.      *
  289.      * @param $fromDate
  290.      * @param $toDate
  291.      * @return mixed
  292.      * @throws \Doctrine\ORM\NonUniqueResultException
  293.      */
  294.     private function getUniqueTransactingCardCount($fromDate$toDate)
  295.     {
  296.         $query $this->getTransactionRepository()
  297.             ->createQueryBuilder('x')
  298.             ->select('COUNT(DISTINCT x.paymentDevice) as uniqueTransactingCards');
  299.         $this->appendDateConditions($query$fromDate$toDate'created');
  300.         return $query->getQuery()->getSingleScalarResult();
  301.     }
  302.     /**
  303.      * Get pos transaction stats
  304.      *
  305.      * @param $fromDate
  306.      * @param $toDate
  307.      * @return mixed
  308.      * @throws \Doctrine\ORM\NoResultException
  309.      * @throws \Doctrine\ORM\NonUniqueResultException
  310.      */
  311.     private function getPosTransactionStats($fromDate$toDate)
  312.     {
  313.         $query $this->getTransactionRepository()
  314.             ->createQueryBuilder('x')
  315.             ->select('COUNT(x.id) as posTransactions, ROUND(SUM(x.amount), 2) as posTransactionsValue, ROUND(AVG(x.amount), 2) as posTransactionsAverageValue')
  316.             ->where('x.group = :posType')
  317.             ->andWhere('x.status = :completedStatus')
  318.             ->setParameter('posType'Transaction::GROUP_POS)
  319.             ->setParameter('completedStatus'Transaction::STATUS_COMPLETED);
  320.         $this->appendDateConditions($query$fromDate$toDate'created');
  321.         return $query->getQuery()->getSingleResult();
  322.     }
  323.     /**
  324.      * Get atm transaction stats
  325.      *
  326.      * @param $fromDate
  327.      * @param $toDate
  328.      * @return mixed
  329.      * @throws \Doctrine\ORM\NoResultException
  330.      * @throws \Doctrine\ORM\NonUniqueResultException
  331.      */
  332.     private function getAtmTransactionStats($fromDate$toDate)
  333.     {
  334.         $query $this->getTransactionRepository()
  335.             ->createQueryBuilder('x')
  336.             ->select('COUNT(x.id) as atmTransactions, ROUND(SUM(x.amount), 2) as atmTransactionsValue, ROUND(AVG(x.amount), 2) as atmTransactionsAverageValue')
  337.             ->where('x.group = :posType')
  338.             ->andWhere('x.status = :completedStatus')
  339.             ->setParameter('posType'Transaction::GROUP_ATM)
  340.             ->setParameter('completedStatus'Transaction::STATUS_COMPLETED);
  341.         $this->appendDateConditions($query$fromDate$toDate'created');
  342.         return $query->getQuery()->getSingleResult();
  343.     }
  344.     /**
  345.      * Get load stats
  346.      *
  347.      * @param $fromDate
  348.      * @param $toDate
  349.      * @return mixed
  350.      * @throws \Doctrine\ORM\NoResultException
  351.      * @throws \Doctrine\ORM\NonUniqueResultException
  352.      */
  353.     private function getLoadStats($fromDate$toDate)
  354.     {
  355.         $query $this->getTopUpRequestRepository()
  356.             ->createQueryBuilder('x')
  357.             ->select('COUNT(x.id) as loadsCount, ROUND(SUM(x.amount), 2) as loadsValue, ROUND(AVG(x.amount), 2) as loadsAverageValue')
  358.             ->andWhere('x.status = :completedStatus')
  359.             ->setParameter('completedStatus'TopUpRequest::STATUS_SUCCESS);
  360.         $this->appendDateConditions($query$fromDate$toDate'created');
  361.         return $query->getQuery()->getSingleResult();
  362.     }
  363.     /**
  364.      * Append date conditions
  365.      *
  366.      * @param QueryBuilder $query
  367.      * @param $fromDate
  368.      * @param $toDate
  369.      * @param $field
  370.      */
  371.     private function appendDateConditions(QueryBuilder $query$fromDate$toDate$field)
  372.     {
  373.         if($fromDate) {
  374.             $query->andWhere('x.' $field ' >= :fromDate');
  375.             $query->setParameter('fromDate'$fromDate);
  376.         }
  377.         if($toDate) {
  378.             $query->andWhere('x.' $field ' <= :toDate');
  379.             $query->setParameter('toDate'$toDate);
  380.         }
  381.     }
  382.     /**
  383.      * Get label for stat field
  384.      *
  385.      * @param $label
  386.      * @return mixed
  387.      */
  388.     private function getLabel($label)
  389.     {
  390.         $mapping = [
  391.             "activatedCards" =>  "Activated Cards",
  392.             "atmTransactions" =>  "ATM Transaction Count",
  393.             "atmTransactionsAverageValue" =>  "ATM Transaction Average Value",
  394.             "atmTransactionsValue" =>  "Atm Transactions Value",
  395.             "donationsAverageValue" =>  "Donations Average Value",
  396.             "donationsCount" =>  "Donations Count",
  397.             "donationsTotalValue" =>  "Donations Total Value",
  398.             "undistributedDonationsAverageValue" =>  "Undistributed Donations Average Value",
  399.             "undistributedDonationsCount" =>  "Undistributed Donations Count",
  400.             "undistributedDonationsTotalValue" =>  "Undistributed Donations Total Value",
  401.             "distributedDonationsAverageValue" =>  "Distributed Donations Average Value",
  402.             "distributedDonationsCount" =>  "Distributed Donations Count",
  403.             "distributedDonationsTotalValue" =>  "Distributed Donations Total Value",
  404.             "giftAidNumberOfSignedUp" =>  "Number of Signed Up for GiftAid",
  405.             "giftAidNumberOfOptedOut" =>  "Number of opted out from GiftAid",
  406.             "giftAidPercentageOfSignedUp" =>  "Percentage of Signed Up for GiftAid",
  407.             "giftAidTotalValue" =>  "GiftAid Total Collected",
  408.             "giftAidAverageValue" =>  "Average value of GiftAid donation",
  409.             "loadedCards" =>  "Card Loads",
  410.             "loadsAverageValue" =>  "Average Card Load Value",
  411.             "loadsCount" =>  "Loads Count",
  412.             "loadsValue" =>  "Value Loaded",
  413.             "orderedCards" =>  "Ordered Cards",
  414.             "posTransactions" =>  "POS Transactions Count",
  415.             "posTransactionsAverageValue" =>  "Pos Transaction Average Value",
  416.             "posTransactionsValue" =>  "POS Transactions Value",
  417.             "schoolsRegisteredCount" =>  "Registered Svhools",
  418.             "uniqueTransactingCards" =>  "Unique Transacting Cards Count"
  419.         ];
  420.         if(isset($mapping[$label])) return $mapping[$label];
  421.         return $label;
  422.     }
  423. }