src/Diplix/KMGBundle/Controller/Accounting/JobController.php line 151

Open in your IDE?
  1. <?php
  2. namespace Diplix\KMGBundle\Controller\Accounting;
  3. use Diplix\Commons\DataHandlingBundle\Repository\SysLogRepository;
  4. use Diplix\KMGBundle\Controller\BaseController;
  5. use Diplix\KMGBundle\Controller\Member\MyMembershipController;
  6. use Diplix\KMGBundle\DataTables\DataTablesHelper;
  7. use Diplix\KMGBundle\DataTables\Expr\ExprStub;
  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\JobCalcItem;
  12. use Diplix\KMGBundle\Entity\Customer;
  13. use Diplix\KMGBundle\Entity\Order;
  14. use Diplix\KMGBundle\Entity\Role;
  15. use Diplix\KMGBundle\Form\Accounting\ImportJobFileForm;
  16. use Diplix\KMGBundle\Form\Accounting\JobForm;
  17. use Diplix\KMGBundle\Helper\ClientConfigProvider;
  18. use Diplix\KMGBundle\PriceCalculator\CalculatorService;
  19. use Diplix\KMGBundle\Repository\JobRepository;
  20. use Diplix\KMGBundle\Service\Accounting\PaymentCalculator;
  21. use Diplix\KMGBundle\Service\Accounting\TamiJobImporter;
  22. use Diplix\KMGBundle\Service\OrderHandler;
  23. use Diplix\KMGBundle\Util\ExcelExportHelper;
  24. use Doctrine\ORM\AbstractQuery;
  25. use Doctrine\ORM\EntityManagerInterface;
  26. use Doctrine\ORM\EntityRepository;
  27. use Doctrine\ORM\Tools\Pagination\Paginator;
  28. use PHPExcel_Style_Border;
  29. use Symfony\Component\Form\FormError;
  30. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  31. use Symfony\Component\HttpFoundation\File\UploadedFile;
  32. use Symfony\Component\HttpFoundation\JsonResponse;
  33. use Symfony\Component\HttpFoundation\Request;
  34. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  35. use Vtiful\Kernel\Excel;
  36. class JobController extends BaseController
  37. {
  38.     public static $columnSetup =
  39.                 array(  0=>array('fieldName' => 'job.id''caption' => '#''headStyle' => 'width:16px;''type' =>DataTablesHelper::T_SELECTOR),
  40.                         1=>array('fieldName' => 'job.orderNumber''caption' => 'Auftrag'),
  41.                         2=>array('fieldName' => 'job.orderTime' 'caption'=>'Datum'),
  42.                         3=>array('fieldName' => 'cust.name','caption'=>'Kunde'),
  43.                         4=>array('fieldName' => 'job.originCity','caption'=>'Ab Ort'),
  44.                         5=>array('fieldName' => 'job.destinationCity''caption'=>'Zu Ort'),
  45.                         6=>array('fieldName' => 'mem.id''caption'=>'MID','visible'=>false,'searchWithLike'=>false),
  46.                         7=>array('fieldName' => 'job.driverId''caption'=>'Fahrer','visible'=>false),
  47.                         8=>array('fieldName' => 'job.member''caption'=>'M''virtual'=>true,'visible'=>false,'sortable'=>false),
  48.                         9=>array('fieldName' => 'job.billed''caption'=>'A''type'=>DataTablesHelper::T_CSSICON'virtual'=>true),
  49.                         10=>array('fieldName' => 'mem.shortCode','virtual'=>false,'visible'=>true,'caption'=>"Mitglied"),
  50.                         11=>array('fieldName' => '_order_id','virtual'=>true,'visible'=>false),
  51.                         12=>array('fieldName' => '_ffl','virtual'=>true,'visible'=>false),
  52.                         13=>array('fieldName' => '_readyforBilling','virtual'=>true,'visible'=>false),
  53.                         14=>array('fieldName' => '_customer_id','virtual'=>true,'visible'=>false),
  54.                         15=>array('fieldName' => 'job.totalCustomerNet''caption'=>'€Kunde'),
  55.                         16=>array('fieldName' => 'job.totalMemberNet''caption'=> '€Mitgld.'),
  56.                         17=>array('fieldName' => '_buttons''caption' => '''virtual' =>true'headStyle' => 'width:96px;''type' =>DataTablesHelper::T_BUTTONS),
  57.                         18=>array('fieldName'=>'job.passenerNotMet','visible'=>false,'sortable'=>false,'searchable'=>false),
  58.                         19=>array('fieldName'=>'job.discarded','visible'=>false,'sortable'=>false,'searchable'=>false),
  59.                         20=>    [ 'fieldName' => '_hasExtraMemberKm''visible'=>false'virtual'=>true],
  60.                         21=>    [ 'fieldName' => '_changedByMember''visible'=>false'virtual'=>true],
  61.                         22=>    [ 'fieldName' => 'lastReadyForBillingCheckResult''visible'=>false'virtual'=>true],
  62.                         23=>    [ 'fieldName' => '_approvalStatus''visible'=>false'virtual'=>true],
  63.                         24=>    [ 'fieldName' => 'ord.orderId''visible'=>false],
  64.                         25=>    [ 'fieldName' => 'ord.orderStatus''visible'=>false,'sortable'=>false,'searchable'=>false],
  65.                         26=>    [ 'fieldName' => 'ord.internalComment','visible'=>false,'sortable'=>false,'searchable'=>false],
  66.                         27=>    [ 'fieldName' => 'ord.taxameter','visible'=>false,'sortable'=>false,'searchable'=>false],
  67.                         28=>['fieldName'=>'_km','caption'=>'A/KM','virtual'=>true,'sortable'=>false],
  68.                         29=>['fieldName'=>'_p','caption'=>'A/P','virtual'=>true,'sortable'=>false],
  69.                         30=>['fieldName'=>'_wz','caption'=>'A/WZ(€)','virtual'=>true,'sortable'=>false],
  70.                         31=>['fieldName'=>'_ex','caption'=>'A/EX(€)','virtual'=>true,'sortable'=>false],
  71.                         32=>['fieldName'=>'_ctrl1','virtual'=>true,'visible'=>false],
  72.                         33=>['fieldName'=>'_ctrl2','virtual'=>true,'visible'=>false],
  73.                         34=>[ 'fieldName' => 'pt.icon','visible'=>false,'sortable'=>false,'searchable'=>false],
  74.                         35=>['fieldName'=>'_wzmin','caption'=>'A/WZ(Min)','virtual'=>true,'sortable'=>false],
  75.                         36=>['fieldName'=>'_customersignature','caption'=>'','virtual'=>true,'sortable'=>false,'visible'=>false],
  76.                         37=>['fieldName'=>'_discount','caption'=>'R','virtual'=>true,'sortable'=>false],
  77.                         38=>['fieldName'=>'ord.originOrderId','caption'=>'','virtual'=>false,'sortable'=>false,'visible'=>false]
  78.                 );
  79.     protected function getDth()
  80.     {
  81.         if ($this->config->isAlternateJobListView())
  82.         {
  83.             self::$columnSetup[3]['visible']=false;
  84.         }
  85.         return new DataTablesHelper(self::$columnSetup,
  86.                     [
  87.                     'ajaxUrl'       => $this->get('router')->generate('acc-jobs-jsonlist'),
  88.                     'ajaxData'      => 'requestData',
  89.                     'ajaxType'      => "POST",
  90.                     'deferLoading'  => true,
  91.                     'stateSave'=>false// implemented manually
  92.                     'defaultSorting'=> array(2=> 'asc')
  93.                     ]);
  94.     }
  95.     protected function init()
  96.     {
  97.         $this->ensureUserHasRole(Role::ACCOUNTING);
  98.     }
  99.     public function __construct(
  100.         protected PaymentCalculator $paymentCalculator,
  101.         protected CalculatorService $calculatorService,
  102.         protected TamiJobImporter $tamiJobImporter,
  103.         ClientConfigProvider $ccp,
  104.         private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry
  105.     )
  106.     {
  107.         $this->config $ccp;
  108.     }
  109.     protected function getMemberList()
  110.     {
  111.         $r $this->managerRegistry->getRepository(CoopMember::class);
  112.         $all $r->findBy([],['name'=>'asc']);
  113.         $list = [];
  114.         foreach ($all as $a){
  115.             $list [$a->getId()] = $a->getShortCode() . ' ('.$a->getName().')';
  116.         }
  117.         return $list;
  118.     }
  119.     /** @var \Diplix\KMGBundle\Helper\ClientConfigProvider */
  120.     protected $config;
  121.     public function indexAction()
  122.     {
  123.         $this->init();
  124.         //$jr = $this->getDoctrine()->getRepository(Job::class);
  125.         //$carList = $jr->findCars();
  126.         $now = new \DateTime();
  127.         $from = (new \DateTime())->sub(new \DateInterval('P3M'));
  128.         $dth $this->getDth();
  129.         return $this->render('@DiplixKMG/Accounting/Job/list.html.twig',[
  130.             'columnSetup' =>$dth->getColumnSetup(),
  131.             'memberList' => $this->getMemberList(),
  132.             'filterFrom' =>$from->format('Y-m-d'),
  133.             'filterTo' =>$now->format('Y-m-d')
  134.         ]);
  135.     }
  136.     public function memberListAction(Request $request)
  137.     {
  138.         $this->init();
  139.         /** @var EntityRepository $repo */
  140.         $repo $this->managerRegistry->getRepository(Job::class);
  141.         $cb $repo->createQueryBuilder('job')
  142.             ->select('job,mem')
  143.             ->leftJoin('job.member','mem')
  144.             ->groupBy('mem.id');
  145.         $req $request->query->all();
  146.         $from = new \DateTime($req["filterFrom"]); $from->setTime(0,0,0);
  147.         $to = new \DateTime($req["filterTo"]); $to->setTime(23,59,59);
  148.         $cb->andWhere('job.orderTime >= :from')->setParameter('from',$from);
  149.         $cb->andWhere('job.orderTime <= :to')->setParameter('to',$to);
  150.         //$cb->orderBy('mem.shortCode','asc');
  151.         $jobs $cb->getQuery()->getResult();
  152.         $members = [];
  153.         /** @var Job $j */
  154.         foreach ($jobs as $j)
  155.         {
  156.             if ($j->getMember()===null) continue;
  157.             $mid $j->getMember()->getId();
  158.             if (!array_key_exists($mid,$members))
  159.             {
  160.                 $members[$mid] = $j->getMember()->getShortCode() . ' ('.$j->getMember()->getName().')';
  161.             }
  162.         }
  163.         uasort($members, fn($a,$b) => strcmp($a,$b));
  164.         return $this->getJsonDataResponse($request,true,$members);
  165.     }
  166.     protected function nf($v)
  167.     {
  168.         return number_format($v,2,',','.');
  169.     }
  170.     protected function diTi($time)
  171.     {
  172.         if ($time === null) return '-';
  173.         return $time->format('H:i');
  174.     }
  175.     public function jsonListAction(Request $request)
  176.     {
  177.         $this->init();
  178.         $dth $this->getDth();
  179.         $req array_merge($request->query->all(),$request->request->all());
  180.         /** @var EntityRepository $repo */
  181.         $repo $this->managerRegistry->getRepository(Job::class);
  182.         $cb $repo->createQueryBuilder('job')
  183.                         ->select('job','ord')
  184.                         ->leftJoin('job.member','mem')
  185.                         ->leftJoin('job.customer','cust')
  186.                         ->leftJoin('job.knownOrder','ord')
  187.                         ->leftJoin("ord.paymentType","pt");
  188.         // process filtering,ordering,paging
  189.         $dth->setQueryBuilder($cb);
  190.         $dth->processRequest($request);
  191.         // additional filters
  192.         $from = new \DateTime($req["filterFrom"]); $from->setTime(0,0,0);
  193.         $to = new \DateTime($req["filterTo"]); $to->setTime(23,59,59);
  194.         $cb->andWhere('job.orderTime >= :from')->setParameter('from',$from);
  195.         $cb->andWhere('job.orderTime <= :to')->setParameter('to',$to);
  196.         $dth->addSimpleFilter(10,$req,$dth->getRawColumnOrder($req),
  197.             [ExprStub::like("mem.shortCode")],"mem.shortCode",true);
  198.         //// XOR filter
  199.         if ($req['filterBilled']==="true")
  200.         {
  201.             $cb->andWhere('job.billingForCustomer IS NOT NULL');
  202.             $cb->andWhere('job.billingForMember IS NOT NULL');
  203.         }
  204.         else
  205.         {
  206.             $cb->andWhere($cb->expr()->orX('job.billingForCustomer IS NULL','job.billingForMember IS NULL'));
  207.         }
  208.         if ($req['filterDiscarded']==='true')
  209.         {
  210.             $cb->andWhere('job.discarded = 1');
  211.         }
  212.         else
  213.         {
  214.             $cb->andWhere('job.discarded = 0');
  215.         }
  216.         /// triState Filter
  217.         if ($req['filterFFL']==="true")
  218.         {
  219.             $cb->andWhere("job.detectedFFL != ''");
  220.         }
  221.         else
  222.         if ($req['filterFFL']==="false")
  223.         {
  224.             $cb->andWhere("job.detectedFFL = ''");
  225.         }
  226.         $plainBoolFilters = [
  227.             'filterReadyForBilling' => 'job.readyForBilling',
  228.             'filterApprovedByDriver' => 'job.approvedByDriver',
  229.             'filterApprovedByPassenger' => 'job.approvedByPassenger',
  230.             'filterApprovedByAccounting' => 'job.approvedByAccounting'
  231.         ];
  232.         foreach ($plainBoolFilters as $formName=>$dbName)
  233.         {
  234.             if ($req[$formName]==='true')
  235.             {
  236.                 $cb->andWhere($dbName.' = 1');
  237.             }
  238.             else
  239.             if ($req[$formName]==='false')
  240.             {
  241.                 $cb->andWhere($dbName.' = 0');
  242.             }
  243.         }
  244.         // query and compile paged data
  245.         $query $cb->getQuery();
  246.         $query->setHydrationMode(AbstractQuery::HYDRATE_OBJECT);
  247.         $paginator = new Paginator($cb->getQuery(),true);
  248.         $filteredCount $paginator->count();
  249.         $filteredAndPaged = array();
  250.         $dit $paginator->getIterator();
  251.         /** @var Job  $row */
  252.         foreach ($dit as $row)
  253.         {
  254.             $memberDetails MyMembershipController::sumUpMemberCalcCategories($row,true);
  255.             $options = [];
  256.             $options[]= array('route' => 'acc-jobs-edit''title' => 'Bearbeiten''icon' => 'glyphicon glyphicon-pencil''parameters' =>array('id' =>$row->getId()) );
  257.             if ($this->hasUserRole(Role::ACCOUNTING_EDIT))
  258.             {
  259.                 $options[]= array('attr'=>['onClick' => sprintf('approvePrompt([%d],true);',$row->getId())], 'title' => 'Freigeben für Abrechnung''icon' => 'glyphicon glyphicon-thumbs-up''parameters' =>array('id' =>$row->getId()) );
  260.                 $options[]= array('attr'=>['onClick' => sprintf('delPrompt(%d,%s);',$row->getId(),$row->isDiscarded()?'true':false)], 'title' => 'Löschen''icon' => 'glyphicon glyphicon-trash''parameters' =>array('id' =>$row->getId()) );
  261.             }
  262.             $one = array(
  263.                     => $row->getId(),
  264.                     => $row->getOrderNumber(),
  265.                     => ( $row->getOrderTime() !== null $row->getOrderTime()->format('d.m.Y H:i') : '-'),
  266.                     =>     $row->getCustomer() !== null $row->getCustomer()->getName(): $row->getCustomerName(),
  267.                     => $row->getOriginCity(),
  268.                     => $row->getDestinationCity(),
  269.                     => $row->getMember()!== null $row->getMember()->getId() : 0,
  270.                     => $row->getDriverId(),
  271.                     => $row->getMember() !== null 10,
  272.                     => ($row->isBilled(Billing::TYPE_CUSTOMER)  &&  $row->isBilled(Billing::TYPE_MEMBER))  ? 0,
  273.                     10 => $row->getMember() !== null $row->getMember()->getShortCode(): '',
  274.                     11 => $row->getKnownOrder() !== null $row->getKnownOrder()->getId() : 0,
  275.                     12 => $row->getDetectedFFL() ,
  276.                     13 => $row->isReadyForBilling(),
  277.                     14 => $row->getCustomer() !== null $row->getCustomer()->getId() : 0,
  278.                     15 => $row->getTotalCustomerNet(),
  279.                     16 => $row->getTotalMemberNet(),
  280.                     17 =>$options,
  281.                     18 => $row->getPassengerNotMet() ? 0,
  282.                     19 => $row->isDiscarded() ? 0,
  283.                     20 => $row->getHasMemberExtraKm() ? 0,
  284.                     21 => $row->getLastChangeByMember()!==null 0,
  285.                     22 => $row->getLastReadyForBillingCheckResult(),
  286.                     23 => $row->getApprovalStatus(),
  287.                     24 => $row->getKnownOrder() !== null $row->getKnownOrder()->getOrderId() : '',
  288.                     25 => $row->getKnownOrder() !== null $row->getKnownOrder()->getOrderStatus()->getId() : '',
  289.                     26 => $row->getKnownOrder() !== null $row->getKnownOrder()->getInternalComment() : '',
  290.                     27 => $row->getTaxameter(),
  291.                     28 => $memberDetails[JobCalcItem::KM],
  292.                     29 => $memberDetails[JobCalcItem::Pauschale],
  293.                     30 => $this->nf($memberDetails[JobCalcItem::Wartezeit]['value']),
  294.                     31 => $this->nf($memberDetails[JobCalcItem::Extra]['value']),
  295.                     32 => implode(";",$row->getControlling()),
  296.                     33 => $memberDetails[JobCalcItem::Extra]['text'],
  297.                     34 => $row->getKnownOrder() !== null $row->getKnownOrder()->getPaymentType()->getIcon() : '',
  298.                     35 => $memberDetails[MyMembershipController::WartezeitMin] ?? 0,
  299.                     36 => ($row->getCustomerSignature()!==null) ? 0,
  300.                     37 => $row->getKnownOrder() !== null ? ( ($row->getKnownOrder()->getDiscount())??0) : 0,
  301.                     38 => ($row->getKnownOrder()!==null $row->getKnownOrder()->getOriginOrderId() : '')
  302.             );
  303.             $filteredAndPaged[]= $one;
  304.         }
  305.         // get total unfiltered count
  306.         $query $repo->createQueryBuilder('job')
  307.                         ->select('COUNT(job.id)')
  308.                         ->getQuery();
  309.         $res $query->getResult();
  310.         $totalRecords = (int)$res[0][1];
  311.         // compile output
  312.         $output = array(
  313.             'draw' => $req['draw'],
  314.             'recordsTotal' =>$totalRecords,
  315.             'recordsFiltered' =>$filteredCount,
  316.             'data' => $filteredAndPaged
  317.         );
  318.         return $this->getJsonResponse($request,$output);
  319.     }
  320.     public function exportAction(Request $request)
  321.     {
  322.         $this->init();
  323.         $req $request->query->all();
  324.         $from = new \DateTime($req['filterFrom']); $from->setTime(0,0,0);
  325.         $to = new \DateTime($req['filterTo']); $to->setTime(23,59,59);
  326.         $repo $this->managerRegistry->getRepository(Job::class);
  327.         $cb $repo->createQueryBuilder('job')
  328.             ->select('job','ord')
  329.             ->leftJoin('job.member','mem')
  330.             ->leftJoin('job.knownOrder','ord');
  331.         // filter time range
  332.         $from = new \DateTime($req["filterFrom"]); $from->setTime(0,0,0);
  333.         $to = new \DateTime($req["filterTo"]); $to->setTime(23,59,59);
  334.         $cb->andWhere('job.orderTime >= :from')->setParameter('from',$from);
  335.         $cb->andWhere('job.orderTime <= :to')->setParameter('to',$to);
  336.         $member = (int)($req['filterCar']??0);
  337.         if ($member 0)
  338.         {
  339.             $cb->andWhere('job.member = :member')->setParameter('member',$member);
  340.         }
  341.         $cb
  342.             ->andWhere('job.discarded = 0')
  343.             ->orderBy('job.orderTime','asc');
  344.         $list $cb->getQuery()->getResult(AbstractQuery::HYDRATE_OBJECT);
  345.         $tempFn $this->getParameter("dx.temp_dir") . uniqid("export"true) . ".xlsx";
  346.         $xh = new ExcelExportHelper();
  347.         ExcelExportHelper::$xlsHeadStyle['borders'] = array('bottom' => ['style' => PHPExcel_Style_Border::BORDER_THIN]);
  348.         $xh->setSingleSheetMode('Fahrten');
  349.         $xh->addIndexedRow(sprintf('Fahrten %s bis %s (%s)',$from->format('d.m.y'),$to->format('d.m.y'),($member>'Einzelmitglied':'Alle Mitglieder')));
  350.         /** @var Job $row */
  351.         foreach ($list as $row)
  352.         {
  353.             $memberDetails MyMembershipController::sumUpMemberCalcCategories($row,true);
  354.             $one = array(
  355.                 "Bestellung"    => $row->getKnownOrder() !== null $row->getKnownOrder()->getOrderId() : '',
  356.                 "Datum"         => ($row->getOrderTime() !== null $row->getOrderTime()->format('d.m.Y H:i') : '-'),
  357.                 "Kostenstelle"  => [$row->getKnownOrder() !== null $row->getKnownOrder()->getCostCenter() : ''ExcelExportHelper::TYPE_STRING],
  358.                 "Start"         => $row->getOriginCity(),
  359.                 "Ziel"          => $row->getDestinationCity(),
  360.                 "Mitglied"      => $row->getMember() !== null $row->getMember()->getShortCode(): '',
  361.                 "Kunde"      => $row->getCustomer() !== null $row->getCustomer()->getName(): '',
  362.                 "Stil" => [$row->getRideStyle(),ExcelExportHelper::TYPE_STRING],
  363.                 "FRA"           => [$row->getDetectedFFL() != '' 10ExcelExportHelper::TYPE_NUMERIC],
  364.                 "Nicht angetroffen" => [$row->getPassengerNotMet() ? ExcelExportHelper::TYPE_NUMERIC],
  365.                 "Werksrundfahrt" => [$row->isWerksRundfahrt()?1:0,ExcelExportHelper::TYPE_NUMERIC],
  366.                 "Bewerberfahrt" => [$row->isApplicantTimeRide()?1:0,ExcelExportHelper::TYPE_NUMERIC],
  367.                 "Sofortfahrt" => [$row->isInstantRide()?1:0,ExcelExportHelper::TYPE_NUMERIC],
  368.                 "Wartezeitfahrt" => [$row->isExtraWaitingTimeRide()?1:0,ExcelExportHelper::TYPE_NUMERIC],
  369.                 "Wartezeit" => [$row->getExtraWaitingTime(),ExcelExportHelper::TYPE_NUMERIC],
  370.                 "Mehr-KM abw." => [$row->getHasMemberExtraKm()?1:0ExcelExportHelper::TYPE_NUMERIC],
  371.                 "Mehr-KM"  => [$row->getMemberExtraKm(), ExcelExportHelper::TYPE_NUMERIC],
  372.                 "Kunde/€"       => [$row->getTotalCustomerNet(), ExcelExportHelper::TYPE_NUMERIC],
  373.                 "Mitglied/€"    => [$row->getTotalMemberNet(), ExcelExportHelper::TYPE_NUMERIC],
  374.                 "Abfahrt" => [$row->getDepartureAtOriginTime()!==null $row->getDepartureAtOriginTime()->format("H:i") :''ExcelExportHelper::TYPE_STRING],
  375.                 "Ankunft" => [$row->getArrivalAtDestinationTime()!==null ?$row->getArrivalAtDestinationTime()->format("H:i") :''ExcelExportHelper::TYPE_STRING],
  376.                 "Pos-Zeit" => [$row->getFlightOnPositionTime()!==null $row->getFlightOnPositionTime()->format("H:i") :''ExcelExportHelper::TYPE_STRING],
  377.                 'KM' => [ $memberDetails[JobCalcItem::KM], ExcelExportHelper::TYPE_NUMERIC],
  378.                 'P' => $memberDetails[JobCalcItem::Pauschale],
  379.                 'WZ(€)' => [$memberDetails[JobCalcItem::Wartezeit]['value'], ExcelExportHelper::TYPE_NUMERIC],
  380.                 'DA(€)' => [$memberDetails[JobCalcItem::Stunde]['value'], ExcelExportHelper::TYPE_NUMERIC],
  381.                 'EX(€)' => [$memberDetails[JobCalcItem::Extra]['value'], ExcelExportHelper::TYPE_NUMERIC],
  382.                 'EX Kommentar' => [$row->getExtraCostName(), ExcelExportHelper::TYPE_STRING],
  383.                 "Ursprungsauftrag" => [ $row->getKnownOrder() !== null $row->getKnownOrder()->getOriginOrderId() : ''ExcelExportHelper::TYPE_STRING]
  384.             );
  385.             $xh->addRow($one,false,[ => ExcelExportHelper::$centerH]);
  386.         }
  387.         $xh->changeColumnWidths([
  388.             => 20,
  389.             => 25,
  390.             => 25,
  391.         ]);
  392.         // finalize
  393.         $outFn ""
  394.             .'Fahrten'
  395.             .'_von_'.$from->format('Y-m-d')
  396.             .'_bis_'.$to->format('Y-m-d')
  397.             .'_Stand_'.date('Y-m-d_H-i')
  398.             .'.' .$xh->getExtension();
  399.         $xh->saveTo($tempFn);
  400.         $resp = new BinaryFileResponse$tempFn);
  401.         $resp->setContentDisposition(
  402.             ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  403.             $outFn
  404.         );
  405.         return $resp;
  406.     }
  407.     public function exportAccAction(Request $request)
  408.     {
  409.         $this->init();
  410.         $req $request->query->all();
  411.         $from = new \DateTime($req['filterFrom']); $from->setTime(0,0,0);
  412.         $to = new \DateTime($req['filterTo']); $to->setTime(23,59,59);
  413.         $repo $this->managerRegistry->getRepository(Job::class);
  414.         $cb $repo->createQueryBuilder('job')
  415.             ->select('job','ord')
  416.             ->leftJoin('job.member','mem')
  417.             ->leftJoin('job.knownOrder','ord');
  418.         // filter time range
  419.         $from = new \DateTime($req["filterFrom"]); $from->setTime(0,0,0);
  420.         $to = new \DateTime($req["filterTo"]); $to->setTime(23,59,59);
  421.         $cb->andWhere('job.orderTime >= :from')->setParameter('from',$from);
  422.         $cb->andWhere('job.orderTime <= :to')->setParameter('to',$to);
  423.         $member = (int)($req['filterCar']??0);
  424.         if ($member 0)
  425.         {
  426.             $cb->andWhere('job.member = :member')->setParameter('member',$member);
  427.         }
  428.         $cb
  429.             ->andWhere('job.discarded = 0')
  430.             ->orderBy('job.orderTime','asc');
  431.         $list $cb->getQuery()->getResult(AbstractQuery::HYDRATE_OBJECT);
  432.         $tempFn $this->getParameter("dx.temp_dir") . uniqid("export"true) . ".xlsx";
  433.         $xh = new ExcelExportHelper();
  434.         ExcelExportHelper::$xlsHeadStyle['borders'] = array('bottom' => ['style' => PHPExcel_Style_Border::BORDER_THIN]);
  435.         $xh->setSingleSheetMode('Mitglieder,Fahrer');
  436.         $xh->addIndexedRow(sprintf('Fahrten %s bis %s (%s)',$from->format('d.m.y'),$to->format('d.m.y'),($member>'Einzelmitglied':'Alle Mitglieder')));
  437.         //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  438.         $SAP_NR 'LeistungsNr';
  439.         $item2array = function ($typeJobCalcItem $item) use($SAP_NR) {
  440.             return [
  441.                 "M/K" => $type,
  442.                 $SAP_NR => $item->accNumber,
  443.                 "Menge" => [$item->quantityExcelExportHelper::TYPE_NUMERIC],
  444.                 "Betrag" => [$item->amountExcelExportHelper::TYPE_NUMERIC],
  445.                 "Summe" => [$item->totalNetExcelExportHelper::TYPE_NUMERIC],
  446.                 "MwSt." => [$item->vatExcelExportHelper::TYPE_NUMERIC],
  447.                 "Text" => [$item->nameExcelExportHelper::TYPE_STRING]
  448.             ];
  449.         };
  450.         $all = [
  451.             'M' => [],
  452.             'K' => [],
  453.         ];
  454.         /** @var Job $row */
  455.         foreach ($list as $row)
  456.         {
  457.             $base = [
  458.                 "Bestellung"    => $row->getKnownOrder() !== null $row->getKnownOrder()->getOrderId() : '',
  459.                 "Datum"         => ($row->getOrderTime() !== null $row->getOrderTime()->format('d.m.Y H:i') : '-'),
  460.                 "Kostenstelle"  => [$row->getKnownOrder() !== null $row->getKnownOrder()->getCostCenter() : ''ExcelExportHelper::TYPE_STRING],
  461.                 "Start"         => $row->getOriginCity(),
  462.                 "Ziel"          => $row->getDestinationCity(),
  463.                 "Mitglied"      => $row->getMember() !== null $row->getMember()->getShortCode(): '',
  464.                 "Stil" => [$row->getRideStyle(),ExcelExportHelper::TYPE_STRING],
  465.             ];
  466.             /** @var JobCalcItem $item */
  467.             foreach ($row->getMemberCalculationItems() as $item)
  468.             {
  469.                 $all["M"][] = array_merge($item2array("M",$item),$base);
  470.             }
  471.             foreach ($row->getCustomerCalculationItems() as $item)
  472.             {
  473.                 $all["K"][] = array_merge($item2array("K",$item),$base);
  474.             }
  475.         }
  476.         // Summieren
  477.         $blank = [ 'Menge'=>0,'Summe'=>0];
  478.         $sum = [];
  479.         foreach ($all as $part)
  480.         foreach ($part as $one)
  481.         {
  482.             $k $one[$SAP_NR];
  483.             if (!array_key_exists($k,$sum))
  484.             {
  485.                 $sum[$k] = $blank;
  486.             }
  487.             $sum[$k]['Menge'] += (int)$one['Menge'][0];
  488.             $sum[$k]['Summe'] += $one['Summe'][0];
  489.         }
  490.         // erstes sheet
  491.         foreach ($all['M'] as $one)
  492.         {
  493.             $xh->addRow($one);
  494.         }
  495.         // zweites Sheet
  496.         $xh->addSheet('Interne Kunden',true);
  497.         foreach ($all['K'] as $one)
  498.         {
  499.             $xh->addRow($one);
  500.         }
  501.         // Summen
  502.         $xh->addSheet('Summen');
  503.         $xh->setupColumns([$SAP_NR,"Menge","Summe"]);
  504.         foreach ($sum as $k=>$one)
  505.         {
  506.             $xh->addRow([
  507.                 $SAP_NR => $k,
  508.                 'Menge'=> [$one['Menge'], ExcelExportHelper::TYPE_NUMERIC],
  509.                 'Summe' => [$one['Summe'], ExcelExportHelper::TYPE_NUMERIC]
  510.             ]);
  511.         }
  512.         /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  513.         // finalize
  514.         $outFn ""
  515.             .'Fahrten-Abrechnung'
  516.             .'_von_'.$from->format('Y-m-d')
  517.             .'_bis_'.$to->format('Y-m-d')
  518.             .'_Stand_'.date('Y-m-d_H-i')
  519.             .'.' .$xh->getExtension();
  520.         $xh->saveTo($tempFn);
  521.         $resp = new BinaryFileResponse$tempFn);
  522.         $resp->setContentDisposition(
  523.             ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  524.             $outFn
  525.         );
  526.         return $resp;
  527.     }
  528.     public function recalcAction(Request $request)
  529.     {
  530.         $this->init();
  531.         /** @var JobRepository $repo */
  532.         $repo $this->managerRegistry->getRepository(Job::class);
  533.         $ids json_decode($request->request->get('selector','[]'),true);
  534.         if (count($ids)<1)
  535.         {
  536.             $this->addFlash('warning','Keine Einträge ausgewählt.');
  537.             return $this->redirectToRoute('acc-jobs');
  538.         }
  539.         if (count($ids)>250)
  540.         {
  541.             $this->addFlash('warning','Bitte wählen Sie max. 250 Einträge für eine Neuberechnung aus.');
  542.             return $this->redirectToRoute('acc-jobs');
  543.         }
  544.         $qb $repo->createQueryBuilder('J');
  545.         $qb->where($qb->expr()->in('J.id',':ids'))->setParameter('ids',$ids);
  546.         $jobs $qb->getQuery()->getResult();
  547.         /** @var Job $row */
  548.         foreach ($jobs as $row)
  549.         {
  550.             if ($row->getMember() === null) {
  551.                continue;
  552.             }
  553.             if ($row->getCustomer() === null) {
  554.                 continue;
  555.             }
  556.             $pc $this->paymentCalculator;
  557.             $pc->setFlashBag($this->container->get('session')->getFlashBag());
  558.             $pc->calculateTotals($row,$row->isBilled(Billing::TYPE_CUSTOMER),$row->isBilled(Billing::TYPE_MEMBER),);
  559.             $on $row->getKnownOrder() !== null $row->getKnownOrder()->getOrderId() : $row->getOrderNumber();
  560.             $this->addFlash('info', ['html' => PaymentCalculator::log2html($on.': Berechnungsergebnis für Mitglieder'$row->getMemberCalculationItems())]);
  561.             $this->addFlash('info', ['html' => PaymentCalculator::log2html($on.': Berechnungsergebnis für Kunden'$row->getCustomerCalculationItems())]);
  562.             if (count($row->getControlling())>0)
  563.             {
  564.                 $this->addFlash('info', ['html' => PaymentCalculator::log2html($on.': Controlling '$row->getControlling())]);
  565.             }
  566.         }
  567.         $repo->flush();
  568.         return $this->redirectToRoute('acc-jobs');
  569.     }
  570.     public function editAction(Request $request$id)
  571.     {
  572.         $this->init();
  573.         /** @var EntityManagerInterface $em */
  574.         $em $this->managerRegistry->getManager();
  575.         /** @var JobRepository $repo */
  576.         $repo $this->managerRegistry->getRepository(Job::class);
  577.         /** @var Job $row */
  578.         $row $repo->findOneBy(['id'=>$id]);
  579.         $prev $repo->findPreviousJobForMember($row);
  580.         $next $repo->findNextJobForMember($row);
  581.         $customerPrev $repo->findPreviousJobForCustomer($row);
  582.         $customerNext $repo->findNextJobForCustomer($row);
  583.         $form $this->createForm(JobForm::class,$row,[]);
  584.         $form->handleRequest($request);
  585.         if ( $form->isSubmitted() )
  586.         {
  587.             $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  588.             // fetch changes
  589.             $uow $em->getUnitOfWork();
  590.             $uow->computeChangeSets();
  591.             $changeSet $uow->getEntityChangeSet($row);
  592.             if ( (isset($changeSet['member'])) && ($form->has('confirmMemberChange')) && ($form->get('confirmMemberChange')->getData()!==true) )
  593.             {
  594.                 $form->get('confirmMemberChange')->addError(new FormError('Bitte bestätigen Sie die Änderung des Mitglieds.'));
  595.             }
  596.             if ( (isset($changeSet['customer'])) && ($form->has('confirmCustomerChange')) && ($form->get('confirmCustomerChange')->getData()!==true) )
  597.             {
  598.                 $form->get('confirmCustomerChange')->addError(new FormError('Bitte bestätigen Sie die Änderung des Kunden.'));
  599.             }
  600.             if ($form->isValid())
  601.             {
  602.                 $repo->flush($row); // initial flush required, otherwise doctrine may loose changes on the way. TODO : why is that ?
  603.                 if ( ($form->has('calc')) && ($form->get('calc')->isClicked()) ) {
  604.                     if ($row->getMember() === null) {
  605.                         $this->addFlash('danger''Fehler: Kein Mitglied zugeordnet !');
  606.                     }
  607.                     if ($row->getCustomer() === null) {
  608.                         $this->addFlash('danger''Fehler: Kein Kunde zugeordnet !');
  609.                     }
  610.                     $pc $this->paymentCalculator;
  611.                     $pc->setFlashBag($this->container->get('session')->getFlashBag());
  612.                     $pc->calculateTotals($row,
  613.                         $row->isBilled(Billing::TYPE_CUSTOMER),
  614.                         $row->isBilled(Billing::TYPE_MEMBER),
  615.                     );
  616.                     $this->addFlash('info', ['html' => PaymentCalculator::log2html('Berechnungsergebnis für Mitglieder'$row->getMemberCalculationItems())]);
  617.                     $this->addFlash('info', ['html' => PaymentCalculator::log2html('Berechnungsergebnis für Kunden'$row->getCustomerCalculationItems())]);
  618.                     if (count($row->getControlling())>0)
  619.                     {
  620.                         $this->addFlash('info', ['html' => PaymentCalculator::log2html('Controlling '$row->getControlling())]);
  621.                     }
  622.                 }
  623.                 if (($form->has('checkReadyForBilling')) && ($form->get('checkReadyForBilling')->isClicked())) {
  624.                     $msg = [];
  625.                     JobRepository::detectReadyForBilling($row$msg);
  626.                     if ($row->isReadyForBilling())
  627.                     {
  628.                         $this->addFlash('info''Buchung ist bereit zur Abrechnung');
  629.                     }
  630.                     else
  631.                     {
  632.                         // if the job is not ready for billing, the warning messages and the state are not persisted since
  633.                         // we also do not want to persist the other changes made via form
  634.                         $this->addFlash('warning''Buchung ist nicht bereit zur Abrechnung');
  635.                         foreach ($msg as $k=>$m) {
  636.                             $this->addFlash('warning'$m);
  637.                             if ($form->has($k))
  638.                             {
  639.                                 $form->get($k)->addError(new FormError($m));
  640.                             }
  641.                         }
  642.                     }
  643.                 }
  644.                 if ($form->getErrors(true,true)->count() < 1)
  645.                 {
  646.                     $row->setLastChangeByMember(null);
  647.                     $this->addFlash('success','Änderungen gespeichert.');
  648.                     $repo->flush($row);
  649.                     return $this->redirectToRoute('acc-jobs-edit', ['id' => $row->getId()]);
  650.                 }
  651.             }
  652.         }
  653.         return $this->render('@DiplixKMG/Accounting/Job/edit.html.twig',[
  654.             'row'=>$row,
  655.             'prev' => $prev,
  656.             'next' => $next,
  657.             'config'=>$this->config,
  658.             'customerPrev'=>$customerPrev,
  659.             'customerNext'=>$customerNext,
  660.             'form' => $form->createView(),
  661.         ]);
  662.     }
  663.     public function deleteAction(Request $request$id)
  664.     {
  665.         $this->init();
  666.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  667.         /** @var EntityManagerInterface $em */
  668.         $em $this->managerRegistry->getManager();
  669.         /** @var JobRepository $repo */
  670.         $repo $this->managerRegistry->getRepository(Job::class);
  671.         $d $request->request->get('discard',-1);
  672.         $ids = [ $id ];
  673.         /** @noinspection TypeUnsafeComparisonInspection */
  674.         if ($id == 0)
  675.         {
  676.             $ids json_decode($request->request->get('selector','[]'),true);
  677.         }
  678.         if (count($ids)<1)
  679.         {
  680.             $this->addFlash('warning','Keine Einträge ausgewählt.');
  681.             return $this->redirectToRoute('acc-jobs');
  682.         }
  683.         $r $repo->discardJobs($ids,$d==1?true:false,$this->getCurrentUser()->getId());
  684.         $this->addFlash('success',sprintf('%d Einträge wurden aktualisiert.',$r));
  685.         return $this->redirectToRoute('acc-jobs');
  686.     }
  687.     public function importAction(Request $request)
  688.     {
  689.         $this->init();
  690.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  691. //        if (!$this->getParameter('tami.enabled'))
  692. //        {
  693. //            $this->addFlash('warning', 'TaMi-Integration ist deaktiviert.');
  694. //            return $this->redirectToRoute('acc-jobs');
  695. //        }
  696.         /** @var EntityManagerInterface $em */
  697.         $em $this->managerRegistry->getManager();
  698.         $form $this->createForm(ImportJobFileForm::class);
  699.         $form->handleRequest($request);
  700.         if ($form->isSubmitted())
  701.         {
  702.             if ($form->isValid())
  703.             {
  704.                 /** @var UploadedFile $file */
  705.                 $file $form->get("importFile")->getData();
  706.                 if ($file->isValid())
  707.                 {
  708.                     try
  709.                     {
  710.                         $imp $this->tamiJobImporter;
  711.                         $resultMessages $imp->importAndProcessJobFile($file->getRealPath());
  712.                         foreach ($resultMessages as $m)
  713.                         {
  714.                             $this->addFlash('success',$m);
  715.                         }
  716.                         return $this->redirectToRoute('acc-jobs');
  717.                     }
  718.                     catch (\Throwable $ex)
  719.                     {
  720.                         $this->addFlash('danger''Import fehlgeschlagen. (' .$ex->getMessage(). ')');
  721.                         throw $ex;
  722.                     }
  723.                 }
  724.                 $this->addFlash('danger''Upload fehlgeschlagen.');
  725.             }
  726.             else
  727.             {
  728.                 $this->addFlash('warning''Bitte überprüfen SIe Ihre Eingaben');
  729.             }
  730.         }
  731.         return $this->render('@DiplixKMG/Accounting/Job/import.html.twig', array('form' => $form->createView()));
  732.     }
  733.     public function reprocessAction(Request $request)
  734.     {
  735.         $this->init();
  736.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  737.         if (!$this->getParameter('tami.enabled'))
  738.         {
  739.             $this->addFlash('warning''TaMi-Integration ist deaktiviert.');
  740.             return $this->redirectToRoute('acc-jobs');
  741.         }
  742.         /** @var EntityManagerInterface $em */
  743.         $em $this->managerRegistry->getManager();
  744.         $imp $this->tamiJobImporter;
  745.         $repo $em->getRepository(Job::class);
  746.         $items $repo->findBy(['readyForBilling'=>false]);
  747.         $this->addFlash('success'count($items) . ' noch nicht zur Abrechnung bereite Einträge geladen');
  748.         $resultInfo $imp->postProcessImport($items);
  749.         foreach ($items as $job)
  750.         {
  751.             JobRepository::detectReadyForBilling($job);
  752.         }
  753.         $repo->flush();
  754.         $msgs $imp->getResultMessages($resultInfo);
  755.         foreach ($msgs as $m)
  756.         {
  757.             $this->addFlash('success',$m);
  758.         }
  759.         return $this->redirectToRoute('acc-jobs');
  760.     }
  761.     protected function timeOrNull($date)
  762.     {
  763.         if ($date === null)
  764.             return null;
  765.         return $date->format('H:i');
  766.     }
  767.     const XFER_ENTRY "[TRANSFER]";
  768.     public function submitxchgAction(Request $request)
  769.     {
  770.         /** @var Job $job */
  771.         $job null;
  772.         $repo null;
  773.         try
  774.         {
  775.             $this->init();
  776.             if ($request->getMethod()!==Request::METHOD_POST)
  777.             {
  778.                 throw new \RuntimeException('Use POST');
  779.             }
  780.             /** @var JobRepository $repo */
  781.             $repo $this->managerRegistry->getRepository(Job::class);
  782.             $sel $request->get('selector','[]');
  783.             $ids strpos($sel,"[") !== false json_decode($sel,true) : [(int)$sel];
  784.             if (count($ids)<1)
  785.             {
  786.                 throw new \RuntimeException("Keine Einträge ausgewählt. ($sel)");
  787.             }
  788.             if (count($ids)>1)
  789.             {
  790.                 throw new \RuntimeException('Zuviele Einträge ausgewählt.');
  791.             }
  792.             $qb $repo->createQueryBuilder('J');
  793.             $qb->where($qb->expr()->in('J.id',':ids'))->setParameter('ids',$ids);
  794.             $jobs $qb->getQuery()->getResult();
  795.             $clientCache = [];
  796.             /** @var Job $job */
  797.             $job reset($jobs);
  798.             if ($job->getCustomer() === null)
  799.             {
  800.                 throw new \RuntimeException("Kein Kunde im Auftrag");
  801.             }
  802.             if ($job->getKnownOrder() === null)
  803.             {
  804.                 throw new \RuntimeException("Keine Bestellung im Auftrag");
  805.             }
  806.             if ($job->getCustomer()->getXchgPlatformUrl()=="")
  807.             {
  808.                 throw new \RuntimeException("Fehlende Fremdsystem-URL");
  809.             }
  810.             if ($job->getKnownOrder()->getXchgStatus()!==Order::XCHG_RECEIVED_FROM_OTHER)
  811.             {
  812.                 throw new \RuntimeException("Auftrag kommt nicht von Fremdsystem.");
  813.             }
  814.             if (!array_key_exists($job->getCustomer()->getId(),$clientCache))
  815.             {
  816.                 $clientCache[$job->getCustomer()->getId()]= OrderHandler::getClientForCustomerWhoLoginsAsMember($job->getCustomer());;
  817.             }
  818.             $client $clientCache[$job->getCustomer()->getId()];
  819.             $res $client->request("POST",rtrim($job->getCustomer()->getXchgPlatformUrl(),'/')."/job-update-by-oid",[
  820.                 'headers'=> [
  821.                     "Content-Type"=>"application/json"
  822.                 ],
  823.                 'body' => json_encode(
  824.                     [
  825.                         "login" => $job->getCustomer()->getXchgLoginAsMember(),
  826.                         "password" => $job->getCustomer()->getXchgLoginPassword(),
  827.                         "orderId"=> $job->getKnownOrder()->getXchgOrderId(),
  828.                         "xchg"=>true,
  829.                         // data
  830.                         'flightOnPositionTime' => $this->timeOrNull($job->getFlightOnPositionTime()),
  831.                         'departureAtOriginTime' => $this->timeOrNull($job->getDepartureAtOriginTime()),
  832.                         'arrivalAtDestinationTime' => $this->timeOrNull($job->getArrivalAtDestinationTime()),
  833.                         'passengerNotMet'=>$job->getPassengerNotMet(),
  834.                         'taxameter'=>$job->getTaxameter(),
  835.                         'taxameterVat'=>$job->getTaxameterVat(),
  836.                         'extraCostName'=>$job->getExtraCostName(),
  837.                         'extraCostPrice'=>$job->getExtraCostPrice(),
  838.                         'hasMemberExtraKm' => $job->getHasMemberExtraKm(),
  839.                         'memberExtraKm'=>$job->getMemberExtraKm(),
  840.                         'confirmedByCustomerThrough'=>$job->getConfirmedByCustomerThrough(),
  841.                         'approvedByDriver'=>$job->isApprovedByDriver(),
  842.                         'werksRundfahrt' => $job->isWerksRundfahrt(),
  843.                         'extraWaitingTimeRide' => $job->isExtraWaitingTimeRide(),
  844.                         'extraWaitingTime' => $job->getExtraWaitingTime(),
  845.                         'instantRideAdditionalWaitingTime' => $job->getInstantRideAdditionalWaitingTime(),
  846.                     ]
  847.                     , JSON_THROW_ON_ERROR)
  848.             ]);
  849.             $res json_decode($res->getBody(), true512JSON_THROW_ON_ERROR);
  850.             if ($res['success']!==true)
  851.             {
  852.                 throw new \RuntimeException('Fehler bei der Übertragung.'.var_export($res,true));
  853.             }
  854.             $jobId =$job->getKnownOrder() !== null $job->getKnownOrder()->getOrderId().": " '';
  855.             return new JsonResponse(['success'=>true,'message'=> $jobId.'Job übertragen.']);
  856.         }
  857.         catch (\Throwable $ex)
  858.         {
  859.             $jobId'';
  860.             if ($job !== null)
  861.             {
  862.                 $cleanCo array_filter($job->getControlling(), function($v) { return strpos($vself::XFER_ENTRY) === false; });
  863.                 $cleanCo[]= self::XFER_ENTRY.$ex->getMessage();
  864.                 $job->setControlling($cleanCo);
  865.                 if ($repo !==null$repo->flush($job);
  866.                 SysLogRepository::logError($this->managerRegistry->getConnection(),$ex->getMessage().$ex->getTraceAsString(),$job);
  867.                 $jobId =$job->getKnownOrder() !== null $job->getKnownOrder()->getOrderId().": " '';
  868.             }
  869.             return new JsonResponse(['success'=>false,'message'=>$jobId.$ex->getMessage()]);
  870.         }
  871. //        $qb = $repo->createQueryBuilder('J');
  872. //        $qb->where($qb->expr()->in('J.id',':ids'))->setParameter('ids',$ids);
  873. //        $jobs = $qb->getQuery()->getResult();
  874. //        $clientCache = [];
  875. //        /** @var Job $job */
  876. //        foreach ($jobs as $job)
  877. //        {
  878. //            try
  879. //            {
  880. //                if ($job->getCustomer() === null)
  881. //                {
  882. //                    throw new \RuntimeException("Kein Kunde im Auftrag");
  883. //                }
  884. //                if ($job->getKnownOrder() === null)
  885. //                {
  886. //                    throw new \RuntimeException("Keine Bestellung im Auftrag");
  887. //                }
  888. //                if ($job->getCustomer()->getXchgPlatformUrl()=="")
  889. //                {
  890. //                    throw new \RuntimeException("Fehlende Fremdsystem-URL");
  891. //                }
  892. //                if ($job->getKnownOrder()->getXchgStatus()!==Order::XCHG_RECEIVED_FROM_OTHER)
  893. //                {
  894. //                    throw new \RuntimeException("Auftrag kommt nicht von Fremdsystem.");
  895. //                }
  896. //
  897. //                if (!array_key_exists($job->getCustomer()->getId(),$clientCache))
  898. //                {
  899. //                    $clientCache[$job->getCustomer()->getId()]= OrderHandler::getClientForCustomerWhoLoginsAsMember($job->getCustomer());;
  900. //                }
  901. //                $client = $clientCache[$job->getCustomer()->getId()];
  902. //
  903. //                $res = $client->request("POST",rtrim($job->getCustomer()->getXchgPlatformUrl(),'/')."/job-update-by-oid",[
  904. //                    'headers'=> [
  905. //                        "Content-Type"=>"application/json"
  906. //                    ],
  907. //                    'body' => json_encode(
  908. //                        [
  909. //                            "login" => $job->getCustomer()->getXchgLoginAsMember(),
  910. //                            "password" => $job->getCustomer()->getXchgLoginPassword(),
  911. //                            "orderId"=> $job->getKnownOrder()->getXchgOrderId(),
  912. //                            "xchg"=>true,
  913. //                            // data
  914. //                              'flightOnPositionTime' => $this->timeOrNull($job->getFlightOnPositionTime()),
  915. //                              'departureAtOriginTime' => $this->timeOrNull($job->getDepartureAtOriginTime()),
  916. //                              'arrivalAtDestinationTime' => $this->timeOrNull($job->getArrivalAtDestinationTime()),
  917. //                              'passengerNotMet'=>$job->getPassengerNotMet(),
  918. //                              'taxameter'=>$job->getTaxameter(),
  919. //                              'taxameterVat'=>$job->getTaxameterVat(),
  920. //                              'extraCostName'=>$job->getExtraCostName(),
  921. //                              'extraCostPrice'=>$job->getExtraCostPrice(),
  922. //                                'hasMemberExtraKm' => $job->getHasMemberExtraKm(),
  923. //                              'memberExtraKm'=>$job->getMemberExtraKm(),
  924. //                              'confirmedByCustomerThrough'=>$job->getConfirmedByCustomerThrough(),
  925. //                              'approvedByDriver'=>$job->isApprovedByDriver(),
  926. //
  927. //                            'werksRundfahrt' => $job->isWerksRundfahrt(),
  928. //                            'extraWaitingTimeRide' => $job->isExtraWaitingTimeRide(),
  929. //                            'extraWaitingTime' => $job->getExtraWaitingTime(),
  930. //                            'instantRideAdditionalWaitingTime' => $job->getInstantRideAdditionalWaitingTime(),
  931. //
  932. //                        ]
  933. //                        , JSON_THROW_ON_ERROR)
  934. //                ]);
  935. //
  936. //                $res = json_decode($res->getBody(), true, 512, JSON_THROW_ON_ERROR);
  937. //                if ($res['success']!==true)
  938. //                {
  939. //                    throw new \RuntimeException('Fehler bei der Übertragung.'.var_export($res,true));
  940. //                }
  941. //                $this->addFlash("success",sprintf("Job %s: %s",$job->getKnownOrder()->getOrderId(),"Übertragen."));
  942. //            }
  943. //            catch (\Throwable $ex)
  944. //            {
  945. //                $this->addFlash('danger',sprintf("Job %s: %s",$job->getKnownOrder()->getOrderId(),$ex->getMessage()));
  946. //                SysLogRepository::logError($this->getDoctrine()->getConnection(),$ex->getMessage().$ex->getTraceAsString(),$job);
  947. //            }
  948. //        }
  949. //        return $this->redirectToRoute('acc-jobs');
  950.     }
  951.     public function customerRatesAction(Request $request$customerId)
  952.     {
  953.         $repo $this->managerRegistry->getRepository(Customer::class);
  954.         /** @var Customer $customer */
  955.         $customer $repo->findOneBy(['id'=>$customerId]);
  956.         return new JsonResponse$customer->getAccRates() );
  957.     }
  958.     public function memberRatesAction(Request $request$memberId)
  959.     {
  960.         $repo $this->managerRegistry->getRepository(CoopMember::class);
  961.         /** @var CoopMember $mem  */
  962.         $mem $repo->findOneBy(['id'=>$memberId]);
  963.         return new JsonResponse$mem->getRates() );
  964.     }
  965.     public function checkReady4BillingAction(Request $request)
  966.     {
  967.         $this->init();
  968.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  969.         /** @var JobRepository $repo */
  970.         $repo $this->managerRegistry->getRepository(Job::class);
  971.         $ids json_decode($request->request->get('selector','[]'),true);
  972.         if (count($ids)<1)
  973.         {
  974.             $this->addFlash('warning','Keine Einträge ausgewählt.');
  975.             return $this->redirectToRoute('acc-jobs');
  976.         }
  977.         $qb $repo->createQueryBuilder('J');
  978.         $qb->where($qb->expr()->in('J.id',':ids'))->setParameter('ids',$ids);
  979.         $jobs $qb->getQuery()->getResult();
  980.         /** @var Job $j */
  981.         foreach ($jobs as $j)
  982.         {
  983.             $msg = [];
  984.             JobRepository::detectReadyForBilling($j$msg);
  985.             if ($j->isReadyForBilling())
  986.             {
  987.                 $j->setLastChangeByMember(null);
  988.             }
  989.             $repo->flush($j);
  990.             $orderTitle $j->getKnownOrder()!==null $j->getKnownOrder()->getOrderId() : 'null';
  991.             if ($j->getOrderNumber()!==''$orderTitle.= ' (TaMi: '.$j->getOrderNumber().')';
  992.             if ($j->isReadyForBilling())
  993.             {
  994.                 $this->addFlash('success'$orderTitle.': Buchung ist bereit zur Abrechnung');
  995.             }
  996.             else
  997.             {
  998.                 $txt '<b>'.$orderTitle.'</b><br>';
  999.                 foreach ($msg as $k=>$m) {
  1000.                     $txt.= $m.'<br>';
  1001.                 }
  1002.                 $this->addFlash('danger',['html'=>$txt]);
  1003.             }
  1004.         }
  1005.         return $this->redirectToRoute('acc-jobs');
  1006.     }
  1007.     public function approve4BillingAction(Request $request$approveOrNot)
  1008.     {
  1009.         $this->init();
  1010.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  1011.         /** @var JobRepository $repo */
  1012.         $repo $this->managerRegistry->getRepository(Job::class);
  1013.         $ids json_decode($request->request->get('selector','[]'),true);
  1014.         if (count($ids)<1)
  1015.         {
  1016.             $this->addFlash('warning','Keine Einträge ausgewählt.');
  1017.             return $this->redirectToRoute('acc-jobs');
  1018.         }
  1019.         $qb $repo->createQueryBuilder('J');
  1020.         $qb->where($qb->expr()->in('J.id',':ids'))->setParameter('ids',$ids);
  1021.         $jobs $qb->getQuery()->getResult();
  1022.         /** @var Job $j */
  1023.         foreach ($jobs as $j)
  1024.         {
  1025.             $on = ($j->getKnownOrder()!==null?$j->getKnownOrder()->getOrderId():'').' '.$j->getOrderNumber();
  1026.             if (!$j->isReadyForBilling())
  1027.             {
  1028.                 $this->addFlash('warning'$on.': Achtung: Buchung war nicht bereit zur Abrechnung. Freigabestatus wurde trotzdem verändert.');
  1029.             }
  1030.             /** @noinspection TypeUnsafeComparisonInspection */
  1031.             $approveOrNot = ($approveOrNot==1);
  1032.             $j->setApprovedByAccounting($approveOrNot);
  1033.             $this->addFlash('success'$on.': '.($approveOrNot?"Freigegeben":"Freigabe entfernt"));
  1034.         }
  1035.         $repo->flush();
  1036.         return $this->redirectToRoute('acc-jobs');
  1037.     }
  1038.     public function refreshFromOrderAction(Request $request$jobId)
  1039.     {
  1040.         $this->init();
  1041.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  1042.         $redir $this->redirectToRoute('acc-jobs-edit',['id'=>$jobId]);
  1043.         /** @var EntityManagerInterface $em */
  1044.         $em $this->managerRegistry->getManager();
  1045.         /** @var JobRepository $repo */
  1046.         $repo $this->managerRegistry->getRepository(Job::class);
  1047.         /** @var Job $job */
  1048.         $job $repo->findOneBy(['id'=>$jobId]);
  1049.         if ($job->isApprovedByAccounting())
  1050.         {
  1051.             $this->addFlash('warning','Fahrtdaten wurden bereits durch die Abrechnung bestätigt. Abgleich mit Bestellung nicht mehr möglich.');
  1052.             return $redir;
  1053.         }
  1054.         if ($job->getKnownOrder() === null)
  1055.         {
  1056.             $this->addFlash('warning','Es ist keine Bestellung verknüpft.');
  1057.             return $redir;
  1058.         }
  1059.         $job->updateFromOrder($job->getKnownOrder());
  1060.         $this->addFlash('info','Daten aus Bestellung neu übernommen');
  1061.         if ($job->getMember()===null)
  1062.         {
  1063.             $job->setMember($job->getKnownOrder()->getAssignedTo());
  1064.             if ($job->getMember()!==null)
  1065.             {
  1066.                 $this->addFlash('info',sprintf('Mitglied %s wurde übernommen.',$job->getMember()->getName()));
  1067.             }
  1068.         }
  1069.         else
  1070.         {
  1071.             if (( $job->getKnownOrder()->getAssignedTo()!==null)&&($job->getKnownOrder()->getAssignedTo()->getId()!=$job->getMember()->getId()) )
  1072.             {
  1073.                 $this->addFlash('warning',sprintf('Achtung Mitglieder unterscheiden sich. Bestellung: %s, Fahrauftrag: %s. Diese Änderung wurde nicht automatisch übernommen.',
  1074.                     $job->getKnownOrder()->getAssignedTo()->getName(), $job->getMember()->getName()
  1075.                 ));
  1076.             }
  1077.         }
  1078.         $repo->flush();
  1079.         return $redir;
  1080.     }
  1081.     public function reCalcDistanceAction(Request $request$jobId)
  1082.     {
  1083.         $this->init();
  1084.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  1085.         $redir $this->redirectToRoute('acc-jobs-edit',['id'=>$jobId]);
  1086.         /** @var EntityManagerInterface $em */
  1087.         $em $this->managerRegistry->getManager();
  1088.         /** @var JobRepository $repo */
  1089.         $repo $this->managerRegistry->getRepository(Job::class);
  1090.         /** @var Job $job */
  1091.         $job $repo->findOneBy(['id'=>$jobId]);
  1092.         if ($job->isApprovedByAccounting())
  1093.         {
  1094.             $this->addFlash('warning','Fahrtdaten wurden bereits durch die Abrechnung bestätigt. Abgleich mit Bestellung nicht mehr möglich.');
  1095.             return $redir;
  1096.         }
  1097.         if ($job->getKnownOrder() === null)
  1098.         {
  1099.             $this->addFlash('warning','Es ist keine Bestellung verknüpft.');
  1100.             return $redir;
  1101.         }
  1102.         $calcService $this->calculatorService;
  1103.         /** @var Order $order */
  1104.         $order $job->getKnownOrder();
  1105.         $flatWaypoints = [ $order->getPriceList()->getHomeAddress() ]; // start/endpoint
  1106.         foreach ($order->getAddressList() as $a) {
  1107.             $flatWaypoints[] = CalculatorService::addressToFlatString(json_decode(json_encode($a),true));
  1108.         }
  1109.         $distances =  $calcService->getDistance($flatWaypoints);
  1110.         $job->setActualKm$calcService->getDistanceSumInKm($distances) );
  1111.         $this->addFlash('info',sprintf('Berechnete Distanz: %02.2f',$job->getActualKm()));
  1112.         $repo->flush();
  1113.         return $redir;
  1114.     }
  1115.     public function reCheckCombiAction(Request $request$jobId)
  1116.     {
  1117.         $this->init();
  1118.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  1119.         $redir $this->redirectToRoute('acc-jobs-edit',['id'=>$jobId]);
  1120.         /** @var EntityManagerInterface $em */
  1121.         $em $this->managerRegistry->getManager();
  1122.         /** @var JobRepository $repo */
  1123.         $repo $this->managerRegistry->getRepository(Job::class);
  1124.         /** @var Job $job */
  1125.         $job $repo->findOneBy(['id'=>$jobId]);
  1126.         if ($job->isApprovedByAccounting())
  1127.         {
  1128.             $this->addFlash('warning','Fahrtdaten wurden bereits durch die Abrechnung bestätigt. Aktualisierung nicht möglich.');
  1129.             return $redir;
  1130.         }
  1131.         $pc $this->paymentCalculator;
  1132.         if ($pc->detectCombiAssociations($job))
  1133.         {
  1134.             $this->addFlash('info','Zuordnung wurde erneut durchgeführt');
  1135.             $em->flush();
  1136.         }
  1137.         return $redir;
  1138.     }
  1139.     public function removeCombiAction(Request $request$jobId)
  1140.     {
  1141.         $this->init();
  1142.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  1143.         $redir $this->redirectToRoute('acc-jobs-edit',['id'=>$jobId]);
  1144.         /** @var EntityManagerInterface $em */
  1145.         $em $this->managerRegistry->getManager();
  1146.         /** @var JobRepository $repo */
  1147.         $repo $this->managerRegistry->getRepository(Job::class);
  1148.         /** @var Job $job */
  1149.         $job $repo->findOneBy(['id'=>$jobId]);
  1150.         if ($job->isApprovedByAccounting())
  1151.         {
  1152.             $this->addFlash('warning','Fahrtdaten wurden bereits durch die Abrechnung bestätigt. Aktualisierung nicht möglich.');
  1153.             return $redir;
  1154.         }
  1155.         self::removeRelation($job);
  1156. //        if ($job->getCombiRideChild()!==null)
  1157. //        {
  1158. //            $job->getCombiRideChild()->setCombiRideParent(null);
  1159. //        }
  1160. //        $job->setCombiRideParent(null);
  1161.         $job->setCombiAssignmentState(Job::CAS_MANUAL);
  1162.         $em->flush();
  1163.         $this->addFlash('info','Zuordnung wurde entfernt. Automatik deaktiviert.');
  1164.         return $redir;
  1165.     }
  1166.     public function getPossibleCombiOptionsAction(Request $request$jobId)
  1167.     {
  1168.         $this->init();
  1169.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  1170.         $redir $this->redirectToRoute('acc-jobs-edit',['id'=>$jobId]);
  1171.         /** @var EntityManagerInterface $em */
  1172.         $em $this->managerRegistry->getManager();
  1173.         /** @var JobRepository $repo */
  1174.         $repo $this->managerRegistry->getRepository(Job::class);
  1175.         /** @var Job $job */
  1176.         $job $repo->findOneBy(['id'=>$jobId]);
  1177.         $possible $repo->findPossiblyRelatedRidesForMemberAsArray($job);
  1178.         return $this->getJsonDataResponse($request,true,$possible);
  1179.     }
  1180.     protected static function removeRelation(Job $job)
  1181.     {
  1182.         if ($job->getCombiRideChild()!==null)
  1183.         {
  1184.             $job->getCombiRideChild()->setCombiRideParent(null);
  1185.         }
  1186.         $job->setCombiRideParent(null);
  1187.     }
  1188.     public function setManualCombiAction(Request $request$jobId$assignJobId)
  1189.     {
  1190.         $this->init();
  1191.         $this->ensureUserHasRole(Role::ACCOUNTING_EDIT);
  1192.         $redir $this->redirectToRoute('acc-jobs-edit',['id'=>$jobId]);
  1193.         /** @var EntityManagerInterface $em */
  1194.         $em $this->managerRegistry->getManager();
  1195.         /** @var JobRepository $repo */
  1196.         $repo $this->managerRegistry->getRepository(Job::class);
  1197.         /** @var Job $job */
  1198.         $job $repo->findOneBy(['id'=>$jobId]);
  1199.         /** @var Job $assignJob */
  1200.         $assignJob $repo->findOneBy(['id'=>$assignJobId]);
  1201.         if ($job->isApprovedByAccounting() || $assignJob->isApprovedByAccounting())
  1202.         {
  1203.             $this->addFlash('warning','Fahrtdaten wurden bereits durch die Abrechnung bestätigt. Aktualisierung nicht möglich.');
  1204.             return $redir;
  1205.         }
  1206.         if ($jobId === $assignJobId)
  1207.         {
  1208.             $this->addFlash('warning','Zuordnung zweier gleicher Fahrten nicht möglich.');
  1209.             return $redir;
  1210.         }
  1211.         // remove assignment
  1212.         self::removeRelation($job);
  1213.         self::removeRelation($assignJob);
  1214.         // set assignment
  1215.         if ($assignJob->getOrderTime() < $job->getOrderTime())
  1216.         {
  1217.             $job->setCombiRideParent($assignJob);
  1218.         }
  1219.         else
  1220.         {
  1221.             $assignJob->setCombiRideParent($job);
  1222.         }
  1223.         $job->setCombiAssignmentState(Job::CAS_MANUAL);
  1224.         $assignJob->setCombiAssignmentState(Job::CAS_MANUAL);
  1225.         $em->flush();
  1226.         $this->addFlash('info','Zuordnung wurde gespeichert. Automatik deaktiviert.');
  1227.         return $redir;
  1228.     }
  1229. }