src/Model/UserDashboardModel.php line 1320

Open in your IDE?
  1. <?php
  2. namespace App\Model;
  3. use Pimcore\Db;
  4. use DateTime;
  5. use Pimcore\Model\DataObject\Organization;
  6. use Pimcore\Model\DataObject\Customer;
  7. use Pimcore\Model\DataObject\Location;
  8. use Pimcore\Model\DataObject\MannedAlertSubscription;
  9. use Pimcore\Model\DataObject\MannedAlertLog;
  10. use Pimcore\Model\DataObject\CustomNotification;
  11. use Doctrine\DBAL\Query\QueryBuilder;
  12. use Knp\Component\Pager\PaginatorInterface;
  13. use Doctrine\DBAL\Connection;
  14. use Pimcore\Model\DataObject;
  15. use Carbon\Carbon;
  16. class UserDashboardModel
  17. {
  18.     private Connection $db;
  19.     private PaginatorInterface $paginator;
  20.     public function __construct(PaginatorInterface $paginator)
  21.     {
  22.         $this->db Db::get();
  23.         $this->paginator $paginator;
  24.     }
  25.     public function fetchTotalUsers(
  26.         ?int    $organizationId,
  27.         ?int    $userId,
  28.         ?string $from,
  29.         ?string $to,
  30.         ?string $search    null,
  31.         ?string $sortBy    null,
  32.         string  $sortDir   'ASC',
  33.         ?string $role      null,      // ← new
  34.         ?int $isPublished      null,
  35.         int     $page      1,
  36.         int     $pageSize  20
  37.     ): array {
  38.         $qb $this->db->createQueryBuilder()
  39.             ->select([
  40.                 'rtu.organization_id',
  41.                 'rtu.user_id',
  42.                 'u.name       AS user_name',
  43.                 'u.email      AS user_email',
  44.                 'r.name       AS user_role',
  45.                 'rtu.created_at',
  46.                 'rtu.updated_at',
  47.                 'obj.o_published'
  48.             ])
  49.             ->from('reporting_total_user''rtu')
  50.             ->leftJoin('rtu''object_query_customer''u''u.oo_id = rtu.user_id')
  51.             ->leftJoin('u',   'object_query_user_role''r''r.oo_id = u.role__id')
  52.             ->leftJoin('rtu''objects''obj''obj.o_id = rtu.user_id');
  53.         $this->applyCommonFilters($qb$organizationId$userId$from$to'updated_at');
  54.         if ($search) {
  55.             $expr $qb->expr();
  56.             $qb->andWhere($expr->orX(
  57.                 $expr->like('u.name',  ':search'),
  58.                 $expr->like('u.email'':search'),
  59.                 $expr->like('r.name',  ':search')
  60.             ))
  61.                 ->setParameter('search'"%{$search}%");
  62.         }
  63.         if ($role !== null) {
  64.             $qb->andWhere('r.name = :role')
  65.                 ->setParameter('role'$role);
  66.         }
  67.         if ($isPublished !== null) {
  68.             $qb->andWhere('obj.o_published = :published')
  69.                 ->setParameter('published'$isPublished);
  70.         }
  71.         $allowed = [
  72.             'organization_id',
  73.             'user_id',
  74.             'user_name',
  75.             'user_email',
  76.             'user_role',
  77.             'created_at',
  78.             'updated_at'
  79.         ];
  80.         if ($sortBy && in_array($sortBy$allowedtrue)) {
  81.             $qb->orderBy($sortBystrtoupper($sortDir) === 'DESC' 'DESC' 'ASC');
  82.         }
  83.         // ─── KNP pagination ───────────────────────────────────────────────────────
  84.         $pagination $this->paginator->paginate(
  85.             $qb,      // the QueryBuilder
  86.             $page,    // current page number
  87.             $pageSize // items per page
  88.         );
  89.         $rows       $pagination->getItems();               // the slice of items
  90.         $totalItems $pagination->getTotalItemCount();      // total across all pages
  91.         $perPage    $pagination->getItemNumberPerPage();   // your $pageSize
  92.         $current    $pagination->getCurrentPageNumber();   // your $page
  93.         // hydrate your data exactly as before
  94.         $data = [];
  95.         foreach ($rows as $r) {
  96.             $org Organization::getById($r['organization_id']);
  97.             $cust \Pimcore\Model\DataObject::getById($r['user_id']);
  98.             $subscriptionListing = new \Pimcore\Model\DataObject\Subscription\Listing();
  99.             //$subscriptionListing->setCondition('endDate >= ?', [time()]);
  100.             $subscriptionListing->filterBySubscriptionType("custom");
  101.             //$subscriptionListing->filterByIsActive(true);
  102.             $subscriptionListing->filterBySubscribedUser($cust);
  103.             $subscription $subscriptionListing->current();
  104.             $data[] = [
  105.                 'id'          => $r['user_id'],
  106.                 'name'        => $r['user_name'],
  107.                 'email'       => $r['user_email'],
  108.                 'role'        => $r['user_role'],
  109.                 'isPublished' => Customer::getById($r['user_id'])?->getPublished(),
  110.                 'organization_id' => $r['organization_id'],
  111.                 'created_at' => $r['created_at'],
  112.                 'updated_at' => $r['updated_at'],
  113.                 'is_published'  => $cust->getPublished() ? true false,
  114.                 'user_status' => $cust->getPublished() ? "active" "pending",
  115.                 'package_name' => ($subscription) ? $subscription->getSubscribedPackage()->getPackageName() : "",
  116.                 'subscription_start_date' => ($subscription) ? $subscription->getStartDate()->format("Y-m-d") : "",
  117.                 'subscription_end_date' => ($subscription) ? $subscription->getEndDate()->format("Y-m-d") : "",
  118.             ];
  119.         }
  120.         return [
  121.             'success'    => true,
  122.             'data'       => $data,
  123.             'paginationVariables' => $pagination->getPaginationData(),
  124.         ];
  125.     }
  126.     public function fetchActiveUsers(
  127.         ?int    $organizationId,
  128.         ?int    $userId,
  129.         ?string $from,
  130.         ?string $to,
  131.         ?string $search    null,
  132.         ?string $sortBy    null,
  133.         string  $sortDir   'ASC',
  134.         ?string $role      null,      // ← new
  135.         int     $page      1,
  136.         int     $pageSize  20
  137.     ): array {
  138.         $qb $this->db->createQueryBuilder()
  139.             ->select([
  140.                 'rau.organization_id',
  141.                 'rau.user_id',
  142.                 'u.name       AS user_name',
  143.                 'u.email      AS user_email',
  144.                 'r.name       AS user_role',
  145.                 'rau.created_at',
  146.                 'rau.updated_at'
  147.             ])
  148.             ->from('reporting_active_user''rau')
  149.             ->leftJoin('rau''object_query_customer',   'u''u.oo_id = rau.user_id')
  150.             ->leftJoin('u',   'object_query_user_role',  'r''r.oo_id = u.role__id');
  151.         $this->applyCommonFilters($qb$organizationId$userId$from$to'created_at');
  152.         if ($search) {
  153.             $expr $qb->expr();
  154.             $qb->andWhere($expr->orX(
  155.                 $expr->like('u.name',  ':search'),
  156.                 $expr->like('u.email'':search'),
  157.                 $expr->like('r.name',  ':search')
  158.             ))
  159.                 ->setParameter('search'"%{$search}%");
  160.         }
  161.         if ($role !== null) {
  162.             $qb->andWhere('r.name = :role')
  163.                 ->setParameter('role'$role);
  164.         }
  165.         $allowed = [
  166.             'organization_id',
  167.             'user_id',
  168.             'user_name',
  169.             'user_email',
  170.             'user_role',
  171.             'created_at',
  172.             'updated_at'
  173.         ];
  174.         if ($sortBy && in_array($sortBy$allowedtrue)) {
  175.             $qb->orderBy($sortBystrtoupper($sortDir) === 'DESC' 'DESC' 'ASC');
  176.         }
  177.         // KNP pagination
  178.         $pagination $this->paginator->paginate($qb$page$pageSize);
  179.         $rows       $pagination->getItems();
  180.         $totalItems $pagination->getTotalItemCount();
  181.         $perPage    $pagination->getItemNumberPerPage();
  182.         $current    $pagination->getCurrentPageNumber();
  183.         $data = [];
  184.         foreach ($rows as $r) {
  185.             $org Organization::getById($r['organization_id']);
  186.             $data[] = [
  187.                 'id'          => $r['user_id'],
  188.                 'name'        => $r['user_name'],
  189.                 'email'       => $r['user_email'],
  190.                 'role'        => $r['user_role'],
  191.                 'isPublished' => Customer::getById($r['user_id'])?->getPublished(),
  192.                 'organization_id' => $r['organization_id'],
  193.                 'created_at' => $r['created_at'],
  194.                 'updated_at' => $r['updated_at'],
  195.             ];
  196.         }
  197.         return [
  198.             'success'    => true,
  199.             'data'       => $data,
  200.             'paginationVariables' => $pagination->getPaginationData(),
  201.         ];
  202.     }
  203.     public function fetchTotalLocations(
  204.         ?int    $organizationId,
  205.         ?int    $userId,
  206.         ?string $from,
  207.         ?string $to,
  208.         ?string $search    null,
  209.         ?string $sortBy    null,
  210.         string  $sortDir   'ASC',
  211.         int     $page      1,
  212.         int     $pageSize  20
  213.     ): array {
  214.         $qb $this->db->createQueryBuilder()
  215.             ->select([
  216.                 'rl.organization_id',
  217.                 'rl.user_id',
  218.                 'rl.location_id',
  219.                 'l.name       AS name_en',
  220.                 'l.title      AS name_ar',
  221.                 'u.name       AS user_name',
  222.                 'u.email      AS user_email',
  223.                 'r.name       AS user_role',
  224.                 'rl.created_at',
  225.                 'rl.updated_at'
  226.             ])
  227.             ->from('reporting_total_location''rl')
  228.             ->leftJoin('rl''object_query_location',  'l''l.oo_id = rl.location_id')
  229.             ->leftJoin('rl''object_query_customer',  'u''u.oo_id = rl.user_id')
  230.             ->leftJoin('u',  'object_query_user_role''r''r.oo_id = u.role__id');
  231.         $this->applyCommonFilters($qb$organizationId$userId$from$to'updated_at');
  232.         if ($search) {
  233.             $expr $qb->expr();
  234.             $qb->andWhere($expr->orX(
  235.                 $expr->like('l.name',    ':search'),
  236.                 $expr->like('l.title',   ':search'),
  237.                 $expr->like('u.name',    ':search'),
  238.                 $expr->like('u.email',   ':search'),
  239.                 $expr->like('r.name',    ':search')
  240.             ))
  241.                 ->setParameter('search'"%{$search}%");
  242.         }
  243.         $allowed = [
  244.             'organization_id',
  245.             'user_id',
  246.             'location_id',
  247.             'name_en',
  248.             'name_ar',
  249.             'user_name',
  250.             'user_email',
  251.             'user_role',
  252.             'created_at',
  253.             'updated_at'
  254.         ];
  255.         if ($sortBy && in_array($sortBy$allowedtrue)) {
  256.             $qb->orderBy($sortBystrtoupper($sortDir) === 'DESC' 'DESC' 'ASC');
  257.         }
  258.         // KNP pagination
  259.         $pagination $this->paginator->paginate($qb$page$pageSize);
  260.         $rows       $pagination->getItems();
  261.         $totalItems $pagination->getTotalItemCount();
  262.         $perPage    $pagination->getItemNumberPerPage();
  263.         $current    $pagination->getCurrentPageNumber();
  264.         $data = [];
  265.         foreach ($rows as $r) {
  266.             // $org  = Organization::getById($r['organization_id']);
  267.             // $cust = Customer::getById($r['user_id']);
  268.             $data[] = [
  269.                 'id'          => $r['location_id'],
  270.                 'name_en'     => $r['name_en'],
  271.                 'name_ar'     => $r['name_ar'],
  272.                 'isPublished' => \Pimcore\Model\DataObject\Location::getById($r['location_id'])?->getPublished(),
  273.                 'created_at' => $r['created_at'],
  274.                 'updated_at' => $r['updated_at'],
  275.             ];
  276.         }
  277.         return [
  278.             'success'    => true,
  279.             'data'       => $data,
  280.             'paginationVariables' => $pagination->getPaginationData(),
  281.         ];
  282.     }
  283.     public function fetchEwsSubscriptions(
  284.         ?int    $organizationId,
  285.         ?int    $userId,
  286.         ?string $from,
  287.         ?string $to,
  288.         ?string $search    null,
  289.         ?string $sortBy    null,
  290.         string  $sortDir   'ASC',
  291.         int     $page      1,
  292.         int     $pageSize  20,
  293.         string  $lang      'en'// Default to English
  294.         bool $isSubscribed null
  295.     ): array {
  296.         $qb $this->db->createQueryBuilder()
  297.             ->select([
  298.                 's.oo_id                  AS subscription_id',
  299.                 's.isSubscribed           AS is_subscribed',
  300.                 'u.name                   AS user_name',
  301.                 'u.email                  AS user_email',
  302.                 'r.name                   AS user_role',
  303.                 'region_en.name              AS region_name_en',
  304.                 'region_ar.name              AS region_name_ar',
  305.                 'FROM_UNIXTIME(s.o_creationDate)     AS created_at',
  306.                 'FROM_UNIXTIME(s.o_modificationDate) AS updated_at'
  307.             ])
  308.             ->from('object_MannedAlertSubscription''s')
  309.             ->leftJoin('s''object_query_customer',   'u''u.oo_id = s.creator__id')
  310.             ->leftJoin('u''object_query_user_role',  'r''r.oo_id = u.role__id')
  311.             ->leftJoin('s''object_localized_query_region_en',     'region_en''region_en.ooo_id = s.region__id')
  312.             ->leftJoin('s''object_localized_query_region_ar',     'region_ar''region_ar.ooo_id = s.region__id');
  313.         // Filter by userId if provided
  314.         if ($userId !== null) {
  315.             $qb->andWhere('u.oo_id = :usr')
  316.                 ->setParameter('usr'$userId);
  317.         }
  318.         if ($isSubscribed !== null) {
  319.             $qb->andWhere('s.isSubscribed = :subscribed')
  320.                 ->setParameter('subscribed'$isSubscribed);
  321.         }
  322.         // Date filter: from/to on o_creationDate (UNIX timestamp)
  323.         if ($from) {
  324.             $fromTs = (new \DateTime($from))->setTime(000)->getTimestamp();
  325.             $qb->andWhere('s.o_creationDate >= :from')
  326.                 ->setParameter('from'$fromTs);
  327.         }
  328.         if ($to) {
  329.             $toTs = (new \DateTime($to))->setTime(235959)->getTimestamp();
  330.             $qb->andWhere('s.o_creationDate <= :to')
  331.                 ->setParameter('to'$toTs);
  332.         }
  333.         if ($search) {
  334.             $expr $qb->expr();
  335.             $qb->andWhere($expr->orX(
  336.                 $expr->like('u.name',  ':search'),
  337.                 $expr->like('u.email'':search'),
  338.                 $expr->like('r.name',  ':search'),
  339.                 $lang == 'en'
  340.                     $expr->like('region_en.name'':search')
  341.                     : $expr->like('region_ar.name'':search')
  342.             ))
  343.                 ->setParameter('search'"%{$search}%");
  344.         }
  345.         $allowed = ['subscription_id''user_name''user_email''user_role''created_at''updated_at''region_name_en''region_name_ar'];
  346.         if ($sortBy && in_array($sortBy$allowedtrue)) {
  347.             $qb->orderBy($sortBystrtoupper($sortDir) === 'DESC' 'DESC' 'ASC');
  348.         }
  349.         // KNP pagination
  350.         $pagination $this->paginator->paginate($qb$page$pageSize);
  351.         $rows       $pagination->getItems();
  352.         $totalItems $pagination->getTotalItemCount();
  353.         $perPage    $pagination->getItemNumberPerPage();
  354.         $current    $pagination->getCurrentPageNumber();
  355.         $data = [];
  356.         foreach ($rows as $r) {
  357.             $sub MannedAlertSubscription::getById((int)$r['subscription_id']);
  358.             if (!$sub) {
  359.                 continue;
  360.             }
  361.             // … your existing region/alertType/governorate/phenomena hydration …
  362.             $phenomenas = [];
  363.             if ($sub->getAlertStatus()) {
  364.                 foreach ($sub->getAlertStatus() as $subscriptionAlertStatus) {
  365.                     $phenomenas[] = [
  366.                         "id" => $subscriptionAlertStatus->getAlertStatusId(),
  367.                         "en" => $subscriptionAlertStatus->getName('en'),
  368.                         "ar" => $subscriptionAlertStatus->getName('ar')
  369.                     ];
  370.                 }
  371.             }
  372.             $data[] = [
  373.                 'id'         => (int)$r['subscription_id'],
  374.                 'name'  => $r['user_name'],
  375.                 'email' => $r['user_email'],
  376.                 'role'  => $r['user_role'],
  377.                 'is_subscribed' => (bool) $r['is_subscribed'],
  378.                 'region_name_en' => $r['region_name_en'],
  379.                 'region_name_ar' => $r['region_name_ar'],
  380.                 'alert_status' => $phenomenas,
  381.                 'created_at' => $r['created_at'],
  382.                 'updated_at' => $r['updated_at'],
  383.             ];
  384.         }
  385.         return [
  386.             'success'    => true,
  387.             'data'       => $data,
  388.             'paginationVariables' => $pagination->getPaginationData(),
  389.         ];
  390.     }
  391.     public function fetchReceivedEmails(
  392.         ?int    $organizationId,
  393.         ?int    $userId,
  394.         ?string $from,
  395.         ?string $to,
  396.         ?string $search    null,
  397.         int     $page      1,
  398.         int     $pageSize  20
  399.     ): array {
  400.         // 1) Build base query and apply filters
  401.         $qb $this->buildBaseQuery('reporting_received_emails');
  402.         $this->applyCommonFilters($qb$organizationId$userId$from$to'sent_date');
  403.         // 2) (Optional) search across 'sent_to' or any other columns
  404.         if ($search) {
  405.             $expr $qb->expr();
  406.             $qb->andWhere($expr->like('sent_to'':search'))
  407.                 ->setParameter('search'"%{$search}%");
  408.         }
  409.         // 3) KNP pagination
  410.         $pagination $this->paginator->paginate($qb$page$pageSize);
  411.         $rows       $pagination->getItems();
  412.         $totalItems $pagination->getTotalItemCount();
  413.         $perPage    $pagination->getItemNumberPerPage();
  414.         $current    $pagination->getCurrentPageNumber();
  415.         // 4) Hydrate data
  416.         $data = [];
  417.         foreach ($rows as $r) {
  418.             $log MannedAlertLog::getById($r['log_id']);
  419.             $data[] = [
  420.                 'log_id'    => $r['log_id'],
  421.                 'classType' => $log?->getClassType(),
  422.                 'purpose'   => $log?->getPurpose(),
  423.                 'sent_to'   => $r['sent_to'],
  424.                 'sent_date' => $r['sent_date'],
  425.             ];
  426.         }
  427.         // 5) Return with full pagination block
  428.         return [
  429.             'success'    => true,
  430.             'data'       => $data,
  431.             'paginationVariables' => $pagination->getPaginationData(),
  432.         ];
  433.     }
  434.     public function fetchCustomNotifications(
  435.         ?int    $organizationId,
  436.         ?int    $userId,
  437.         ?int    $type,
  438.         ?string $from,
  439.         ?string $to,
  440.         ?string $search    null,
  441.         ?string $sortBy    null,
  442.         string  $sortDir   'ASC',
  443.         int     $page      1,
  444.         int     $pageSize  20
  445.     ): array {
  446.         // 1) Build query with joins
  447.         $qb $this->db->createQueryBuilder()
  448.             ->select([
  449.                 'rcn.organization_id',
  450.                 'rcn.user_id',
  451.                 'rcn.notification_id',
  452.                 'rcn.type',
  453.                 'cn.title',
  454.                 'cn.color',
  455.                 'cn.units',
  456.                 'cn.minValue',
  457.                 'cn.maxValue',
  458.                 'u.name       AS user_name',
  459.                 'u.email      AS user_email',
  460.                 'r.name       AS user_role',
  461.                 'rcn.created_at'
  462.             ])
  463.             ->from('reporting_custom_notification''rcn')
  464.             ->leftJoin('rcn''object_query_custom_notification''cn''cn.oo_id = rcn.notification_id')
  465.             ->leftJoin('rcn''object_query_customer',            'u''u.oo_id = rcn.user_id')
  466.             ->leftJoin('u',   'object_query_user_role',           'r''r.oo_id = u.role__id');
  467.         // 2) Apply common org/user/date filters
  468.         $this->applyCommonFilters($qb$organizationId$userId$from$to'created_at');
  469.         // 3) Optional type filter: 1 = Basic, 2 = Advance
  470.         if ($type !== null) {
  471.             $qb->andWhere('rcn.type = :type')
  472.                 ->setParameter('type'$type);
  473.         }
  474.         // 4) Optional search
  475.         if ($search) {
  476.             $expr $qb->expr();
  477.             $qb->andWhere($expr->orX(
  478.                 $expr->like('cn.title',   ':search'),
  479.                 $expr->like('u.name',     ':search'),
  480.                 $expr->like('u.email',    ':search'),
  481.                 $expr->like('r.name',     ':search')
  482.             ))
  483.                 ->setParameter('search'"%{$search}%");
  484.         }
  485.         // 5) Sorting
  486.         $allowed = [
  487.             'organization_id',
  488.             'user_id',
  489.             'notification_id',
  490.             'title',
  491.             'color',
  492.             'units',
  493.             'minValue',
  494.             'maxValue',
  495.             'user_name',
  496.             'user_email',
  497.             'user_role',
  498.             'created_at',
  499.             'type'
  500.         ];
  501.         if ($sortBy && in_array($sortBy$allowedtrue)) {
  502.             $qb->orderBy($sortBystrtoupper($sortDir) === 'DESC' 'DESC' 'ASC');
  503.         }
  504.         // 6) Pagination
  505.         $pagination $this->paginator->paginate($qb$page$pageSize);
  506.         $rows       $pagination->getItems();
  507.         // 7) Hydrate
  508.         $data = [];
  509.         foreach ($rows as $r) {
  510.             $data[] = [
  511.                 'id'         => $r['notification_id'],
  512.                 'title'      => $r['title'],
  513.                 'color'      => $r['color'],
  514.                 'units'      => $r['units'],
  515.                 'minValue'   => $r['minValue'],
  516.                 'maxValue'   => $r['maxValue'],
  517.                 'created_at' => $r['created_at'],
  518.                 'type'       => isset($r['type']) ? $r['type'] : null,
  519.             ];
  520.         }
  521.         return [
  522.             'success'              => true,
  523.             'data'                 => $data,
  524.             'paginationVariables'  => $pagination->getPaginationData(),
  525.         ];
  526.     }
  527.     public function fetchAddedLocations(
  528.         ?int    $organizationId,
  529.         ?int    $userId,
  530.         ?string $from,
  531.         ?string $to,
  532.         ?string $search,
  533.         int     $page     1,
  534.         int     $pageSize 20
  535.     ): array {
  536.         return $this->fetchTotalLocations($organizationId$userId$from$to$page$pageSize);
  537.     }
  538.     private function buildBaseQuery(string $table): QueryBuilder
  539.     {
  540.         return $this->db->createQueryBuilder()
  541.             ->select('*')
  542.             ->from($table);
  543.     }
  544.     /**
  545.      * Fetch per‐user login details: summary + paginated list.
  546.      *
  547.      * @return array{
  548.      *   success: bool,
  549.      *   summary: array{ totalAttempts: int, successAttempts: int, failureAttempts: int, lastLogin: string|null },
  550.      *   count: int,
  551.      *   data: array<int, array{ id: int, user: array, organization: array, status: int, attempt_time: string, ip_address: string|null, user_agent: string|null }>
  552.      * }
  553.      */
  554.     public function fetchUserLoginDetails(
  555.         ?int    $organizationId,
  556.         ?int    $userId,
  557.         ?string $from,
  558.         ?string $to,
  559.         ?string $search,
  560.         int     $page     1,
  561.         int     $pageSize 20
  562.     ): array {
  563.         // Use a COLLATE on the join to force both sides to utf8mb4_unicode_ci
  564.         $joinCondition "ua.customer_email COLLATE utf8mb4_unicode_ci = c.email";
  565.         // 1) Summary
  566.         $summaryQb $this->db->createQueryBuilder()
  567.             ->select(
  568.                 'COUNT(*) AS total',
  569.                 'SUM(CASE WHEN ua.status = 1 THEN 1 ELSE 0 END) AS success',
  570.                 'SUM(CASE WHEN ua.status = 0 THEN 1 ELSE 0 END) AS failure',
  571.                 'MAX(CASE WHEN ua.status = 1 THEN ua.attempt_time ELSE NULL END) AS last_login',
  572.                 'MAX(CASE WHEN ua.status = 0 THEN ua.attempt_time ELSE NULL END) AS last_failed_login' // Added for last failed login
  573.             )
  574.             ->from('user_login_attempts''ua')
  575.             ->leftJoin('ua''object_query_customer''c'$joinCondition);
  576.         $this->applyLoginFilters($summaryQb$organizationId$userId$from$to);
  577.         $summaryRow $summaryQb->execute()->fetchAssociative();
  578.         // 2) Summary
  579.         $emailDataQb $this->db->createQueryBuilder()
  580.             ->select(
  581.                 'COUNT(id) AS totalEmailReceived',
  582.                 'SUM(CASE WHEN re.email_type = "Customer" THEN 1 ELSE 0 END) AS totalOtpEmailReceived',
  583.                 'SUM(CASE WHEN re.email_type = "CustomNotification" THEN 1 ELSE 0 END) AS totalBasicCustomNotificationEmailReceived',
  584.                 'SUM(CASE WHEN re.email_type = "AdvanceCustomNotification" THEN 1 ELSE 0 END) AS totalAdvanceCustomNotificationEmailReceived',
  585.                 'SUM(CASE WHEN re.email_type = "EwsNotification" THEN 1 ELSE 0 END) AS totalEwsAlertEmailReceived'
  586.             )
  587.             ->from('reporting_received_emails''re'// Reporting received emails table
  588.             ->where('re.user_id = :userId');
  589.         $emailDataQb->setParameter('userId'$userId);
  590.         // Execute the query
  591.         $emailDataSummary $emailDataQb->execute()->fetchAssociative();
  592.         // 2) Detail query
  593.         $detailQb $this->db->createQueryBuilder()
  594.             ->select(
  595.                 'ua.id',
  596.                 'ua.customer_email',
  597.                 'c.oo_id              AS user_id',
  598.                 'c.organization__id   AS organization_id',
  599.                 'ua.status',
  600.                 'ua.attempt_time',
  601.                 'ua.ip_address',
  602.                 'ua.user_agent'
  603.             )
  604.             ->from('user_login_attempts''ua')
  605.             ->leftJoin('ua''object_query_customer''c'$joinCondition);
  606.         $this->applyLoginFilters($detailQb$organizationId$userId$from$to);
  607.         // 3) KNP pagination
  608.         $pagination  $this->paginator->paginate($detailQb$page$pageSize);
  609.         $rows        $pagination->getItems();
  610.         $totalItems  $pagination->getTotalItemCount();
  611.         $perPage     $pagination->getItemNumberPerPage();
  612.         $currentPage $pagination->getCurrentPageNumber();
  613.         // 4) Hydrate
  614.         $data = [];
  615.         $data["summary"] = [
  616.             'totalAttempts'   => (int)$summaryRow['total'],
  617.             'successAttempts' => (int)$summaryRow['success'],
  618.             'failureAttempts' => (int)$summaryRow['failure'],
  619.             'lastLogin'       => $summaryRow['last_login'],
  620.             'lastFailedLogin' => $summaryRow['last_failed_login'], // Added for last failed login
  621.             'totalEmailReceived' => (int) $emailDataSummary['totalEmailReceived'] ?? 0,
  622.             'totalOtpEmailReceived' => (int) $emailDataSummary['totalOtpEmailReceived'] ?? 0,
  623.             'totalBasicCustomNotificationEmailReceived' => (int) $emailDataSummary['totalBasicCustomNotificationEmailReceived'] ?? 0,
  624.             'totalAdvanceCustomNotificationEmailReceived' => (int) $emailDataSummary['totalAdvanceCustomNotificationEmailReceived'] ?? 0,
  625.             'totalEwsAlertEmailReceived' => (int) $emailDataSummary['totalEwsAlertEmailReceived'] ?? 0
  626.         ];
  627.         $cust \Pimcore\Model\DataObject\Customer::getById($userId);
  628.         if (!$cust) {
  629.             return [
  630.                 'success'    => false,
  631.                 'message'       => "User not found"
  632.             ];
  633.         }
  634.         $org  $cust->getOrganization();
  635.         $subscriptionListing = new \Pimcore\Model\DataObject\Subscription\Listing();
  636.         // $subscriptionListing->setCondition('endDate >= ?', [time()]);
  637.         $subscriptionListing->filterBySubscriptionType("custom");
  638.         // $subscriptionListing->filterByIsActive(true);
  639.         $subscriptionListing->filterBySubscribedUser($cust);
  640.         $subscription $subscriptionListing->current();
  641.         $data["user"] = [
  642.             'id'    => $cust->getId(),
  643.             'email' => $cust->getEmail(),
  644.             'name'  => $cust?->getName(),
  645.             'role'  => $cust?->getRole()?->getName(),
  646.             'is_active'  => $cust->getPublished() ? true false,
  647.             'is_expired' => ($subscription) ? false true,
  648.             'is_pending'  => !$cust->getPublished() ? true false,
  649.             'user_status' => $cust->getPublished() ? "active" "pending",
  650.             'package_name' => ($subscription) ? $subscription->getSubscribedPackage()->getPackageName() : "",
  651.             'subscription_start_date' => ($subscription) ? $subscription->getStartDate()->format("Y-m-d") : "",
  652.             'subscription_end_date' => ($subscription) ? $subscription->getEndDate()->format("Y-m-d") : "",
  653.         ];
  654.         $data["organization"] = [
  655.             'id'             => $org?->getId(),
  656.             'name_en'        => $org?->getName('en'),
  657.             'name_ar'        => $org?->getName('ar'),
  658.             'smsLimit'       => $org?->getSmsLimit(),
  659.             'smsConsumption' => $org?->getSmsConsumption(),
  660.             'smsBalance'     => $org ? (int)$org->getSmsLimit() -  (int)$org->getSmsConsumption() : null,
  661.             'organization_status' => $this->determineOrganizationStatus($org),
  662.         ];
  663.         $data["attempts"] = [];
  664.         foreach ($rows as $r) {
  665.             $data["attempts"][] = [
  666.                 'status'       => (int)$r['status'],
  667.                 'attempt_time' => $r['attempt_time'],
  668.                 'ip_address'   => $r['ip_address'],
  669.                 'user_agent'   => $r['user_agent'],
  670.             ];
  671.         }
  672.         // 5) Return with pagination block
  673.         return [
  674.             'success'    => true,
  675.             'data'       => $data,
  676.             'paginationVariables' => $pagination->getPaginationData(),
  677.         ];
  678.     }
  679.     /**
  680.      * Applies org, user and date-range filters.
  681.      * Date filters now cover the full day: from 00:00:00 to 23:59:59.
  682.      */
  683.     private function applyCommonFilters(
  684.         QueryBuilder $qb,
  685.         ?int    $organizationId,
  686.         ?int    $userId,
  687.         ?string $from,
  688.         ?string $to,
  689.         string  $dateField 'created_at'
  690.     ): void {
  691.         $expr $qb->expr();
  692.         if ($organizationId !== null) {
  693.             $qb->andWhere($expr->eq('organization_id'':org'))
  694.                 ->setParameter('org'$organizationId);
  695.         }
  696.         if ($userId !== null) {
  697.             $qb->andWhere($expr->eq('user_id'':usr'))
  698.                 ->setParameter('usr'$userId);
  699.         }
  700.         if ($from) {
  701.             // start of day
  702.             $fromTs = (new DateTime($from))->format('Y-m-d') . ' 00:00:00';
  703.             $qb->andWhere($expr->gte($dateField':from'))
  704.                 ->setParameter('from'$fromTs);
  705.         }
  706.         if ($to) {
  707.             // end of day
  708.             $toTs = (new DateTime($to))->format('Y-m-d') . ' 23:59:59';
  709.             $qb->andWhere($expr->lte($dateField':to'))
  710.                 ->setParameter('to'$toTs);
  711.         }
  712.     }
  713.     /**
  714.      * Apply organization, user‐ID, and date‐range filters to a login‐attempts QB.
  715.      */
  716.     private function applyLoginFilters(
  717.         QueryBuilder $qb,
  718.         ?int    $organizationId,
  719.         ?int    $userId,
  720.         ?string $from,
  721.         ?string $to
  722.     ): void {
  723.         $expr $qb->expr();
  724.         if ($organizationId) {
  725.             $qb->andWhere($expr->eq('c.organization__id'':org'))
  726.                 ->setParameter('org'$organizationId);
  727.         }
  728.         if ($userId) {
  729.             $qb->andWhere($expr->eq('c.oo_id'':usr'))
  730.                 ->setParameter('usr'$userId);
  731.         }
  732.         if ($from) {
  733.             $fromTs = (new DateTime($from))->format('Y-m-d') . ' 00:00:00';
  734.             $qb->andWhere($expr->gte('ua.attempt_time'':from'))
  735.                 ->setParameter('from'$fromTs);
  736.         }
  737.         if ($to) {
  738.             $toTs = (new DateTime($to))->format('Y-m-d') . ' 23:59:59';
  739.             $qb->andWhere($expr->lte('ua.attempt_time'':to'))
  740.                 ->setParameter('to'$toTs);
  741.         }
  742.     }
  743.     /**
  744.      * Adds a LIKE‐OR group across all given column names.
  745.      */
  746.     private function applySearchFilter(QueryBuilder $qb, ?string $search, array $columns): void
  747.     {
  748.         if (!$search) {
  749.             return;
  750.         }
  751.         $expr $qb->expr();
  752.         $orX  $expr->orX();
  753.         foreach ($columns as $col) {
  754.             $orX->add($expr->like($col':search'));
  755.         }
  756.         $qb->andWhere($orX)
  757.             ->setParameter('search''%' $search '%');
  758.     }
  759.     /**
  760.      * Return total counts for each report entity, grouped by organization (localized names),
  761.      * optionally filtering by organization name, and sorting.
  762.      *
  763.      * @param string|null $search   substring to match against name_en or name_ar
  764.      * @param string|null $sortBy   one of: organization_id, name_en, name_ar,
  765.      *                              total_users, active_users, total_locations,
  766.      *                              total_ews_subscriptions, total_custom_notifications,
  767.      *                              total_received_emails, total_added_locations
  768.      * @param string      $sortDir  ASC or DESC
  769.      */
  770.     public function fetchEntityCounts(
  771.         ?int    $organizationId null,
  772.         ?string $search         null,
  773.         ?string $sortBy         null,
  774.         string  $sortDir        'DESC',
  775.         int     $page           1,
  776.         int     $pageSize       20
  777.     ): array {
  778.         // 1) Base SQL
  779.         $sql = <<<SQL
  780.     SELECT 
  781.         o.oo_id                      AS organization_id,
  782.         en.name                      AS name_en,
  783.         ar.name                      AS name_ar,
  784.         o.clientType                 AS client_type,
  785.         pkg.packageName              AS package_name,
  786.         pkg.maxUsers                 AS total_user_allowed, 
  787.         COALESCE(o.SmsConsumption, 0) AS total_sms_consumed,
  788.         COALESCE(rtu.total, 0)       AS total_users,
  789.         COALESCE(rau.total, 0)       AS active_users,
  790.         COALESCE(rtl.total, 0)       AS total_locations,
  791.         COALESCE(res.total, 0)       AS total_ews_subscriptions,
  792.         COALESCE(rcn.total, 0)       AS total_custom_notifications,
  793.         COALESCE(rre.total, 0)       AS total_received_emails,
  794.         COALESCE(ral.total, 0)       AS total_added_locations,
  795.         COUNT(DISTINCT CASE 
  796.             WHEN c.o_published = 1 AND s.subscribedUser__id IS NOT NULL AND r.name IN ('CLIENT_USER', 'CLIENT_ADMIN') AND (s.isWso IS NULL OR s.isWso = 0)
  797.                 AND (s.isActive = 1)
  798.             THEN c.oo_id ELSE NULL END) AS total_active_user,
  799.         COUNT(DISTINCT CASE 
  800.             WHEN c.o_published = 1 AND s.subscribedUser__id IS NOT NULL AND r.name IN ('CLIENT_USER', 'CLIENT_ADMIN') AND (s.isWso IS NULL OR s.isWso = 0)
  801.                 AND (s.isActive = 0)
  802.             THEN c.oo_id ELSE NULL END) AS total_suspended_user,
  803.         COUNT(DISTINCT CASE 
  804.             WHEN c.token IS NOT NULL AND c.o_published = 0 AND r.name IN ('CLIENT_USER', 'CLIENT_ADMIN') AND (s.isWso IS NULL OR s.isWso = 0)
  805.             THEN c.oo_id ELSE NULL END) AS total_pending_user,
  806.         
  807.         COUNT(DISTINCT CASE 
  808.             WHEN  r.name ='CLIENT_ADMIN' AND (s.isWso IS NULL OR s.isWso = 0)
  809.             THEN c.oo_id ELSE NULL END) AS total_admin_user,
  810.         COUNT(DISTINCT CASE 
  811.                 WHEN c.jwtToken IS NOT NULL 
  812.                     AND c.jwtToken <> '' 
  813.                     AND c.jwtTokenExpiry > UNIX_TIMESTAMP()  -- Valid token and not expired
  814.                 THEN c.oo_id 
  815.                 ELSE NULL 
  816.             END) AS total_real_login_users
  817.         FROM object_query_organization o
  818.         LEFT JOIN object_localized_query_organization_en en 
  819.         ON en.ooo_id = o.oo_id
  820.         LEFT JOIN object_localized_query_organization_ar ar 
  821.         ON ar.ooo_id = o.oo_id
  822.         LEFT JOIN object_localized_packages_en pkg
  823.         ON o.package__id = pkg.oo_id
  824.         LEFT JOIN (
  825.         SELECT organization_id, COUNT(*) AS total
  826.         FROM reporting_total_user
  827.         GROUP BY organization_id
  828.         ) rtu ON rtu.organization_id = o.oo_id
  829.         LEFT JOIN (
  830.         SELECT organization_id, COUNT(*) AS total
  831.         FROM reporting_active_user
  832.         GROUP BY organization_id
  833.         ) rau ON rau.organization_id = o.oo_id
  834.         LEFT JOIN (
  835.         SELECT organization_id, COUNT(*) AS total
  836.         FROM reporting_total_location
  837.         GROUP BY organization_id
  838.         ) rtl ON rtl.organization_id = o.oo_id
  839.         LEFT JOIN (
  840.         SELECT organization_id, COUNT(*) AS total
  841.         FROM reporting_ews_subscription
  842.         GROUP BY organization_id
  843.         ) res ON res.organization_id = o.oo_id
  844.         LEFT JOIN (
  845.         SELECT organization_id, COUNT(*) AS total
  846.         FROM reporting_custom_notification
  847.         GROUP BY organization_id
  848.         ) rcn ON rcn.organization_id = o.oo_id
  849.         LEFT JOIN (
  850.         SELECT organization_id, COUNT(*) AS total
  851.         FROM reporting_received_emails
  852.         GROUP BY organization_id
  853.         ) rre ON rre.organization_id = o.oo_id
  854.         LEFT JOIN (
  855.         SELECT organization_id, COUNT(*) AS total
  856.         FROM reporting_added_locations
  857.         GROUP BY organization_id
  858.         ) ral ON ral.organization_id = o.oo_id
  859.         LEFT JOIN object_customer c
  860.             ON c.organization__id = o.oo_id
  861.         LEFT JOIN object_subscription s 
  862.             ON s.subscribedUser__id = c.oo_id
  863.         LEFT JOIN object_user_role r ON r.oo_id = c.role__id
  864.         WHERE (o.isDeleted IS NULL OR o.isDeleted = 0) AND (o.isInternal IS NULL OR o.isInternal = 0)
  865.     
  866.     SQL;
  867.         $params = [];
  868.         // 2) Optional organization filter
  869.         if ($organizationId !== null) {
  870.             $sql .= "\nAND o.oo_id = :org";
  871.             $params['org']  = $organizationId;
  872.         }
  873.         // 3) Apply search filter
  874.         if ($search) {
  875.             $clause = isset($params['org']) ? 'AND' 'AND';
  876.             $sql    .= "\n{$clause} (en.name LIKE :search OR ar.name LIKE :search)";
  877.             $params['search'] = "%{$search}%";
  878.         }
  879.         $sql .= " GROUP BY o.oo_id, en.name, ar.name, pkg.packageName, pkg.maxUsers, o.SmsConsumption, rtu.total, rau.total, rtl.total, res.total, rcn.total, rre.total, ral.total";
  880.         $allowed = [
  881.             'organization_id',
  882.             'name_en',
  883.             'name_ar',
  884.             'client_type',
  885.             'package_name',
  886.             'total_user_allowed',
  887.             'total_sms_consumed',
  888.             'total_users',
  889.             'active_users',
  890.             'total_locations',
  891.             'total_ews_subscriptions',
  892.             'total_custom_notifications',
  893.             'total_received_emails',
  894.             'total_added_locations',
  895.             'total_suspended_user',
  896.             'total_active_user',
  897.             'total_pending_user',
  898.             'total_real_login_users',
  899.             'total_admin_user'
  900.         ];
  901.         if ($sortBy && in_array($sortBy$allowedtrue)) {
  902.             $dir strtoupper($sortDir) === 'DESC' 'DESC' 'ASC';
  903.             $sql .= "\nORDER BY {$sortBy} {$dir}";
  904.         } else {
  905.             // Default sort by latest organization ID
  906.             $sql .= "\nORDER BY o.oo_id DESC";
  907.         }
  908.         $all $this->db->executeQuery($sql$params)->fetchAll(\PDO::FETCH_ASSOC);
  909.         $pagination $this->paginator->paginate($all$page$pageSize);
  910.         // 7) Summary counts
  911.         $totalEntities = (int) $this->db->executeQuery("SELECT COUNT(*) FROM object_query_organization WHERE (isDeleted IS NULL OR isDeleted = 0) AND (isInternal IS NULL OR isInternal = 0)")->fetchOne();
  912.         $totalOrganizations = (int) $this->db->executeQuery("SELECT COUNT(*) FROM object_query_organization WHERE clientType = 'organization' AND (isDeleted IS NULL OR isDeleted = 0) AND (isInternal IS NULL OR isInternal = 0)")->fetchOne();
  913.         $totalGovernmentEntities = (int) $this->db->executeQuery("SELECT COUNT(*) FROM object_query_organization WHERE clientType = 'government' AND (isDeleted IS NULL OR isDeleted = 0) AND (isInternal IS NULL OR isInternal = 0)")->fetchOne();
  914.         // ✅ Updated user status logic for global counts
  915.         $userStatusSql = <<<SQL
  916.         SELECT
  917.             COUNT(DISTINCT CASE 
  918.                 WHEN c.o_published = 1 AND s.subscribedUser__id IS NOT NULL AND r.name IN ('CLIENT_USER', 'CLIENT_ADMIN') AND (s.isWso IS NULL OR s.isWso = 0) AND s.subscriptionType = 'custom'
  919.                     AND (s.isActive = 1)
  920.                 THEN c.oo_id ELSE NULL END) AS total_active,
  921.             COUNT(DISTINCT CASE 
  922.                 WHEN c.o_published = 1 AND s.subscribedUser__id IS NOT NULL AND r.name IN ('CLIENT_USER', 'CLIENT_ADMIN') AND (s.isWso IS NULL OR s.isWso = 0)
  923.                     AND (s.isActive = 0)
  924.                 THEN c.oo_id ELSE NULL END) AS total_suspended,
  925.             COUNT(DISTINCT CASE 
  926.                 WHEN c.token IS NOT NULL AND c.o_published = 0 AND r.name IN ('CLIENT_USER', 'CLIENT_ADMIN') AND (s.isWso IS NULL OR s.isWso = 0) ANd s.subscriptionType != 'custom'
  927.                 THEN c.oo_id ELSE NULL END) AS total_pending
  928.         FROM object_customer c 
  929.         LEFT JOIN object_subscription s ON s.subscribedUser__id = c.oo_id
  930.         LEFT JOIN object_user_role r ON r.oo_id = c.role__id
  931.         WHERE (c.isDeleted IS NULL OR c.isDeleted = 0)
  932.         SQL;
  933.         $userStatus $this->db->executeQuery($userStatusSql)->fetchAssociative();
  934.         // 8) Per-organization enrichment
  935.         $orgResult = [];
  936.         $items $pagination->getItems();
  937.         if ($items) {
  938.             $counter 0;
  939.             foreach ($items as $item) {
  940.                 $organization \Pimcore\Model\DataObject::getById($item["organization_id"]);
  941.                 if ($organization) {
  942.                     $orgResult[$counter] = $item;
  943.                     $counter++;
  944.                 }
  945.             }
  946.         }
  947.         // 9) Return final data
  948.         return [
  949.             'success' => true,
  950.             'summary' => [
  951.                 'total_entities' => $totalEntities,
  952.                 'client_type_entity' => $totalOrganizations,
  953.                 'client_type_government' => $totalGovernmentEntities,
  954.                 'user_status_counts' => [
  955.                     'active'    => (int) ($userStatus['total_active'] ?? 0),
  956.                     'pending'   => (int) ($userStatus['total_pending'] ?? 0),
  957.                     'suspended' => (int) ($userStatus['total_suspended'] ?? 0), // ✅ UPDATED here
  958.                 ],
  959.             ],
  960.             'data' => $orgResult,
  961.             'paginationVariables' => $pagination->getPaginationData(),
  962.         ];
  963.     }
  964.     /**
  965.      * Return high-level summary counts for a given user (and optional date range).
  966.      *
  967.      * @param int|null    $userId
  968.      * @param string|null $from    YYYY-MM-DD
  969.      * @param string|null $to      YYYY-MM-DD
  970.      * @return array{ success: bool, summary: array{ emailsSent: int, smsSent: int, ewsCount: int, customNotifications: int, passwordResetEmails: int } }
  971.      */
  972.     public function userAnalytics(
  973.         ?int    $userId null,
  974.         ?string $from   null,
  975.         ?string $to     null
  976.     ): array {
  977.         // helper to apply date filters
  978.         $applyDate = function (QueryBuilder $qbstring $aliasstring $dateCol) use ($from$to) {
  979.             if ($from) {
  980.                 $qb->andWhere("$alias.$dateCol >= :from")
  981.                     ->setParameter('from', (new DateTime($from))->format('Y-m-d') . ' 00:00:00');
  982.             }
  983.             if ($to) {
  984.                 $qb->andWhere("$alias.$dateCol <= :to")
  985.                     ->setParameter('to', (new DateTime($to))->format('Y-m-d') . ' 23:59:59');
  986.             }
  987.         };
  988.         // 1) Emails sent
  989.         $qbEmails $this->db->createQueryBuilder()
  990.             ->select('COUNT(*)')
  991.             ->from('reporting_received_emails''re');
  992.         if ($userId !== null) {
  993.             $qbEmails->andWhere('re.user_id = :usr')
  994.                 ->setParameter('usr'$userId);
  995.         }
  996.         $applyDate($qbEmails're''sent_date');
  997.         $emailsSent = (int)$qbEmails->execute()->fetchOne();
  998.         // 2) Total SMS sent
  999.         //    (If you have a per-user SMS log table, swap this to count that instead;
  1000.         //     otherwise you’ll need to track SMS per user differently.)
  1001.         $qbSms $this->db->createQueryBuilder()
  1002.             ->select('COALESCE(SUM(o.SmsConsumption), 0)')
  1003.             ->from('object_query_organization''o')
  1004.             ->leftJoin('o''object_query_customer''c''c.organization__id = o.oo_id');
  1005.         if ($userId !== null) {
  1006.             $qbSms->andWhere('c.oo_id = :usr')
  1007.                 ->setParameter('usr'$userId);
  1008.         }
  1009.         $smsSent = (int)$qbSms->execute()->fetchOne();
  1010.         // 3) EWS subscription count
  1011.         $qbEws $this->db->createQueryBuilder()
  1012.             ->select('COUNT(*)')
  1013.             ->from('reporting_ews_subscription''s');
  1014.         if ($userId !== null) {
  1015.             $qbEws->andWhere('s.user_id = :usr')
  1016.                 ->setParameter('usr'$userId);
  1017.         }
  1018.         $applyDate($qbEws's''created_at');
  1019.         $ewsCount = (int)$qbEws->execute()->fetchOne();
  1020.         // 4) Custom notification count
  1021.         $qbCustom $this->db->createQueryBuilder()
  1022.             ->select('COUNT(*)')
  1023.             ->from('reporting_custom_notification''rcn');
  1024.         if ($userId !== null) {
  1025.             $qbCustom->andWhere('rcn.user_id = :usr')
  1026.                 ->setParameter('usr'$userId);
  1027.         }
  1028.         $applyDate($qbCustom'rcn''created_at');
  1029.         $customNotifications = (int)$qbCustom->execute()->fetchOne();
  1030.         // 5) Password-reset email count
  1031.         $qbPw $this->db->createQueryBuilder()
  1032.             ->select('COUNT(*)')
  1033.             ->from('reporting_received_emails''re')
  1034.             ->leftJoin('re''object_MannedAlertLog''l''l.oo_id = re.log_id')
  1035.             ->andWhere('l.purpose = :purpose')
  1036.             ->setParameter('purpose''Email Send For Reset Password');
  1037.         if ($userId !== null) {
  1038.             $qbPw->andWhere('re.user_id = :usr')
  1039.                 ->setParameter('usr'$userId);
  1040.         }
  1041.         $applyDate($qbPw're''sent_date');
  1042.         $passwordResetEmails = (int)$qbPw->execute()->fetchOne();
  1043.         return [
  1044.             'success' => true,
  1045.             'summary' => [
  1046.                 'emailsSent'          => $emailsSent,
  1047.                 'smsSent'             => $smsSent,
  1048.                 'ewsCount'            => $ewsCount,
  1049.                 'customNotifications' => $customNotifications,
  1050.                 'passwordResetEmails' => $passwordResetEmails,
  1051.             ],
  1052.         ];
  1053.     }
  1054.     public function getLocationsExcel(
  1055.         ?int    $userId,
  1056.         ?string $from,
  1057.         ?string $to,
  1058.         string  $sortDir   'ASC',
  1059.         string  $lang      'en',
  1060.         $translator
  1061.     ): array {
  1062.         // Build query
  1063.         $qb $this->db->createQueryBuilder()
  1064.             ->select([
  1065.                 'rl.organization_id',
  1066.                 'rl.user_id',
  1067.                 'rl.location_id',
  1068.                 'l.name       AS name_en',
  1069.                 'l.title      AS name_ar',
  1070.                 'u.name       AS user_name',
  1071.                 'u.email      AS user_email',
  1072.                 'r.name       AS user_role',
  1073.                 'rl.created_at',
  1074.                 'rl.updated_at'
  1075.             ])
  1076.             ->from('reporting_total_location''rl')
  1077.             ->leftJoin('rl''object_query_location',  'l''l.oo_id = rl.location_id')
  1078.             ->leftJoin('rl''object_query_customer',  'u''u.oo_id = rl.user_id')
  1079.             ->leftJoin('u',  'object_query_user_role''r''r.oo_id = u.role__id');
  1080.         // $this->applyCommonFilters($qb, $userId, $from, $to, 'updated_at');
  1081.         $allowed = [
  1082.             'organization_id',
  1083.             'user_id',
  1084.             'location_id',
  1085.             'name_en',
  1086.             'name_ar',
  1087.             'user_name',
  1088.             'user_email',
  1089.             'user_role',
  1090.             'created_at',
  1091.             'updated_at'
  1092.         ];
  1093.         if ($sortDir && in_array($sortDir$allowedtrue)) {
  1094.             $qb->orderBy($sortDirstrtoupper($sortDir) === 'DESC' 'DESC' 'ASC');
  1095.         }
  1096.         $rows $qb->execute()->fetchAllAssociative();
  1097.         $data = [];
  1098.         if (count($rows) > 0) {
  1099.             // Header row
  1100.             $data[] = [
  1101.                 'S. No'         => 'S. No',
  1102.                 'User Name'     => 'User Name',
  1103.                 'User Email'    => 'User Email',
  1104.                 'User Role'     => 'User Role',
  1105.                 'Location ID'   => 'Location ID',
  1106.                 'Location Name' => 'Location Name (EN)',
  1107.                 'Location Name (AR)' => 'Location Name (AR)',
  1108.                 'Created At'    => 'Created At',
  1109.                 'Updated At'    => 'Updated At'
  1110.             ];
  1111.             foreach ($rows as $index => $row) {
  1112.                 $data[] = [
  1113.                     'S. No'         => $index 1,
  1114.                     'User Name'     => $row['user_name'],
  1115.                     'User Email'    => $row['user_email'],
  1116.                     'User Role'     => $row['user_role'],
  1117.                     'Location ID'   => $row['location_id'],
  1118.                     'Location Name' => $row['name_en'],
  1119.                     'Location Name (AR)' => $row['name_ar'],
  1120.                     'Created At'    => $row['created_at'],
  1121.                     'Updated At'    => $row['updated_at']
  1122.                 ];
  1123.             }
  1124.             // You must have ExcelGenerator and $translator available in your context
  1125.             $excelData \App\Lib\ExcelGenerator::createAndSaveXlsx($data"locations_excel"true'/User_dashboard/excel/Locations/');
  1126.             return [
  1127.                 "success" => true,
  1128.                 "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1129.                 "data"    => $excelData
  1130.             ];
  1131.         } else {
  1132.             return [
  1133.                 "success" => false,
  1134.                 "message" => isset($translator) ? $translator->trans("records_not_found") : "Records not found"
  1135.             ];
  1136.         }
  1137.     }
  1138.     public function getEntityExcel(
  1139.         ?int    $userId,
  1140.         ?string $from,
  1141.         ?string $to,
  1142.         string  $sortDir   'ASC',
  1143.         string  $lang      'en',
  1144.         $translator
  1145.     ): array {
  1146.         // Build query
  1147.         $entityData $this->fetchEntityCounts(
  1148.             null,
  1149.             null,
  1150.             null,
  1151.             $sortDir,
  1152.             1,
  1153.             10000
  1154.         );
  1155.         //    dd($entityData['data']);
  1156.         $rows $entityData['data'];
  1157.         $data = [];
  1158.         if (count($rows) > 0) {
  1159.             // Header row
  1160.             $data[] = [
  1161.                 'S. No'         => 'S. No',
  1162.                 'Entity Name'     => 'Entity Name',
  1163.                 'Package'    => 'Package',
  1164.                 'Allocated User Count'     => 'Allocated User Count',
  1165.                 'Total Users'   => 'Total Users',
  1166.                 'Active Users' => 'Active Users',
  1167.                 'Pending Users' => 'Pending Users',
  1168.                 'Total Locations'    => 'Total Locations',
  1169.                 'Added Locations'    => 'Added Locations',
  1170.                 'Alerts Subscription' => 'Alerts Subscription',
  1171.                 'Custom Notifications' => 'Custom Notifications',
  1172.                 'Received Emails' => 'Received Emails'
  1173.             ];
  1174.             foreach ($rows as $index => $row) {
  1175.                 $data[] = [
  1176.                     'S. No'         => $index 1,
  1177.                     'Entity Name'     => $row['name_en'],
  1178.                     'Package'    => $row['package_name'],
  1179.                     'Allocated User Count' => $row['total_user_allowed'],
  1180.                     'Total Users'   => $row['total_users'],
  1181.                     'Active Users' => $row['active_users'],
  1182.                     'Pending Users' => $row['total_pending_user'],
  1183.                     'Total Locations'    => $row['total_locations'],
  1184.                     'Added Locations'    => $row['total_locations'],
  1185.                     'Alerts Subscription' => $row['total_ews_subscriptions'],
  1186.                     'Custom Notifications' => $row['total_custom_notifications'],
  1187.                     'Received Emails' => $row['total_received_emails']
  1188.                 ];
  1189.             }
  1190.             // You must have ExcelGenerator and $translator available in your context
  1191.             $excelData \App\Lib\ExcelGenerator::createAndSaveXlsx($data"entity_excel"true'/User_dashboard/excel/Entities/');
  1192.             return [
  1193.                 "success" => true,
  1194.                 "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1195.                 "data"    => $excelData
  1196.             ];
  1197.         } else {
  1198.             return [
  1199.                 "success" => false,
  1200.                 "message" => isset($translator) ? $translator->trans("records_not_found") : "Records not found"
  1201.             ];
  1202.         }
  1203.     }
  1204.     public function getEntityDetailExcel(
  1205.         ?int    $organizationId,
  1206.         ?int    $userId,
  1207.         ?string $from,
  1208.         ?string $to,
  1209.         ?string $search    null,
  1210.         ?string $sortBy    null,
  1211.         string  $sortDir   'ASC',
  1212.         ?string $role      null,      // ← new
  1213.         ?int $isPublished      null,
  1214.         int     $page      1,
  1215.         int     $pageSize  20,
  1216.         string  $lang      'en'// Default to English
  1217.         $translator
  1218.     ): array {
  1219.         // Build query
  1220.         $entityData $this->fetchTotalUsers(
  1221.             $organizationId,
  1222.             $userId,
  1223.             $from,
  1224.             $to,
  1225.             $search,
  1226.             $sortBy,
  1227.             $sortDir,
  1228.             $role,
  1229.             $isPublished,
  1230.             $page,
  1231.             $pageSize
  1232.         );
  1233.         //    dd($entityData);
  1234.         $rows $entityData['data'];
  1235.         $data = [];
  1236.         if (count($rows) > 0) {
  1237.             // Header row
  1238.             $data[] = [
  1239.                 'S. No'         => 'S. No',
  1240.                 'Email'     => 'Entity Name',
  1241.                 'Role'    => 'Role',
  1242.                 'Created On'   => 'Created On',
  1243.                 'Package Name'    => 'Package Name',
  1244.                 'Package Expiry' => 'Package Expiry',
  1245.                 'Status' => 'Status',
  1246.             ];
  1247.             foreach ($rows as $index => $row) {
  1248.                 $data[] = [
  1249.                     'S. No'         => $index 1,
  1250.                     'Email'     => $row['email'],
  1251.                     'Role'    => $row['role'],
  1252.                     'Created On'   => $row['created_at'],
  1253.                     'Package Name'    => $row['package_name'],
  1254.                     'Package Expiry' => $row['subscription_end_date'] ?? 'N/A',
  1255.                     'Status' => $row['user_status'] ?? 'N/A'
  1256.                 ];
  1257.             }
  1258.             // You must have ExcelGenerator and $translator available in your context
  1259.             $excelData \App\Lib\ExcelGenerator::createAndSaveXlsx($data"entity_details_excel"true'/User_dashboard/excel/Entity-details/');
  1260.             return [
  1261.                 "success" => true,
  1262.                 "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1263.                 "data"    => $excelData
  1264.             ];
  1265.         } else {
  1266.             return [
  1267.                 "success" => false,
  1268.                 "message" => isset($translator) ? $translator->trans("records_not_found") : "Records not found"
  1269.             ];
  1270.         }
  1271.     }
  1272.     public function getEntityUserNotificationExcel(
  1273.         ?int    $organizationId,
  1274.         ?int    $userId,
  1275.         ?int    $type,
  1276.         ?string $from,
  1277.         ?string $to,
  1278.         ?string $search    null,
  1279.         ?string $sortBy    null,
  1280.         string  $sortDir   'ASC',
  1281.         int     $page      1,
  1282.         int     $pageSize  20,
  1283.         string  $lang      'en'// Default to English
  1284.         $translator
  1285.     ): array {
  1286.         // Build query
  1287.         $entityData $this->fetchCustomNotifications(
  1288.             $organizationId,
  1289.             $userId,
  1290.             $type,
  1291.             $from,
  1292.             $to,
  1293.             $search,
  1294.             $sortBy,
  1295.             $sortDir,
  1296.             $page,
  1297.             $pageSize
  1298.         );
  1299.         //    dd($entityData);
  1300.         $rows $entityData['data'];
  1301.         $data = [];
  1302.         if (count($rows) > 0) {
  1303.             // Header row
  1304.             $data[] = [
  1305.                 'S. No'         => 'S. No',
  1306.                 'Notification Title'     => 'Notification Title',
  1307.                 'Type'    => 'Type',
  1308.                 'Created On'   => 'Created On',
  1309.             ];
  1310.             foreach ($rows as $index => $row) {
  1311.                 $data[] = [
  1312.                     'S. No'         => $index 1,
  1313.                     'Notification Title'     => $row['title'],
  1314.                     'Type'    => $row['type'] == 'Custom' 'System',
  1315.                     'Created On'   => $row['created_at'],
  1316.                 ];
  1317.             }
  1318.             // You must have ExcelGenerator and $translator available in your context
  1319.             $excelData \App\Lib\ExcelGenerator::createAndSaveXlsx($data"entity_user_notifications_excel"true'/User_dashboard/excel/entity-user-notifications-excel/');
  1320.             return [
  1321.                 "success" => true,
  1322.                 "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1323.                 "data"    => $excelData
  1324.             ];
  1325.         } else {
  1326.             return [
  1327.                 "success" => false,
  1328.                 "message" => isset($translator) ? $translator->trans("records_not_found") : "Records not found"
  1329.             ];
  1330.         }
  1331.     }
  1332.     public function getEntityUserEwsSubscriptionsExcel(
  1333.         ?int    $organizationId,
  1334.         ?int    $userId,
  1335.         ?string $from,
  1336.         ?string $to,
  1337.         ?string $search    null,
  1338.         ?string $sortBy    null,
  1339.         string  $sortDir   'ASC',
  1340.         int     $page      1,
  1341.         int     $pageSize  20,
  1342.         string  $lang      'en'// Default to English
  1343.         $translator
  1344.     ): array {
  1345.         // Build query
  1346.         $entityData $this->fetchEwsSubscriptions(
  1347.             $organizationId,
  1348.             $userId,
  1349.             $from,
  1350.             $to,
  1351.             $search,
  1352.             $sortBy,
  1353.             $sortDir,
  1354.             $page,
  1355.             $pageSize,
  1356.             $lang
  1357.         );
  1358.         //    dd($entityData);
  1359.         $rows $entityData['data'];
  1360.         $data = [];
  1361.         if (count($rows) > 0) {
  1362.             // Header row
  1363.             $data[] = [
  1364.                 'S. No'         => 'S. No',
  1365.                 'Region Name'     => 'Region Name',
  1366.                 'Subscription Date'    => 'Subscription Date',
  1367.                 'Status'   => 'Status',
  1368.             ];
  1369.             foreach ($rows as $index => $row) {
  1370.                 $data[] = [
  1371.                     'S. No'         => $index 1,
  1372.                     'Region Name'     => $row['region_name_en'],
  1373.                     'Subscription Date'    => $row['created_at'],
  1374.                     'Status'   => 'Active'// Assuming all subscriptions are active
  1375.                 ];
  1376.             }
  1377.             // You must have ExcelGenerator and $translator available in your context
  1378.             $excelData \App\Lib\ExcelGenerator::createAndSaveXlsx($data"entity_user_ews_subscription_excel"true'/User_dashboard/excel/entity-user-ews-subscription-excel/');
  1379.             return [
  1380.                 "success" => true,
  1381.                 "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1382.                 "data"    => $excelData
  1383.             ];
  1384.         } else {
  1385.             return [
  1386.                 "success" => false,
  1387.                 "message" => isset($translator) ? $translator->trans("records_not_found") : "Records not found"
  1388.             ];
  1389.         }
  1390.     }
  1391.     public function getEntityUserTotalLocationsExcel(
  1392.         ?int    $organizationId,
  1393.         ?int    $userId,
  1394.         ?string $from,
  1395.         ?string $to,
  1396.         ?string $search    null,
  1397.         ?string $sortBy    null,
  1398.         string  $sortDir   'ASC',
  1399.         int     $page      1,
  1400.         int     $pageSize  20,
  1401.         string  $lang      'en'// Default to English
  1402.         $translator
  1403.     ): array {
  1404.         // Build query
  1405.         $entityData $this->fetchTotalLocations(
  1406.             $organizationId,
  1407.             $userId,
  1408.             $from,
  1409.             $to,
  1410.             $search,
  1411.             $sortBy,
  1412.             $sortDir,
  1413.             $page,
  1414.             $pageSize,
  1415.             $lang
  1416.         );
  1417.         $rows $entityData['data'];
  1418.         $data = [];
  1419.         if (count($rows) > 0) {
  1420.             // Header row
  1421.             $data[] = [
  1422.                 'S. No'         => 'S. No',
  1423.                 'Added Locations'     => 'Added Locations',
  1424.                 'Created On' => 'Created On',
  1425.                 'Updated On' => 'Updated On',
  1426.             ];
  1427.             foreach ($rows as $index => $row) {
  1428.                 $data[] = [
  1429.                     'S. No'         => $index 1,
  1430.                     'Added Locations'     => $row['name_en'],
  1431.                     'Created On' => $row['created_at'],
  1432.                     'Updated On' => $row['updated_at'],
  1433.                 ];
  1434.             }
  1435.             // You must have ExcelGenerator and $translator available in your context
  1436.             $excelData \App\Lib\ExcelGenerator::createAndSaveXlsx($data"entity_user_locations_excel"true'/User_dashboard/excel/entity-user-locations-excel/');
  1437.             return [
  1438.                 "success" => true,
  1439.                 "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1440.                 "data"    => $excelData
  1441.             ];
  1442.         } else {
  1443.             return [
  1444.                 "success" => false,
  1445.                 "message" => isset($translator) ? $translator->trans("records_not_found") : "Records not found"
  1446.             ];
  1447.         }
  1448.     }
  1449.     private function determineOrganizationStatus($org): string
  1450.     {
  1451.         $today Carbon::now();
  1452.         if ($org?->getStatus() === 'trial') {
  1453.             $limitDays = (int) $org?->getTrialLimit();
  1454.             $activationDate Carbon::parse($org?->getPackageActivationDate());
  1455.             return $today->greaterThan($activationDate->copy()->addDays($limitDays)) ? 'expired' 'active';
  1456.         }
  1457.         if ($org?->getStatus() === 'expired') {
  1458.             return 'expired';
  1459.         }
  1460.         $limitDays = (int) $org?->getTrialLimit();
  1461.         $activationDate Carbon::parse($org?->getPackageActivationDate());
  1462.         return $today->greaterThan($activationDate->copy()->addDays($limitDays)) ? 'expired' 'active';
  1463.     }
  1464.     public function getEntityUserCollectiveExcel(
  1465.         ?int    $organizationId,
  1466.         ?int    $userId,
  1467.         ?string $from,
  1468.         ?string $to,
  1469.         ?string $search    null,
  1470.         ?string $sortBy    null,
  1471.         string  $sortDir   'ASC',
  1472.         string  $lang      'en'// Default to English
  1473.         $translator
  1474.     ): array {
  1475.         $allSheets = [];
  1476.         // Get User Login Details first (needed for User Details sheet)
  1477.         $loginData $this->fetchUserLoginDetails(
  1478.             $organizationId,
  1479.             $userId,
  1480.             $from,
  1481.             $to,
  1482.             $search,
  1483.             1,
  1484.             1000000
  1485.         );
  1486.         // 1. User Details Sheet (FIRST SHEET)
  1487.         $userDetailsSheetData = [];
  1488.         if (isset($loginData['data']['user']) && isset($loginData['data']['summary'])) {
  1489.             $user $loginData['data']['user'];
  1490.             $summary $loginData['data']['summary'];
  1491.             $attempts $loginData['data']['attempts'] ?? [];
  1492.             // Calculate attempts from the attempts array
  1493.             $successAttempts 0;
  1494.             $failedAttempts 0;
  1495.             $successAttemptDates = [];
  1496.             $failedAttemptDates = [];
  1497.             foreach ($attempts as $attempt) {
  1498.                 if ($attempt['status'] == 1) {
  1499.                     $successAttempts++;
  1500.                     // Format date to "August 05, 2025 04:55 PM" format
  1501.                     try {
  1502.                         // Handle different date formats
  1503.                         $attemptTime $attempt['attempt_time'];
  1504.                         if (is_numeric($attemptTime)) {
  1505.                             // If it's a timestamp
  1506.                             $date = new \DateTime('@' $attemptTime);
  1507.                         } else {
  1508.                             // If it's a date string
  1509.                             $date = new \DateTime($attemptTime);
  1510.                         }
  1511.                         // Set timezone to Asia/Riyadh as per user preference
  1512.                         $date->setTimezone(new \DateTimeZone('Asia/Riyadh'));
  1513.                         $successAttemptDates[] = $date->format('F d, Y g:i A');
  1514.                     } catch (\Exception $e) {
  1515.                         // Fallback to original format if parsing fails
  1516.                         $successAttemptDates[] = $attempt['attempt_time'];
  1517.                     }
  1518.                 } elseif ($attempt['status'] == 0) {
  1519.                     $failedAttempts++;
  1520.                     // Format date to "August 05, 2025 04:55 PM" format
  1521.                     try {
  1522.                         // Handle different date formats
  1523.                         $attemptTime $attempt['attempt_time'];
  1524.                         if (is_numeric($attemptTime)) {
  1525.                             // If it's a timestamp
  1526.                             $date = new \DateTime('@' $attemptTime);
  1527.                         } else {
  1528.                             // If it's a date string
  1529.                             $date = new \DateTime($attemptTime);
  1530.                         }
  1531.                         // Set timezone to Asia/Riyadh as per user preference
  1532.                         $date->setTimezone(new \DateTimeZone('Asia/Riyadh'));
  1533.                         $failedAttemptDates[] = $date->format('F d, Y g:i A');
  1534.                     } catch (\Exception $e) {
  1535.                         // Fallback to original format if parsing fails
  1536.                         $failedAttemptDates[] = $attempt['attempt_time'];
  1537.                     }
  1538.                 }
  1539.             }
  1540.             // Determine package status based on subscription
  1541.             $packageStatus 'Active';
  1542.             if (isset($user['subscription_end_date']) && !empty($user['subscription_end_date'])) {
  1543.                 $endDate = new \DateTime($user['subscription_end_date']);
  1544.                 $today = new \DateTime();
  1545.                 if ($today $endDate) {
  1546.                     $packageStatus 'Expired';
  1547.                 }
  1548.             } else {
  1549.                 $packageStatus 'No Package';
  1550.             }
  1551.             // Format last login dates
  1552.             $lastLoginDate '';
  1553.             $lastFailedLoginDate '';
  1554.             if (!empty($summary['lastLogin'])) {
  1555.                 try {
  1556.                     $lastLogin = new \DateTime($summary['lastLogin']);
  1557.                     $lastLogin->setTimezone(new \DateTimeZone('Asia/Riyadh'));
  1558.                     $lastLoginDate $lastLogin->format('F d, Y g:i A');
  1559.                 } catch (\Exception $e) {
  1560.                     $lastLoginDate $summary['lastLogin'];
  1561.                 }
  1562.             }
  1563.             if (!empty($summary['lastFailedLogin'])) {
  1564.                 try {
  1565.                     $lastFailedLogin = new \DateTime($summary['lastFailedLogin']);
  1566.                     $lastFailedLogin->setTimezone(new \DateTimeZone('Asia/Riyadh'));
  1567.                     $lastFailedLoginDate $lastFailedLogin->format('F d, Y g:i A');
  1568.                 } catch (\Exception $e) {
  1569.                     $lastFailedLoginDate $summary['lastFailedLogin'];
  1570.                 }
  1571.             }
  1572.             // Capitalize first letter of user status
  1573.             $userStatus $user['user_status'] ?? '';
  1574.             if (!empty($userStatus)) {
  1575.                 $userStatus ucfirst($userStatus);
  1576.             }
  1577.             // Add User Details in row-wise format
  1578.             $userDetailsSheetData[] = [
  1579.                 'Field' => 'USER DETAILS',
  1580.                 'Value' => ''
  1581.             ];
  1582.             $userDetailsSheetData[] = [
  1583.                 'Field' => 'Name',
  1584.                 'Value' => $user['name'] ?? ''
  1585.             ];
  1586.             $userDetailsSheetData[] = [
  1587.                 'Field' => 'Email',
  1588.                 'Value' => $user['email'] ?? ''
  1589.             ];
  1590.             $userDetailsSheetData[] = [
  1591.                 'Field' => 'Package Name',
  1592.                 'Value' => $user['package_name'] ?? ''
  1593.             ];
  1594.             $userDetailsSheetData[] = [
  1595.                 'Field' => 'Package Status',
  1596.                 'Value' => $packageStatus
  1597.             ];
  1598.             $userDetailsSheetData[] = [
  1599.                 'Field' => 'User Status',
  1600.                 'Value' => $userStatus
  1601.             ];
  1602.             $userDetailsSheetData[] = [
  1603.                 'Field' => 'Last Login Date',
  1604.                 'Value' => $lastLoginDate
  1605.             ];
  1606.             $userDetailsSheetData[] = [
  1607.                 'Field' => 'Last Failed Login Date',
  1608.                 'Value' => $lastFailedLoginDate
  1609.             ];
  1610.             $userDetailsSheetData[] = [
  1611.                 'Field' => 'Total Attempts',
  1612.                 'Value' => $failedAttempts $successAttempts
  1613.             ];
  1614.             $userDetailsSheetData[] = [
  1615.                 'Field' => 'Failed Attempts',
  1616.                 'Value' => $failedAttempts
  1617.             ];
  1618.             $userDetailsSheetData[] = [
  1619.                 'Field' => 'Success Attempts',
  1620.                 'Value' => $successAttempts
  1621.             ];
  1622.         } else {
  1623.             $userDetailsSheetData[] = [
  1624.                 'Field' => 'Status',
  1625.                 'Value' => 'No User Details found'
  1626.             ];
  1627.         }
  1628.         $allSheets[] = [
  1629.             'Sheet Name' => 'User Details',
  1630.             'Data' => $userDetailsSheetData
  1631.         ];
  1632.         // 1. Get EWS Subscriptions Data
  1633.         $ewsData $this->fetchEwsSubscriptions(
  1634.             $organizationId,
  1635.             $userId,
  1636.             $from,
  1637.             $to,
  1638.             $search,
  1639.             $sortBy,
  1640.             $sortDir,
  1641.             1,
  1642.             100000,
  1643.             $lang
  1644.         );
  1645.         $ewsSheetData = [];
  1646.         // Add EWS Subscriptions headers
  1647.         $ewsSheetData[] = [
  1648.             'S. No' => 'S. No',
  1649.             'Region Name' => 'Region Name',
  1650.             'Subscription Date' => 'Subscription Date',
  1651.             'Status' => 'Status',
  1652.         ];
  1653.         if (count($ewsData['data']) > 0) {
  1654.             foreach ($ewsData['data'] as $index => $row) {
  1655.                 $ewsSheetData[] = [
  1656.                     'S. No' => $index 1,
  1657.                     'Region Name' => $row['region_name_en'],
  1658.                     'Subscription Date' => $row['created_at'],
  1659.                     'Status' => 'Active',
  1660.                 ];
  1661.             }
  1662.         } else {
  1663.             $ewsSheetData[] = [
  1664.                 'S. No' => '',
  1665.                 'Region Name' => 'No EWS Subscriptions found',
  1666.                 'Subscription Date' => '',
  1667.                 'Status' => '',
  1668.             ];
  1669.         }
  1670.         $allSheets[] = [
  1671.             'Sheet Name' => 'EWS Subscriptions',
  1672.             'Data' => $ewsSheetData
  1673.         ];
  1674.         // 2. Get Notifications Data
  1675.         $notificationData $this->fetchCustomNotifications(
  1676.             $organizationId,
  1677.             $userId,
  1678.             null// type
  1679.             $from,
  1680.             $to,
  1681.             $search,
  1682.             $sortBy,
  1683.             $sortDir,
  1684.             1,
  1685.             1000000
  1686.         );
  1687.         $notificationSheetData = [];
  1688.         // Add Notifications headers
  1689.         $notificationSheetData[] = [
  1690.             'S. No' => 'S. No',
  1691.             'Notification Title' => 'Notification Title',
  1692.             'Type' => 'Type',
  1693.             'Created On' => 'Created On',
  1694.         ];
  1695.         if (count($notificationData['data']) > 0) {
  1696.             foreach ($notificationData['data'] as $index => $row) {
  1697.                 $notificationSheetData[] = [
  1698.                     'S. No' => $index 1,
  1699.                     'Notification Title' => $row['title'],
  1700.                     'Type' => $row['type'] == 'Custom' 'System',
  1701.                     'Created On' => $row['created_at'],
  1702.                 ];
  1703.             }
  1704.         } else {
  1705.             $notificationSheetData[] = [
  1706.                 'S. No' => '',
  1707.                 'Notification Title' => 'No Notifications found',
  1708.                 'Type' => '',
  1709.                 'Created On' => '',
  1710.             ];
  1711.         }
  1712.         $allSheets[] = [
  1713.             'Sheet Name' => 'Notifications',
  1714.             'Data' => $notificationSheetData
  1715.         ];
  1716.         // 3. Get Total Locations Data
  1717.         $locationsData $this->fetchTotalLocations(
  1718.             $organizationId,
  1719.             $userId,
  1720.             $from,
  1721.             $to,
  1722.             $search,
  1723.             $sortBy,
  1724.             $sortDir,
  1725.             1,
  1726.             1000000,
  1727.             $lang
  1728.         );
  1729.         $locationsSheetData = [];
  1730.         // Add Total Locations headers
  1731.         $locationsSheetData[] = [
  1732.             'S. No' => 'S. No',
  1733.             'Added Locations' => 'Added Locations',
  1734.             'Created On' => 'Created On',
  1735.             'Updated On' => 'Updated On',
  1736.         ];
  1737.         if (count($locationsData['data']) > 0) {
  1738.             foreach ($locationsData['data'] as $index => $row) {
  1739.                 $locationsSheetData[] = [
  1740.                     'S. No' => $index 1,
  1741.                     'Added Locations' => $row['name_en'],
  1742.                     'Created On' => $row['created_at'],
  1743.                     'Updated On' => $row['updated_at'],
  1744.                 ];
  1745.             }
  1746.         } else {
  1747.             $locationsSheetData[] = [
  1748.                 'S. No' => '',
  1749.                 'Added Locations' => 'No Locations found',
  1750.                 'Created On' => '',
  1751.                 'Updated On' => '',
  1752.             ];
  1753.         }
  1754.         $allSheets[] = [
  1755.             'Sheet Name' => 'Total Locations',
  1756.             'Data' => $locationsSheetData
  1757.         ];
  1758.         // 4. Get Email Statistics (using existing loginData)
  1759.         $emailSheetData = [];
  1760.         // Add Email Received headers with exact format from screenshot
  1761.         $emailSheetData[] = [
  1762.             'OTP' => 'OTP',
  1763.             'Early Warning Emails' => 'Early Warning Emails',
  1764.             'Custom Notifications' => 'Custom Notifications',
  1765.         ];
  1766.         if (isset($loginData['data']['summary'])) {
  1767.             $summary $loginData['data']['summary'];
  1768.             // Calculate Custom Notifications (sum of basic and advance)
  1769.             $basicCount $summary['totalBasicCustomNotificationEmailReceived'] ?? 0;
  1770.             $advanceCount $summary['totalAdvanceCustomNotificationEmailReceived'] ?? 0;
  1771.             $totalCustomCount $basicCount $advanceCount;
  1772.             // Add data row with exact format from screenshot
  1773.             $emailSheetData[] = [
  1774.                 'OTP' => $summary['totalOtpEmailReceived'] ?? 0,
  1775.                 'Early Warning Emails' => $summary['totalEwsAlertEmailReceived'] ?? 0,
  1776.                 'Custom Notifications' => $totalCustomCount,
  1777.             ];
  1778.         } else {
  1779.             // Add data row with zeros if no data
  1780.             $emailSheetData[] = [
  1781.                 'OTP' => 0,
  1782.                 'Early Warning Emails' => 0,
  1783.                 'Custom Notifications' => 0,
  1784.             ];
  1785.         }
  1786.         $allSheets[] = [
  1787.             'Sheet Name' => 'Email Received',
  1788.             'Data' => $emailSheetData
  1789.         ];
  1790.         if (count($allSheets) > 0) {
  1791.             // Create multi-sheet Excel file
  1792.             $excelData \App\Lib\ExcelGenerator::createAndSaveMultiSheetXlsx($allSheets"entity_user_collective_excel" time(), true'/User_dashboard/excel/entity-user-collective-excel/');
  1793.             return [
  1794.                 "success" => true,
  1795.                 "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1796.                 "data"    => $excelData
  1797.             ];
  1798.         } else {
  1799.             return [
  1800.                 "success" => false,
  1801.                 "message" => isset($translator) ? $translator->trans("records_not_found") : "Records not found"
  1802.             ];
  1803.         }
  1804.     }
  1805.     public function generateEntityUserAllCollectiveExcel(
  1806.         ?int    $organizationId,
  1807.         ?int    $userId,
  1808.         ?string $from,
  1809.         ?string $to,
  1810.         ?string $search null,
  1811.         ?string $sortBy null,
  1812.         string  $sortDir 'ASC',
  1813.         string  $lang 'en'// Default to English
  1814.         $translator
  1815.     ): array {
  1816.         $allSheets = [];
  1817.         // Filter by entityId if provided
  1818.         $usersData $this->fetchFilteredUsersData(
  1819.             $organizationId,
  1820.             $userId,
  1821.             $from,
  1822.             $to,
  1823.             $search,
  1824.             $sortBy,
  1825.             $sortDir
  1826.         );
  1827.         if (count($usersData) == 0) {
  1828.             return ['success' => false'message' => $translator->trans("records_not_found")];   
  1829.         }
  1830.         // Excel Header Row
  1831.         $excelData = [];
  1832.         $excelData[] = [
  1833.             'Name',
  1834.             'Email',
  1835.             'Role',
  1836.             'Client Type',
  1837.             'Package Name',
  1838.             'Package Status',
  1839.             'Entity Name',
  1840.             'User Status',
  1841.             'Last Login Date',
  1842.             'Last Failed Login Date',
  1843.             'Total Attempts',
  1844.             'Failed Attempts',
  1845.             'Success Attempts',
  1846.             'Subscription Count',
  1847.             'Notification Count',
  1848.             'Added Locations Count',
  1849.             'Email Received Count',
  1850.             'Email Received OTP Count',
  1851.             'Email Received Early Warning Count',
  1852.             'Email Received Custom Notifications Count'
  1853.         ];
  1854.         // Add user data to Excel
  1855.         foreach ($usersData as $user) {
  1856.             $userDetails = [
  1857.                 $user['name'] ?? '',
  1858.                 $user['email'] ?? '',
  1859.                 $user['role'] ?? '',
  1860.                 $user['client_type'] ?? '',
  1861.                 $user['package_name'] ?? '',
  1862.                 $user['package_status'] ?? '',
  1863.                 $user['entity_name'] ?? '',
  1864.                 $user['user_status'] ?? '',
  1865.                 $user['last_login'] ?? '',
  1866.                 $user['last_failed_login'] ?? '',
  1867.                 $user['total_attempts'] ?? 0,
  1868.                 $user['failed_attempts'] ?? 0,
  1869.                 $user['success_attempts'] ?? 0,
  1870.                 $user['subscription_count'] ?? 0,
  1871.                 $user['notification_count'] ?? 0,
  1872.                 $user['added_locations_count'] ?? 0,
  1873.                 $user['email_received_count'] ?? 0,
  1874.                 $user['email_received_otp_count'] ?? 0,
  1875.                 $user['email_received_ews_count'] ?? 0,
  1876.                 $user['email_received_custom_notifications_count'] ?? 0
  1877.             ];
  1878.             $excelData[] = $userDetails;
  1879.         }
  1880.         // Create the Excel file
  1881.         $generatedExcel \App\Lib\ExcelGenerator::createAndSaveMultiSheetXlsx(
  1882.             [['Sheet Name' => 'User Data''Data' => $excelData]],
  1883.             "entity_user_collective_excel" time(),
  1884.             true,
  1885.             '/User_dashboard/excel/entity-user-collective-excel/'
  1886.         );
  1887.         return [
  1888.             "success" => true,
  1889.             "message" => isset($translator) ? $translator->trans("excel_generated") : "Excel generated",
  1890.             "data"    => $generatedExcel,
  1891.         ];
  1892.     }
  1893.     private function fetchFilteredUsersData(
  1894.         ?int $organizationId,
  1895.         ?int $userId,
  1896.         ?string $from,
  1897.         ?string $to,
  1898.         ?string $search,
  1899.         ?string $sortBy,
  1900.         string $sortDir
  1901.     ): array {
  1902.         // Normalize date filters
  1903.         $fromDateTime $from ? (new \DateTime($from))->format('Y-m-d') . ' 00:00:00' null;
  1904.         $toDateTime   $to   ? (new \DateTime($to))->format('Y-m-d') . ' 23:59:59' null;
  1905.         $fromTs       $from ? (new \DateTime($from))->setTime(000)->getTimestamp() : null;
  1906.         $toTs         $to   ? (new \DateTime($to))->setTime(235959)->getTimestamp() : null;
  1907.         // Base users query (ensure one row per user). Join to latest custom subscription per user.
  1908.         $qb $this->db->createQueryBuilder();
  1909.         $qb
  1910.             ->select([
  1911.                 'u.oo_id',
  1912.                 'u.name',
  1913.                 'u.email',
  1914.                 'org.clientType AS clientType',
  1915.                 'u.o_published',
  1916.                 'u.token',
  1917.                 'ur.name AS role',
  1918.                 'pkg.packageName AS packageName',
  1919.                 'org_en.name AS entityName',
  1920.                 'sub.isActive',
  1921.                 'sub.isWso',
  1922.                 'sub.oo_id AS sub_id'
  1923.             ])
  1924.             ->from('object_customer''u')
  1925.             // Latest custom subscription per user
  1926.             ->leftJoin(
  1927.                 'u',
  1928.                 '(SELECT s.subscribedUser__id AS user_id, MAX(s.o_creationDate) AS max_created
  1929.                   FROM object_subscription s
  1930.                   WHERE s.subscriptionType = "custom"
  1931.                   GROUP BY s.subscribedUser__id)',
  1932.                 'smax',
  1933.                 'smax.user_id = u.oo_id'
  1934.             )
  1935.             ->leftJoin(
  1936.                 'u',
  1937.                 'object_subscription',
  1938.                 'sub',
  1939.                 'sub.subscribedUser__id = u.oo_id AND sub.subscriptionType = "custom" AND sub.o_creationDate = smax.max_created'
  1940.             )
  1941.             ->leftJoin('sub''object_localized_packages_en''pkg''pkg.oo_id = sub.subscribedPackage__id')
  1942.             ->leftJoin('u''object_user_role''ur''ur.oo_id = u.role__id')
  1943.             ->leftJoin('u''object_localized_query_organization_en''org_en''org_en.ooo_id = u.organization__id')
  1944.             ->leftJoin('u''object_query_organization''org''org.oo_id = u.organization__id')
  1945.             ->where('ur.name IN ("CLIENT_ADMIN", "CLIENT_USER")');
  1946.         // Filters
  1947.         if ($organizationId) {
  1948.             $qb->andWhere('u.organization__id = :organizationId')->setParameter('organizationId'$organizationId);
  1949.         }
  1950.         if ($userId) {
  1951.             $qb->andWhere('u.oo_id = :userId')->setParameter('userId'$userId);
  1952.         }
  1953.         if ($fromTs) {
  1954.             $qb->andWhere('u.o_creationDate >= :fromUser')->setParameter('fromUser'$fromTs);
  1955.         }
  1956.         if ($toTs) {
  1957.             $qb->andWhere('u.o_creationDate <= :toUser')->setParameter('toUser'$toTs);
  1958.         }
  1959.         if ($search) {
  1960.             $expr $qb->expr();
  1961.             $qb->andWhere($expr->or(
  1962.                 $expr->like('u.name'':search'),
  1963.                 $expr->like('u.email'':search')
  1964.             ))->setParameter('search'"%{$search}%");
  1965.         }
  1966.         // Get users
  1967.         $users $qb->execute()->fetchAllAssociative();
  1968.         // Unique user IDs for batch queries
  1969.         $userIds array_values(array_unique(array_column($users'oo_id')));
  1970.         if (empty($userIds)) {
  1971.             return [];
  1972.         }
  1973.         // Batch queries with date filtering
  1974.         $loginAttempts      $this->fetchLoginAttemptsBatch($userIds$fromDateTime$toDateTime);
  1975.         $subscriptionCounts $this->fetchSubscriptionCountsBatch($userIds$fromTs$toTs); // EWS subscriptions
  1976.         $locationCounts     $this->fetchLocationCountsBatch($userIds$fromDateTime$toDateTime);
  1977.         $emailStats         $this->fetchEmailStatsBatch($userIds$fromDateTime$toDateTime);
  1978.         $notificationCounts $this->fetchNotificationCountsBatch($userIds$fromDateTime$toDateTime);
  1979.         // Merge data
  1980.         $result = [];
  1981.         foreach ($users as $u) {
  1982.             $uid = (int)$u['oo_id'];
  1983.             $hasPackage = !empty($u['sub_id']);
  1984.             $isActive   = isset($u['isActive']) ? (int)$u['isActive'] === false;
  1985.             $isWso      = isset($u['isWso']) ? (int)$u['isWso'] === false;
  1986.             // Derive user status
  1987.             $userStatus 'Unknown';
  1988.             if ((int)$u['o_published'] === && $hasPackage && !$isWso) {
  1989.                 $userStatus $isActive 'Active' 'Suspended';
  1990.             } elseif (!empty($u['token']) && (int)$u['o_published'] === && !$isWso) {
  1991.                 $userStatus 'Pending';
  1992.             }
  1993.             $login $loginAttempts[$uid] ?? [
  1994.                 'lastLogin' => null,
  1995.                 'lastFailedLogin' => null,
  1996.                 'LoginCount' => 0,
  1997.                 'LogoutCount' => 0
  1998.             ];
  1999.             $email $emailStats[$uid] ?? [
  2000.                 'user_id' => $uid,
  2001.                 'totalEmailReceived' => 0,
  2002.                 'totalOtpEmailReceived' => 0,
  2003.                 'totalBasicCustomNotificationEmailReceived' => 0,
  2004.                 'totalAdvanceCustomNotificationEmailReceived' => 0,
  2005.                 'totalEwsAlertEmailReceived' => 0
  2006.             ];
  2007.             $result[] = [
  2008.                 'name' => $u['name'],
  2009.                 'email' => $u['email'],
  2010.                 'client_type' => $u['clientType'] == 'organization' 'Entity' 'Government',
  2011.                 'role' => $u['role']  == 'CLIENT_ADMIN' 'Admin' 'User',
  2012.                 'package_name' => $u['packageName'] ?? '',
  2013.                 'package_status' => $hasPackage ? ($isActive 'Active' 'Inactive') : 'No Package',
  2014.                 'entity_name' => $u['entityName'],
  2015.                 'user_status' => $userStatus,
  2016.                 'last_login' => $login['lastLogin'] ?? '',
  2017.                 'last_failed_login' => $login['lastFailedLogin'] ?? '',
  2018.                 'total_attempts' => (int)($login['LoginCount'] ?? 0) + (int)($login['LogoutCount'] ?? 0),
  2019.                 'failed_attempts' => (int)($login['LogoutCount'] ?? 0),
  2020.                 'success_attempts' => (int)($login['LoginCount'] ?? 0),
  2021.                 'subscription_count' => (int)($subscriptionCounts[$uid] ?? 0),
  2022.                 'notification_count' => (int)($notificationCounts[$uid] ?? 0),
  2023.                 'added_locations_count' => (int)($locationCounts[$uid] ?? 0),
  2024.                 'email_received_count' => (int)($email['totalEmailReceived'] ?? 0),
  2025.                 'email_received_otp_count' => (int)($email['totalOtpEmailReceived'] ?? 0),
  2026.                 'email_received_ews_count' => (int)($email['totalEwsAlertEmailReceived'] ?? 0),
  2027.                 'email_received_custom_notifications_count' =>
  2028.                 (int)($email['totalBasicCustomNotificationEmailReceived'] ?? 0)
  2029.                     + (int)($email['totalAdvanceCustomNotificationEmailReceived'] ?? 0)
  2030.             ];
  2031.         }
  2032.         // Optional sorting on merged result
  2033.         if ($sortBy) {
  2034.             $allowedSort = [
  2035.                 'name',
  2036.                 'email',
  2037.                 'role',
  2038.                 'package_name',
  2039.                 'package_status',
  2040.                 'entity_name',
  2041.                 'user_status',
  2042.                 'last_login',
  2043.                 'last_failed_login',
  2044.                 'total_attempts',
  2045.                 'failed_attempts',
  2046.                 'success_attempts',
  2047.                 'subscription_count',
  2048.                 'notification_count',
  2049.                 'added_locations_count',
  2050.                 'email_received_count',
  2051.                 'email_received_otp_count',
  2052.                 'email_received_ews_count',
  2053.                 'email_received_custom_notifications_count'
  2054.             ];
  2055.             if (in_array($sortBy$allowedSorttrue)) {
  2056.                 $dir strtoupper($sortDir) === 'DESC' ? -1;
  2057.                 usort($result, function ($a$b) use ($sortBy$dir) {
  2058.                     $va $a[$sortBy] ?? null;
  2059.                     $vb $b[$sortBy] ?? null;
  2060.                     // Normalize dates for comparison if needed
  2061.                     if (in_array($sortBy, ['last_login''last_failed_login'], true)) {
  2062.                         $ta $va ? (strtotime($va) ?: 0) : 0;
  2063.                         $tb $vb ? (strtotime($vb) ?: 0) : 0;
  2064.                         return ($ta <=> $tb) * $dir;
  2065.                     }
  2066.                     // Numeric comparison for counts
  2067.                     if (is_numeric($va) && is_numeric($vb)) {
  2068.                         return ((int)$va <=> (int)$vb) * $dir;
  2069.                     }
  2070.                     return strcmp((string)$va, (string)$vb) * $dir;
  2071.                 });
  2072.             }
  2073.         }
  2074.        
  2075.         return $result;
  2076.     }
  2077.     private function fetchLoginAttemptsBatch(array $userIds, ?string $fromDateTime, ?string $toDateTime): array
  2078.     {
  2079.         $placeholders implode(','array_fill(0count($userIds), '?'));
  2080.         $params $userIds;
  2081.         $dateConds '';
  2082.         if ($fromDateTime) {
  2083.             $dateConds .= ' AND la.attempt_time >= ?';
  2084.             $params[] = $fromDateTime;
  2085.         }
  2086.         if ($toDateTime) {
  2087.             $dateConds .= ' AND la.attempt_time <= ?';
  2088.             $params[] = $toDateTime;
  2089.         }
  2090.         $sql "SELECT 
  2091.             c.oo_id AS user_id,
  2092.             MAX(CASE WHEN la.status = 1 THEN la.attempt_time ELSE NULL END) AS lastLogin,
  2093.             MAX(CASE WHEN la.status = 0 THEN la.attempt_time ELSE NULL END) AS lastFailedLogin,
  2094.             COUNT(CASE WHEN la.status = 1 THEN 1 ELSE NULL END) AS LoginCount,
  2095.             COUNT(CASE WHEN la.status = 0 THEN 1 ELSE NULL END) AS LogoutCount
  2096.         FROM object_customer c
  2097.         LEFT JOIN user_login_attempts la 
  2098.             ON c.email COLLATE utf8mb4_unicode_ci = la.customer_email COLLATE utf8mb4_unicode_ci
  2099.         WHERE c.oo_id IN ($placeholders$dateConds
  2100.         GROUP BY c.oo_id";
  2101.         $result $this->db->executeQuery($sql$params)->fetchAllAssociative();
  2102.         return array_column($resultnull'user_id');
  2103.     }
  2104.     private function fetchSubscriptionCountsBatch(array $userIds, ?int $fromTs, ?int $toTs): array
  2105.     {
  2106.         // EWS subscriptions count per user (object_MannedAlertSubscription)
  2107.         $placeholders implode(','array_fill(0count($userIds), '?'));
  2108.         $params $userIds;
  2109.         $dateConds '';
  2110.         if ($fromTs !== null) {
  2111.             $dateConds .= ' AND s.o_creationDate >= ?';
  2112.             $params[] = $fromTs;
  2113.         }
  2114.         if ($toTs !== null) {
  2115.             $dateConds .= ' AND s.o_creationDate <= ?';
  2116.             $params[] = $toTs;
  2117.         }
  2118.         $sql "SELECT s.creator__id AS user_id, COUNT(*) AS count
  2119.         FROM object_MannedAlertSubscription s
  2120.         WHERE s.creator__id IN ($placeholders$dateConds
  2121.         GROUP BY s.creator__id";
  2122.         $result $this->db->executeQuery($sql$params)->fetchAllAssociative();
  2123.         return array_column($result'count''user_id');
  2124.     }
  2125.     private function fetchLocationCountsBatch(array $userIds, ?string $fromDateTime null, ?string $toDateTime null): array
  2126.     {
  2127.         $placeholders implode(','array_fill(0count($userIds), '?'));
  2128.         $params $userIds;
  2129.         $dateConds '';
  2130.         if ($fromDateTime) {
  2131.             $dateConds .= ' AND updated_at >= ?';
  2132.             $params[] = $fromDateTime;
  2133.         }
  2134.         if ($toDateTime) {
  2135.             $dateConds .= ' AND updated_at <= ?';
  2136.             $params[] = $toDateTime;
  2137.         }
  2138.         $sql "SELECT user_id, COUNT(*) AS count
  2139.         FROM reporting_total_location
  2140.         WHERE user_id IN ($placeholders$dateConds
  2141.         GROUP BY user_id";
  2142.         $result $this->db->executeQuery($sql$params)->fetchAllAssociative();
  2143.         return array_column($result'count''user_id');
  2144.     }
  2145.     private function fetchEmailStatsBatch(array $userIds, ?string $fromDateTime, ?string $toDateTime): array
  2146.     {
  2147.         $placeholders implode(','array_fill(0count($userIds), '?'));
  2148.         $params $userIds;
  2149.         $dateConds '';
  2150.         if ($fromDateTime) {
  2151.             $dateConds .= ' AND sent_date >= ?';
  2152.             $params[] = $fromDateTime;
  2153.         }
  2154.         if ($toDateTime) {
  2155.             $dateConds .= ' AND sent_date <= ?';
  2156.             $params[] = $toDateTime;
  2157.         }
  2158.         $sql "SELECT 
  2159.             user_id,
  2160.             COUNT(*) AS totalEmailReceived,
  2161.             COUNT(CASE WHEN email_type = 'Customer' THEN 1 ELSE NULL END) AS totalOtpEmailReceived,
  2162.             COUNT(CASE WHEN email_type = 'CustomNotification' THEN 1 ELSE NULL END) AS totalBasicCustomNotificationEmailReceived,
  2163.             COUNT(CASE WHEN email_type = 'AdvanceCustomNotification' THEN 1 ELSE NULL END) AS totalAdvanceCustomNotificationEmailReceived,
  2164.             COUNT(CASE WHEN email_type = 'EwsNotification' THEN 1 ELSE NULL END) AS totalEwsAlertEmailReceived
  2165.         FROM reporting_received_emails
  2166.         WHERE user_id IN ($placeholders$dateConds
  2167.         GROUP BY user_id";
  2168.         $result $this->db->executeQuery($sql$params)->fetchAllAssociative();
  2169.         return array_column($resultnull'user_id');
  2170.     }
  2171.     private function fetchNotificationCountsBatch(array $userIds, ?string $fromDateTime, ?string $toDateTime): array
  2172.     {
  2173.         $placeholders implode(','array_fill(0count($userIds), '?'));
  2174.         $params $userIds;
  2175.         $dateConds '';
  2176.         if ($fromDateTime) {
  2177.             $dateConds .= ' AND created_at >= ?';
  2178.             $params[] = $fromDateTime;
  2179.         }
  2180.         if ($toDateTime) {
  2181.             $dateConds .= ' AND created_at <= ?';
  2182.             $params[] = $toDateTime;
  2183.         }
  2184.         $sql "SELECT user_id, COUNT(*) AS count
  2185.         FROM reporting_custom_notification
  2186.         WHERE user_id IN ($placeholders$dateConds
  2187.         GROUP BY user_id";
  2188.         $result $this->db->executeQuery($sql$params)->fetchAllAssociative();
  2189.         return array_column($result'count''user_id');
  2190.     }
  2191. }