src/Diplix/KMGBundle/Controller/Dispatching/DashboardController.php line 429

Open in your IDE?
  1. <?php
  2. namespace Diplix\KMGBundle\Controller\Dispatching;
  3. use Diplix\Commons\DataHandlingBundle\Entity\SysLogEntry;
  4. use Diplix\Commons\DataHandlingBundle\Repository\SysLogRepository;
  5. use Diplix\KMGBundle\Controller\BaseController;
  6. use Diplix\KMGBundle\Controller\Service\Api2Controller;
  7. use Diplix\KMGBundle\DataTables\DataTablesHelper;
  8. use Diplix\KMGBundle\Entity\Accounting\Billing;
  9. use Diplix\KMGBundle\Entity\Accounting\CoopMember;
  10. use Diplix\KMGBundle\Entity\Accounting\Job;
  11. use Diplix\KMGBundle\Entity\Accounting\JobInterestExpression;
  12. use Diplix\KMGBundle\Entity\Address;
  13. use Diplix\KMGBundle\Entity\Availability;
  14. use Diplix\KMGBundle\Entity\Dispatching\ChatMessage;
  15. use Diplix\KMGBundle\Entity\Dispatching\DispatchQueueItem;
  16. use Diplix\KMGBundle\Entity\Note;
  17. use Diplix\KMGBundle\Entity\Order;
  18. use Diplix\KMGBundle\Entity\OrderStatus;
  19. use Diplix\KMGBundle\Entity\Platform\PlatformClient;
  20. use Diplix\KMGBundle\Entity\Role;
  21. use Diplix\KMGBundle\Entity\Setting;
  22. use Diplix\KMGBundle\Entity\User;
  23. use Diplix\KMGBundle\Form\Dispatching\NoteDoneForm;
  24. use Diplix\KMGBundle\Form\Dispatching\NoteForm;
  25. use Diplix\KMGBundle\Form\Dispatching\OrderDispatchStatusForm;
  26. use Diplix\KMGBundle\Helper\ChatMessageNormalizer;
  27. use Diplix\KMGBundle\Helper\SimpleFileNormalizer;
  28. use Diplix\KMGBundle\Repository\OrderRepository;
  29. use Diplix\KMGBundle\Service\ArrayLogWrapper;
  30. use Diplix\KMGBundle\Service\JobAllocator;
  31. use Diplix\KMGBundle\Service\MobileNotifier;
  32. use Diplix\KMGBundle\Service\Notifier;
  33. use Diplix\KMGBundle\Service\OrderHandler;
  34. use Doctrine\ORM\AbstractQuery;
  35. use Doctrine\ORM\EntityRepository;
  36. use Doctrine\ORM\QueryBuilder;
  37. use Doctrine\ORM\Tools\Pagination\Paginator;
  38. use Liip\ImagineBundle\Imagine\Cache\CacheManager;
  39. use PhpImap\Mailbox;
  40. use SecIT\ImapBundle\Service\Imap;
  41. use Symfony\Component\HttpFoundation\JsonResponse;
  42. use Symfony\Component\HttpFoundation\Request;
  43. use Symfony\Component\Serializer\Encoder\JsonEncoder;
  44. use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
  45. use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
  46. use Symfony\Component\Serializer\Serializer;
  47. class DashboardController extends BaseController
  48. {
  49.     protected $chatRepo;
  50.     public function __construct(
  51.         protected MobileNotifier $mobileNotifier,
  52.         protected Notifier $notifier,
  53.         protected OrderHandler $orderHandler,
  54.         protected Imap $imapService,
  55.         protected CacheManager $imgagineCacheManager,
  56.         private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry
  57.     )
  58.     {
  59.     }
  60.     protected function init()
  61.     {
  62.         $this->ensureUserHasRole(Role::DISPO);
  63.     }
  64.     protected function getMembers()
  65.     {
  66.         $repo $this->managerRegistry->getRepository(CoopMember::class);
  67.         return $repo->findBy(["active"=>true],['shortCode'=>'asc']);
  68.     }
  69.     protected function getUsersWhichAreMember()
  70.     {
  71.         $repo $this->managerRegistry->getRepository(User::class);
  72.         return $repo->findUsersWhichAreMember();
  73.     }
  74.     protected function isDashoardPermitted(?string $dispatchCategory): bool
  75.     {
  76.         $possible $this->getCurrentUser()->getDashboards();
  77.         $categoryToCheck $dispatchCategory ?? PlatformClient::DEFAULT_DASHBOARD;
  78.         return in_array($categoryToCheck$possibletrue);
  79.     }
  80.     public function dashboard2Action(Request $request)
  81.     {
  82.         $this->init();
  83.         $osr $this->managerRegistry->getRepository(OrderStatus::class)->getByIdMap();
  84.         $osr array_map(function($el){ return $el->getName();},$osr);
  85.         $dispatchCategory $request->query->get('category',null);
  86.         if (!$this->isDashoardPermitted($dispatchCategory))
  87.         {
  88.             $this->addFlash('danger','Bitte prüfen Sie Ihre Berechtigungen');
  89.             return $this->redirectToRoute('kmg_home');
  90.         }
  91.         return $this->render('@DiplixKMG/Dispatching/dashboard_react.html.twig',[
  92.             'members' => $this->getMembers(),
  93.             'dispatchCategory' => $dispatchCategory,
  94.             'statusMap' => $osr,
  95.         ]);
  96.     }
  97.     protected function prepareOrderArrayForOutput(&$arr)
  98.     {
  99.         if ($arr === null) return null;
  100.         $arr['orderStatus'] = $arr['orderStatus']['id'];
  101.         $arr['orderTime'] = $arr['orderTime']->format(Api2Controller::DATE_FORMAT);
  102.         $arr['jobPdfRequestedOn'] = $arr['jobPdfRequestedOn'] !==null ?  $arr['jobPdfRequestedOn']->format(Api2Controller::DATE_FORMAT) : null;
  103.         $arr['assignedTo'] = $arr['assignedTo'] !== null $arr['assignedTo']['id'] : null;
  104.         $route = [];
  105.         foreach ($arr['addressList'] as $a)
  106.         {
  107.             $route [] = sprintf('%s'$a['city']);
  108.         }
  109.         $route implode(' > ',$route);
  110.         $arr['route'] = $route;
  111.         return $arr;
  112.     }
  113.     public function dashboardData2Action(Request $request)
  114.     {
  115.         $this->init();
  116.         /** @var EntityRepository $repo */
  117.         $repo $this->managerRegistry->getRepository(Order::class);
  118.         /** @var QueryBuilder $cb */
  119.         $cb $repo->getQb();
  120.         $dispatchCategory $request->query->get('category',null);
  121.         if (!$this->isDashoardPermitted($dispatchCategory))
  122.         {
  123.             return $this->getJsonDataResponse($request,false,null,"Bitte prüfen Sie Ihre Berechtigungen");
  124.         }
  125.         if (!empty($dispatchCategory))
  126.         {
  127.             $cb->andWhere('A.dispatchCategory = :dicat')->setParameter('dicat',$dispatchCategory);
  128.         }
  129.         else
  130.         {
  131.             $cb->andWhere('A.dispatchCategory is null');
  132.         }
  133.         // hide some
  134.         // $cb->andWhere('A.remoteStatus != :rs')->setParameter('rs',Order::REMOTE_PENDING);
  135.         $cb->andWhere$cb->expr()->notIn('A.orderStatus',':hiddenStati'))
  136.             ->setParameter('hiddenStati',[OrderStatus::STATUS_CANCELEDOrderStatus::STATUS_DRAFTOrderStatus::STATUS_INSTANT_ORDER_PENDING]);
  137.         // only rides to x days to the future
  138.         $from = new \DateTime(); $from->setTime(0,0,0); $from->sub(new \DateInterval('P1D'));
  139.         $to = clone $from;
  140.         $to->add(new \DateInterval('P150D')); $to->setTime(23,59,59);
  141.         $cb->andWhere('A.orderTime <= :to')->setParameter('to',$to);
  142.         // all rides from yesterday, independant of status
  143.         $cb->andWhere($cb->expr()->gte("A.orderTime",":from"))
  144.             ->setParameter('from',$from);
  145.         /*
  146.         // limit finished rides but always show all open rides
  147.         $cb->andWhere($cb->expr()->orX(
  148.             $cb->expr()->eq('A.orderStatus',OrderStatus::STATUS_OPEN),
  149.             $cb->expr()->gte("A.orderTime",":from")
  150.         ))->setParameter('from',$from);
  151.         */
  152.         $cb->orderBy('A.orderTime');
  153.         $data =  $cb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
  154.         $id2i = [];
  155.         for ($i=0;$i<$ic=count($data);$i++) {
  156.             $this->prepareOrderArrayForOutput($data[$i]);
  157.             $data[$i]['hasInterest'] = false;
  158.             $id2i[$data[$i]['orderId']] = $i;
  159.         }
  160.         $fer $this->managerRegistry->getRepository(JobInterestExpression::class);
  161.         $fe $fer->findFutureInterests($from);
  162.         /** @var JobInterestExpression $row */
  163.         foreach ($fe as $row)
  164.         {
  165.             $on $row->getJob()->getKnownOrder()->getOrderId();
  166.             if (array_key_exists($on,$id2i))
  167.             {
  168.                 $data$id2i[$on] ] ['hasInterest'] = true;
  169.             }
  170.         }
  171.         return new JsonResponse([
  172.             'success'   => true,
  173.             'from'      => $from,
  174.             'until'     => $to,
  175.             'data'      => $data
  176.         ]);
  177.     }
  178.     /**
  179.      * @param $id
  180.      * @param int $hydrationMode
  181.      * @return array|Order
  182.      * @throws \Doctrine\ORM\NoResultException
  183.      * @throws \Doctrine\ORM\NonUniqueResultException
  184.      */
  185.     protected function fetchSingle($id$hydrationMode AbstractQuery::HYDRATE_ARRAY)
  186.     {
  187.         /** @var OrderRepository $repo */
  188.         $repo $this->managerRegistry->getRepository(Order::class);
  189.         $cb $repo->getQb()->where('A.id = :id')->setParameter('id',$id);
  190.         return $cb->getQuery()->getSingleResult($hydrationMode);
  191.     }
  192.     public function singleDataAction(Request $request$id)
  193.     {
  194.         try {
  195.             $item $this->fetchSingle($id);
  196.             $this->prepareOrderArrayForOutput($item);
  197.             return $this->getJsonDataResponse($request,true$item);
  198.         }
  199.         catch (\Throwable $ex)
  200.         {
  201.             return $this->getJsonDataResponse($request,false,null,$ex->getMessage());
  202.         }
  203.     }
  204.     public function assignMemberAction(Request $request$orderId$memberId)
  205.     {
  206.         $this->init();
  207.         try {
  208.             $order $this->fetchSingle($orderId,AbstractQuery::HYDRATE_OBJECT);
  209. //            if ($order->getXchgTo()!==null)
  210. //            {
  211. //                throw new \RuntimeException('Fahrt ist Fremdystem zugeordnet.');
  212. //            }
  213.             $oh $this->orderHandler;
  214.             $oh->setTransactionTag($request->query->get('transactionTag',''));
  215.             $order $oh->assignMemberToOrder((int)$orderId$memberId);
  216.             $item $this->fetchSingle($order->getId());
  217.             $this->prepareOrderArrayForOutput($item);
  218.             return $this->getJsonDataResponse($request,true$item);
  219.         }
  220.         catch (\Throwable $ex)
  221.         {
  222.             $item $this->fetchSingle($orderId);
  223.             $this->prepareOrderArrayForOutput($item);
  224.             return $this->getJsonDataResponse($request,false,$item,$ex->getMessage());
  225.         }
  226.     }
  227.     public function triggerMemberNotificationAction(Request $request$orderId)
  228.     {
  229.         $this->init();
  230.         try {
  231.             $order $this->fetchSingle($orderId,AbstractQuery::HYDRATE_OBJECT);
  232.             if ($order->getAssignedTo()===null)
  233.             {
  234.                 throw new \RuntimeException('Kein Mitglied zugewiesen.');
  235.             }
  236.             $this->notifier->triggerOrderUpdateForMember($order,Notifier::M_CONFIRMATION_REQUIRED);
  237.             return $this->getJsonDataResponse($request,true);
  238.         }
  239.         catch (\Throwable $ex)
  240.         {
  241.             return $this->getJsonDataResponse($request,false,null,'Benachrichtigung fehlgeschlagen: ' $ex->getMessage());
  242.         }
  243.     }
  244.     public function contextAction(Request $request)
  245.     {
  246.         $this->init();
  247.         $osr $this->managerRegistry->getRepository(OrderStatus::class)->getByIdMap();
  248.         $statusMap array_map(function($el){ return $el->getName();},$osr);
  249.         $memberList $this->getMembers();
  250.         return $this->getJsonDataResponse($request,true,[
  251.            'memberList' => $memberList,
  252.            'statusMap'  => $statusMap
  253.         ]);
  254.     }
  255.     public function memberUserListAction(Request $request)
  256.     {
  257.         $this->init();
  258.         return $this->getJsonDataResponse($request,true,[
  259.             'userList' => $this->getUsersWhichAreMember(),
  260.         ]);
  261.     }
  262.     public function deviceLogAction(Request $request $orderId)
  263.     {
  264.         $this->init();
  265.         $sysRepo $this->managerRegistry->getRepository(SysLogEntry::class);
  266.         $dqiRepo $this->managerRegistry->getRepository(DispatchQueueItem::class);
  267.         $order $this->managerRegistry->getManager()->find(Order::class,$orderId);
  268.         // queue items
  269.         $items $dqiRepo->findBy([
  270.             'order' => $order,
  271.         ]);
  272.         $idList array_map(function ($o){ return $o->getId(); },$items);
  273.         $shortedList = [];
  274.         $idx 1;
  275.         foreach ($idList as $id)
  276.         {
  277.             $shortedList[$id] = $idx++;
  278.         }
  279.         $sortedItems= [];
  280.         foreach ($items as $i)
  281.         {
  282.             $sortedItems[$i->getId()] = $i;
  283.         }
  284.         //print_r($idList);
  285.         $data $sysRepo->findBy([
  286.             'refStr' => $idList,
  287.             'type' => MobileNotifier::LOG_TYPE
  288.         ],['logTime'=>'desc']);
  289.         $html '<table class="table">';
  290.         /** @var SysLogEntry $one */
  291.         foreach ($data as $one)
  292.         {
  293.             $ref $sortedItems[$one->getRefStr()] ?? null;
  294.             $html .= sprintf('<tr>
  295.                                     <td>%s</td>
  296.                                     <td title="%s">%s</td>
  297.                                     <td>%s</td>
  298.                                     <td>%s</td>
  299.                                     <td>%s</td>
  300.                               </tr>',
  301.                     $one->getLogTime()->format('d.m.y H:i:s'),
  302.                     $one->getRefStr(),
  303.                     $shortedList[$one->getRefStr()],
  304.                     $one->getMessage(),
  305.                     $ref !== null ?  ($ref->getMember() !== null $ref->getMember()->getName() : '-kein-') : 'null',
  306.                     $ref !== null ?  ($ref->getBeOwner() !== null $ref->getBeOwner()->getShortName() : '-kein-Besitzer-') : 'null'
  307.             );
  308.         }
  309.         if (count($data)<1)
  310.         {
  311.             $html.= '<tr><td> - keine Daten -</td></tr>';
  312.         }
  313.         $html .='</table>';
  314.         return $this->getJsonDataResponse($request,true$html);
  315.     }
  316.     public static function checkImapMailStatus(Mailbox $box)
  317.     {
  318.         $result = [
  319.             'mails' => -1,
  320.             'unread' => -1,
  321.             'error' => null
  322.         ];
  323.         try {
  324.             $info $box->getMailboxInfo();
  325.             $result['mails'] = $info->{'Nmsgs'};
  326.             $result['unread'] = $info->{'Unread'};
  327.         }
  328.         catch (\Throwable $ex)
  329.         {
  330.             $result['error'] = $ex->getMessage();
  331.         }
  332.         return $result;
  333.     }
  334.     public function checkImapStatusAction(Request $request)
  335.     {
  336.         $this->init();
  337.         $result self::checkImapMailStatus($this->imapService->get('kmg_dispo'));
  338.         return $this->getJsonDataResponse($request,($result['error']===null), $result);
  339.     }
  340.     protected function sendMessageToSingleMember(User  $u$msg$parent null)
  341.     {
  342.         assert($u->getMember()!==null);
  343.         $message ChatMessage::textTo($u,$msg);
  344.         if ($parent!==null)
  345.         {
  346.             $message->setGroupParent($parent);
  347.         }
  348.         $this->chatRepo->persistFlush($message);
  349.         $message->setQueueItemDispatchQueueItem::createFor(DispatchQueueItem::ACT_MESSAGE,$u->getMember(),$u,$this->getCurrentUser()));
  350.         $this->mobileNotifier->queue($message->getQueueItem());
  351.         $this->chatRepo->persistFlush($message);
  352.         $this->notifier->notifyWebSocketAboutChatMessage($message);
  353.         $this->mobileNotifier->dispatchSingle($message->getQueueItem());
  354.         return $message;
  355.     }
  356.     public function sendMessageToMemberAction(Request $request)
  357.     {
  358.         $this->init();
  359.         $this->chatRepo  $this->managerRegistry->getRepository(ChatMessage::class);
  360.         $userRepo $this->managerRegistry->getRepository(User::class);
  361.         $data \GuzzleHttp\json_decode($request->getContent(),true);
  362.         $u $data['userId'];
  363.         $msg $data['message'];
  364.         if ($u!==0)
  365.         {
  366.             $all = [ $userRepo->findOneBy(['id'=>$u]) ];
  367.         }
  368.         else {
  369.             $all $userRepo->findUsersWhichAreMember();
  370.         }
  371.         $parent null;
  372.         /** @var User $targetUser */
  373.         foreach ($all as $targetUser)
  374.         {
  375.             $m $this->sendMessageToSingleMember($targetUser,$msg,$parent);
  376.             if ($parent===null)
  377.             {
  378.                 $parent $m;
  379.             }
  380.         }
  381.         return $this->getJsonDataResponse($request,true);
  382.     }
  383.     public function getMessagesAction(Request $request$userId)
  384.     {
  385.         $this->init();
  386.         $chatRepo  $this->managerRegistry->getRepository(ChatMessage::class);
  387.         $userRepo $this->managerRegistry->getRepository(User::class);
  388.         $all $chatRepo->findFor((int)$userId);
  389.         $serializer ChatMessage::getSerializer$this->imgagineCacheManager );
  390.         $data =  $serializer->serialize($all'json',["filterName"=>"avatar_small"]);
  391.         return $this->getJsonDataResponse($request,true,json_decode($data,false));
  392.     }
  393.     public function getNotesAction(Request $request)
  394.     {
  395.         $this->init();
  396.         $noteRepo  $this->managerRegistry->getRepository(Note::class);
  397.         $all $noteRepo->findMyNotes($this->getCurrentUser(),false);
  398.         $data array_map(function(Note $el){
  399.             return [
  400.                 'id' => $el->getId(),
  401.                 'text' => $el->getText(),
  402.                 'startTime' => $el->getStartTime()->format('d.m.y H:i:s'),
  403.                 'done' => $el->getDone() !== null,
  404.                 'type' => $el->getType(),
  405.                 'audience' => $el->getAudience() !== null $el->getAudience()->getLastName() : null,
  406.                 'customer' => $el->getCustomer() !== null $el->getCustomer()->getName() : null
  407.             ];
  408.         },$all);
  409.         return $this->getJsonDataResponse($request,true,$data);
  410.     }
  411.     public function memberAvailabilityAction(Request $request$orderId)
  412.     {
  413.         $this->init();
  414.         $orderRepo $this->managerRegistry->getRepository(Order::class);
  415.         $userRepo $this->managerRegistry->getRepository(User::class);
  416.         $memberRepo $this->managerRegistry->getRepository(CoopMember::class);
  417.         $avRepo $this->managerRegistry->getRepository(Availability::class);
  418.         $jexRepo $this->managerRegistry->getRepository(JobInterestExpression::class);
  419.         $order $orderRepo->findOneBy(['id'=>$orderId]);
  420.         $orderTime $order->getOrderTime();
  421.         // $memberUser = $userRepo->findUsersWhichAreMember();
  422.         $memberList $memberRepo->findBy(['showInCalendar'=>true]);
  423.         // a) alle termine im zielzeitraum
  424.         $now = new \DateTime();
  425.         $avsByMember $avRepo->findForRange($orderTime,$orderTime,true,null,false);
  426.         // b) bereitschaftsmeldungen für den aktuellen tag
  427.         $readyByMember $avRepo->findForRange($now,$now,true,null,false,null,null,[Availability::READY]);
  428.         // c) fahrten heutiger tag
  429.         $ridesToday $orderRepo->findAssignedToAnyMemberForDay($now);
  430.         // d) Interessenmeldungungen
  431.         $interests $jexRepo->findFutureInterests(null,[$order->getJob()->getId()]);
  432.         $interestMemberIds = [];
  433.         /** @var JobInterestExpression $i */
  434.         foreach ($interests as $i)
  435.         {
  436.             $interestMemberIds[]= $i->getMember()->getId();
  437.         }
  438.             // [..]
  439.         /*
  440.          *
  441.          * -> Anzahl Fahrten heutiger Tag pro Mitglied in der Wertigkeit Ihrer Ready-Meldung (die je nach Zeitabstand unterschiedlich hoch ist - erste Meldung am
  442.          * höchsten, spätere Meldung niederwertiger)
  443.          *
  444.          * Tag fängt um 0 Uhr an.
  445.          * Fahrt um 12. Erste Anmeldung am Tag kommt dran (z.B. Anmeldungen um 9 ,11, 11:55) --> Anmeldung von 9 kommt zum Zug
  446.          *
  447.          * falls bereits Fahrt an dem Tag -1, wieder Bereit gemeldet + 1 , Reihenfolge der Anmeldezeiten bleibt gewichtet (zuerst gemeldet = erster)
  448.          *
  449.          *
  450.          * Leute ohne Einträge -> wer die wenigsten Fahrten am Tag hat kommt zum Zug
  451.          *
  452.          *
  453.          *
  454.          * + Fahrten am Tag anzeigen
  455.          * + anzeigee Shortcode
  456.          *
  457.          *
  458.          *
  459.          */
  460.         // gewichten
  461.         $weightsByMember = [];
  462.         $commentsByMember = [];
  463.         $members = [];
  464.         /** @var CoopMember $m */
  465.         foreach ($memberList as $m)
  466.         {
  467.             if (!array_key_exists($m->getId(),$weightsByMember))
  468.             {
  469.                 $memberId $m->getId();
  470.                 $weightsByMember$memberId ] = 100;
  471.                 $commentsByMember$memberId ] = [];
  472.                 if ($m->isCoopPartner())
  473.                 {
  474.                     $weightsByMember$memberId ]  -= 30;
  475.                     $commentsByMember[$memberId] []= '-30 Kooperationspartner';
  476.                 }
  477.                 if (in_array($m->getId(),$interestMemberIds))
  478.                 {
  479.                     $weightsByMember$memberId ]  += 10;
  480.                     $commentsByMember[$memberId] []= '+10 Interesse bekundet';
  481.                 }
  482.             }
  483.             $members[$m->getId()] = $m;
  484.         }
  485.         // auf orderTime liegende Termine führen zu Abzug
  486.         foreach ($avsByMember as $memberId => $avList)
  487.         {
  488.             /** @var Availability $av */
  489.             foreach ($avList as $av)
  490.             {
  491.                 if (!array_key_exists($memberId,$weightsByMember))
  492.                 {
  493.                     continue; // throw new \RuntimeException(sprintf('member#%d unknown in availability#%d',$memberId,$av->getId()));
  494.                 }
  495.                 if ($av->getAvailType()=== Availability::NOT_AVAILABLE)
  496.                 {
  497.                     if (in_array($memberId,$interestMemberIds))
  498.                     {
  499.                         // falls intessiert zählt N/V nicht
  500.                         $commentsByMember[$memberId] []= '-0 Nicht verfügbar (nicht gezählt da Interesse bekundet)';
  501.                     }
  502.                     else
  503.                     {
  504.                         $weightsByMember[$memberId] -= 50;
  505.                         $commentsByMember[$memberId] []= '-50 Nicht verfügbar';
  506.                     }
  507.                 }
  508.                 else
  509.                 if ($av->getAvailType()!== Availability::READY)
  510.                 {
  511.                     $weightsByMember[$memberId] -= 1;
  512.                     $commentsByMember[$memberId] []= '-1 Kalendereintrag';
  513.                 }
  514.             }
  515.         }
  516.         // Ready-Meldungen
  517.         foreach ($readyByMember as $memberId => $avList)
  518.         {
  519.             $rides array_key_exists($memberId,$ridesToday) ? $ridesToday[$memberId] : [];
  520.             $lastRide count($rides)>end($rides) : null;
  521.             $lastAv count($avList)>end($avList) : null;
  522.             if (($lastRide!==null)&&($lastAv!==null))
  523.             {
  524.                 if ($lastAv->getAvailFrom() > $lastRide->getOrderTime())
  525.                 {
  526.                     $minutes = ($now->getTimestamp() - $av->getAvailFrom()->getTimestamp())/60;
  527.                     $faktor max(floor($minutes/30),1);
  528.                     $weightsByMember[$memberId] += $faktor;
  529.                     $commentsByMember[$memberId] [] = sprintf("+%d Verfügbar nach Fahrt",$faktor);
  530.                 }
  531.             }
  532.             else
  533.             if ($lastAv!==null)
  534.             {   // an dieser Stelle Verfügbar ber bisher keine Fahrt heute
  535.                 $minutes = ($now->getTimestamp() - $av->getAvailFrom()->getTimestamp())/60;
  536.                 $faktor max(ceil($minutes/30),1);
  537.                 $weightsByMember[$memberId] += $faktor;
  538.                 $commentsByMember[$memberId] [] = sprintf("+%d Verfügbar heute",$faktor);
  539.             }
  540.             else
  541.             if ($lastRide!==null)
  542.             {
  543.                 // an diesr Stelle Fahrt aber keine Verfügbarkeitsmeldung
  544.                 $weightsByMember[$memberId] -= 5;
  545.                 $commentsByMember[$memberId] [] = "-5 Fahrt um".$lastRide->getOrderTime()->format("H:i");
  546.             }
  547.         }
  548.         arsort($weightsByMember); // sort by descending weight
  549.         return $this->getJsonDataResponse(
  550.             $request,
  551.             true,
  552.             [
  553.                 'html' =>
  554.                         $this->renderView('@DiplixKMG/Dispatching/member-availabilites-for-order.html.twig',[
  555.                             'weight'=>$weightsByMember,
  556.                             'members'=>$members,
  557.                             'avByMember'=>$avsByMember,
  558.                             'readyByMember'=>$readyByMember,
  559.                             'avDisplayMap' => Availability::$displayMap,
  560.                             'avColorMap'=> Availability::$colorMap
  561.                         ]),
  562.                 'weight'=>$weightsByMember,
  563.                 'members'=>$members,
  564.                 'avByMember'=>$avsByMember,
  565.                 'readyByMember'=>$readyByMember,
  566.                 'avDisplayMap' => Availability::$displayMap,
  567.                 'avColorMap'=> Availability::$colorMap,
  568.                 'comments'=>$commentsByMember
  569.             ]
  570.         );
  571.     }
  572.     public function testAllocatorAction(Request $request)
  573.     {
  574.         $this->init();
  575.         $orderRepo $this->managerRegistry->getRepository(Order::class);
  576.         // Bestellungen
  577.         $start = (new \DateTime())->modify('+ 1 day')->setTime(0,0,0,0);
  578.         $end = clone $start;
  579.         $end->setTime(8,59,59);
  580.         $orders $orderRepo->findNotAssignedToAnyMemberForDateRange($start,$end);
  581.         $memberRepo $this->managerRegistry->getRepository(CoopMember::class);
  582.         $members $memberRepo->findBy(['showInCalendar'=>true]);
  583.         $membersById = [];
  584.         foreach ($members as $m)
  585.         {
  586.             $membersById[$m->getId()] = $m;
  587.         }
  588.         $setRepo $this->managerRegistry->getRepository(Setting::class);
  589.         $allocEnabled $setRepo->getOptional(Setting::AUTOMATIC_RIDE_ASSIGNMENT,false);
  590.         // assignment
  591.         $arrLog = new ArrayLogWrapper();
  592.         $jac = new JobAllocator($this->managerRegistry->getManager(),$arrLog);
  593.         $results $jac->assignForNextDayProposal();
  594.         return $this->render('@DiplixKMG/Dispatching/alloc.html.twig',[
  595.             'orders'=>$orders,
  596.             'allocEnabled'=>$allocEnabled,
  597.             'members' => $membersById,
  598.             'map'=>$results,
  599.             'log'=> $arrLog->get()
  600.         ]);
  601.     }
  602.     public function setAutomaticAction(Request $request,$mode)
  603.     {
  604.         $this->init();
  605.         $setRepo $this->managerRegistry->getRepository(Setting::class);
  606.         $setRepo->set(Setting::AUTOMATIC_RIDE_ASSIGNMENT,$mode==='on'?true:false);
  607.         return $this->redirectToRoute('dispatch-preview-allocator');
  608.     }
  609.     public function checkLoggedInAction(Request $request)
  610.     {
  611.         // actually this is not working in combination with the configured firewall/access_control
  612.         // as long as we can reach this point and send any response we are still logged in
  613.         // otherwise symfony triggers a redirect
  614.         if(!$this->isGranted(Role::USER))
  615.         {
  616.             return $this->getJsonDataResponse($request,false);
  617.         }
  618.         return $this->getJsonDataResponse($request,true);
  619.     }
  620.     public function editNoteAction(Request $request $noteId)
  621.     {
  622.         $this->init();
  623.         $em $this->managerRegistry->getManager();
  624.         $noteRepo $em->getRepository(Note::class);
  625.         if ($noteId==0)
  626.         {
  627.             $row = new Note();
  628.             $row->setStartTime(new \DateTimeImmutable());
  629.         }
  630.         else
  631.         {
  632.             $row $noteRepo->find($noteId);
  633.             if ($row===null)
  634.             {
  635.                 return $this->getJsonDataResponse($request,false,null,'Notiz nicht gefunden');
  636.             }
  637.         }
  638.         try {
  639.             $form $this->createForm(NoteForm::class,$row,[]);
  640.             $form->handleRequest($request);
  641.             if ($form->isSubmitted())
  642.             {
  643.                 if ($form->isValid())
  644.                 {
  645.                     $em->persist($row);
  646.                     $em->flush();
  647.                     return $this->getJsonDataResponse($request,true);
  648.                 }
  649.                 return $this->getJsonDataResponse($request,false,
  650.                     $this->renderView('@DiplixKMG/Default/dialog_edit.html.twig',[
  651.                         'form' =>$form->createView(),
  652.                         'row' =>$row,
  653.                     ]),
  654.                     'Bitte überprüfen Sie Ihre Eingabe. '.$form->getErrors()->__toString());
  655.             }
  656.             return $this->getJsonDataResponse(
  657.                 $request,
  658.                 true,
  659.                 $this->renderView('@DiplixKMG/Default/dialog_edit.html.twig',[
  660.                     'form' =>$form->createView(),
  661.                     'row' =>$row,
  662.                 ])
  663.             );
  664.         }
  665.         catch (\Exception $ex)
  666.         {
  667.             //throw $ex;
  668.             return $this->getJsonDataResponse($request,false,$ex->getMessage(),$ex->getMessage());
  669.         }
  670.     }
  671.     public function doneNoteAction(Request $request $noteId)
  672.     {
  673.         $this->init();
  674.         $em $this->managerRegistry->getManager();
  675.         $noteRepo $em->getRepository(Note::class);
  676.               $row $noteRepo->find($noteId);
  677.         if ($row===null)
  678.         {
  679.             return $this->getJsonDataResponse($request,false,null,'Notiz nicht gefunden');
  680.         }
  681.         try {
  682.             $form $this->createForm(NoteDoneForm::class,$row,[]);
  683.             $form->handleRequest($request);
  684.             if ($form->isSubmitted())
  685.             {
  686.                 if ($form->isValid())
  687.                 {
  688.                     $row->setDone(new \DateTimeImmutable());
  689.                     $em->flush();
  690.                     return $this->getJsonDataResponse($request,true);
  691.                 }
  692.                 return $this->getJsonDataResponse($request,false,
  693.                     $this->renderView('@DiplixKMG/Default/dialog_edit.html.twig',[
  694.                         'form' =>$form->createView(),
  695.                         'row' =>$row,
  696.                     ]),
  697.                     'Bitte überprüfen Sie Ihre Eingabe. '.$form->getErrors()->__toString());
  698.             }
  699.             return $this->getJsonDataResponse(
  700.                 $request,
  701.                 true,
  702.                 $this->renderView('@DiplixKMG/Default/dialog_edit.html.twig',[
  703.                     'form' =>$form->createView(),
  704.                     'row' =>$row,
  705.                 ])
  706.             );
  707.         }
  708.         catch (\Exception $ex)
  709.         {
  710.             //throw $ex;
  711.             return $this->getJsonDataResponse($request,false,$ex->getMessage(),$ex->getMessage());
  712.         }
  713.     }
  714. }