<?php 
namespace Diplix\KMGBundle\Controller\Accounting; 
 
use Diplix\KMGBundle\Controller\BaseController; 
use Diplix\KMGBundle\DataTables\DataTablesHelper; 
use Diplix\KMGBundle\DataTables\Expr\ExprStub; 
use Diplix\KMGBundle\Entity\Accounting\Billing; 
use Diplix\KMGBundle\Entity\Accounting\Document; 
use Diplix\KMGBundle\Entity\Accounting\Job; 
use Diplix\KMGBundle\Entity\Accounting\JobCalcItem; 
use Diplix\KMGBundle\Entity\File; 
use Diplix\KMGBundle\Entity\Platform\PlatformClient; 
use Diplix\KMGBundle\Entity\Role; 
use Diplix\KMGBundle\Entity\StandByDispoEntry; 
use Diplix\KMGBundle\Form\Accounting\DocumentForm; 
use Diplix\KMGBundle\Form\Accounting\NewDocumentForm; 
use Diplix\KMGBundle\Form\DefaultDeleteForm; 
use Diplix\KMGBundle\Helper\ClientConfigProvider; 
use Diplix\KMGBundle\PdfGeneration\InvoicePdf; 
use Diplix\KMGBundle\PdfGeneration\ZugferdGenerator; 
use Diplix\KMGBundle\Repository\FileRepository; 
use Diplix\KMGBundle\Service\MailHelper; 
use Diplix\KMGBundle\Util\ExcelExportHelper; 
use Doctrine\ORM\AbstractQuery; 
use Doctrine\ORM\EntityManagerInterface; 
use Doctrine\ORM\EntityRepository; 
use Doctrine\ORM\Tools\Pagination\Paginator; 
use horstoeko\zugferd\ZugferdDocumentReader; 
use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerDefaultRenderer; 
use horstoeko\zugferdvisualizer\ZugferdVisualizer; 
use League\Flysystem\FilesystemOperator; 
use PHPExcel_Style_Border; 
use PHPExcel_Style_Fill; 
use Symfony\Component\Form\FormError; 
use Symfony\Component\HttpFoundation\BinaryFileResponse; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\RedirectResponse; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\HttpFoundation\ResponseHeaderBag; 
use Symfony\Component\HttpFoundation\StreamedResponse; 
use Symfony\Component\PropertyAccess\PropertyAccess; 
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 
 
class BillingController extends BaseController 
{ 
    public static $bold = [ 
        'font' => array( 
            'name' => 'Arial', 
            'bold'=> true, 
            'italic'    => false, 
        )]; 
 
    public static $columnSetup = 
                array(  0=>array('fieldName' => 'doc.id', 'caption' => '#', 'headStyle' => 'width:16px;', 'type' =>DataTablesHelper::T_SELECTOR), 
                        1=>array('fieldName' => 'doc.type', 'caption' => 'T','headStyle' => 'width:16px;'), 
                        2=>array('fieldName' => 'doc.number','caption'=>'Nummer'), 
                        3=>array('fieldName' => 'doc.date' , 'caption'=>'Datum'), 
                        4=>array('fieldName' => 'doc.accountingMonth', 'caption'=>'M'), 
                        5=>array('fieldName' => 'doc.accountingYear', 'caption'=>'Y'), 
                        6=>array('fieldName' => 'doc.ref', 'virtual'=>true, 'caption'=>'Kunde/Mitglied'), 
                        7=>array('fieldName' => 'doc.totalNet','caption'=>'Gesamtbetrag'), 
                        8=>array('fieldName' => 'doc.status', 'caption'=>'Status'), 
                        9=>array('fieldName'=>'sentToReceiver','type'=>DataTablesHelper::T_CSSICON, 'caption'=>'V','virtual'=>true), 
                        10=>array('fieldName' => '_buttons', 'caption' => '', 'virtual' =>true, 'headStyle' => 'width:96px;', 'type' =>DataTablesHelper::T_BUTTONS), 
                        11=>array('fieldName' => '_status','visible'=>false,'virtual'=>true), 
 
                ); 
 
    protected function getDth() 
    { 
        return new DataTablesHelper(self::$columnSetup, 
                    [ 
                    'ajaxUrl'       => $this->get('router')->generate('acc-billing-jsonlist'), 
                    'ajaxData'      => 'requestData', // js function to call for getting additional req data 
                    'defaultSorting'=> array(3=> 'asc',2=> 'asc') 
                    ]); 
    } 
 
    protected function init() 
    { 
        $this->ensureUserHasRole(Role::ACCOUNTING); 
    } 
 
    public function __construct( 
        protected ClientConfigProvider $ccp, 
        FileRepository $fileRepository, 
        protected MailHelper $mailHelper, 
        protected FilesystemOperator $uploadsFilesystem, 
        private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry 
    ) 
    { 
        $this->fileRepo = $fileRepository; 
    } 
 
    public function indexAction(Request $request, $type) 
    { 
        $this->init(); 
 
        $now = (new \DateTime())->modify('-1 month'); 
        $aM = $request->query->get('am',$now->format('m')); 
        $aY = $request->query->get('ay',$now->format('Y')); 
        $now = new \DateTime(sprintf('%4d-%02d-01',$aY,$aM)); 
        $from = (clone $now)->sub(new \DateInterval('P3M')); 
        $until = (clone $now)->add(new \DateInterval('P3M')); 
 
        $dth = $this->getDth(); 
        return $this->render('@DiplixKMG/Accounting/Billing/list.html.twig',[ 
            'columnSetup' =>$dth->getColumnSetup(), 
            'type' => $type, 
            'stati' => Document::$statusMap, 
            'filterFrom' =>'',//$from->format('Y-m-01'), 
            'filterTo' =>'',//$until->format('Y-m-t'), 
            'accountingYear' => $aY, 
            'accountingMonth'=> $aM 
        ]); 
    } 
 
    public function jsonListAction(Request $request) 
    { 
        $this->init(); 
        $dth = $this->getDth(); 
 
        $req = $request->query->all(); 
        /** @var EntityRepository $repo */ 
        $repo = $this->managerRegistry->getRepository(Document::class); 
        $cb = $repo->createQueryBuilder("doc") 
                        ->select("doc") 
                        ->leftJoin("doc.member","mem") 
                        ->leftJoin("doc.customer","customer"); 
        // process filtering,ordering,paging 
        $dth->setQueryBuilder($cb); 
        if ($req['type']!=='') 
        { 
            $dth->addAnd('doc.type = :type',['type'=>$req['type']]); 
        } 
        if ($req['status']!=='') 
        { 
            $dth->addAnd('doc.status = :status',['status'=>$req['status']]); 
        } 
        if ($req['filterFrom']!=='') 
        { 
            $from = new \DateTime($req['filterFrom']); $from->setTime(0,0,0); 
            $cb->andWhere('doc.date >= :from')->setParameter('from',$from); 
        } 
        if ($req['filterTo']!=='') 
        { 
            $to = new \DateTime($req['filterTo']); $to->setTime(23,59,59); 
            $cb->andWhere('doc.date <= :to')->setParameter('to',$to); 
        } 
        if ($req['accountingMonth']!=='') 
        { 
            $cb->andWhere('doc.accountingMonth = :m')->setParameter('m',$req['accountingMonth']); 
        } 
        if ($req['accountingYear']!=='') 
        { 
            $cb->andWhere('doc.accountingYear = :y')->setParameter('y',$req['accountingYear']); 
        } 
 
        $rawOrder = $dth->getRawColumnOrder($req); 
        if ($req['type']===Document::TYPE_RECHNUNG) 
        { 
            $dth->addSimpleFilter(6,$req,$rawOrder,[ExprStub::like("customer.name")],"customer.name",true); 
        } 
        else 
        if ($req['type']===Document::TYPE_GUTSCHRIFT) 
        { 
            $dth->addSimpleFilter(6,$req,$rawOrder,[ExprStub::like("mem.name")],"mem.name",true); 
        } 
        $dth->addSimpleFilter(9,$req,$rawOrder,[],"doc.sentToReceiver"); 
 
 
        $dth->processRequest($request); 
        // query and compile paged data 
        $query = $cb->getQuery(); 
        $query->setHydrationMode(AbstractQuery::HYDRATE_OBJECT); 
        $paginator = new Paginator($cb->getQuery(),true); 
        $filteredCount = $paginator->count(); 
        $filteredAndPaged = array(); 
        $dit = $paginator->getIterator(); 
        /** @var Document  $row */ 
        foreach ($dit as $row) 
        { 
            $options = []; 
            $options[]= array("route"=>"acc-billing-edit", "title"=>"Bearbeiten", "icon"=>"glyphicon glyphicon-pencil", "parameters"=>array("id"=>$row->getId()) ); 
 
 
            $options[]= array("route"=>"acc-billing-new-plain", "title"=>"Klonen", "icon"=>"glyphicon glyphicon-screenshot", "parameters"=>array("type"=>$row->getType(),"cloneFrom"=>$row->getId()) ); 
 
            if ($this->hasUserRole(Role::ACCOUNTING_EDIT)) 
            { 
                $options[]= array("route"=>"acc-billing-delete", "title"=>"Löschen", "icon"=>"glyphicon glyphicon-trash", "parameters"=>array("id"=>$row->getId()) ); 
            } 
            $options[]= array("route"=>"acc-billing-pdf", "title"=>"Dokument", "icon"=>"glyphicon glyphicon-floppy-save", "parameters"=>array("id"=>$row->getId()) ); 
            $one = array( 
                    0 => $row->getId(), 
                    1 => Document::$typeMap[ $row->getType()], 
                    2 => $row->getNumber(), 
                    3 => ( $row->getDate() !== null ? $row->getDate()->format("d.m.Y") : "-" ), 
                    4 => sprintf('%02d',$row->getAccountingMonth()), 
                    5 => sprintf('%4d',$row->getAccountingYear()), 
                    6=> trim( ($row->getCustomer()!==null ? $row->getCustomer()->getName():"") ." ". ($row->getMember()!==null ? $row->getMember()->getName():"") ), 
                    7 => $row->getTotalNet(), 
                    8 => Document::$statusMap[ $row->getStatus() ] , 
                    9 => $row->isSentToReceiver()!==null ? 1 : 0, 
                    10 =>$options, 
                    11 => $row->getStatus() 
            ); 
            $filteredAndPaged[]= $one; 
        } 
        // get total unfiltered count 
        $query = $repo->createQueryBuilder("doc") 
                        ->select("COUNT(doc.id)") 
                        ->getQuery(); 
        $res = $query->getResult(); 
        $totalRecords = (int)$res[0][1]; 
        // compile output 
        $output = array( 
            "draw" => $req['draw'], 
            "recordsTotal"=>$totalRecords, 
            "recordsFiltered"=>$filteredCount, 
            "data" => $filteredAndPaged 
        ); 
        return $this->getJsonResponse($request,$output); 
    } 
 
    public function newDocumentAction(Request $request,$type) 
    { 
        $this->init(); 
        $this->ensureUserHasRole(Role::ACCOUNTING_EDIT); 
        $audience = $type===Document::TYPE_GUTSCHRIFT ? Billing::TYPE_MEMBER : Billing::TYPE_CUSTOMER; 
        return $this->redirectToRoute('acc-billing-process',['type'=>$audience,'fromStr'=>'null']); 
    } 
 
    public function editByBillingIdAction(Request $request, $billingId) 
    { 
        $this->init(); 
        $repo = $this->managerRegistry->getRepository(Document::class); 
        $row = $repo->findOneBy(['billing' =>$billingId]); 
        return $this->redirectToRoute('acc-billing-edit',['id'=> ($row!==null ? $row->getId() : -1 )]); 
    } 
 
    protected function newPdf(Document $row, ?ZugferdGenerator $zugferdGenerator = null) 
    { 
        $config = $this->ccp; 
        $pdfLogo = $config->getPdfLogo()!== null ?  '@' . stream_get_contents( $this->fileRepo->getStream($config->getPdfLogo()) ) : null; 
        $pdf = new InvoicePdf("de","",$pdfLogo); 
        $pdf->setZugferdGenerator($zugferdGenerator); 
        $pdf->setSender( 
            $config->getPdfSenderShort(), 
            $config->getPdfSenderLong(), 
            $config->getPdfFooter() 
        ); 
        $pdf->render($row); 
        return $pdf; 
    } 
 
    protected function createPdf(Document $row) 
    { 
        $pdf = $this->newPdf($row); 
        $pdfData = $pdf->Output('temp.pdf','S'); 
        $F = File::fromPsr7StreamOrString($pdfData, $row->generateFilename(), $this->uploadsFilesystem ,'pdf'); 
        $this->managerRegistry->getManager()->persist($F); 
        $this->managerRegistry->getManager()->flush(); 
        $this->addFlash('success','PDF-Datei wurde erzeugt'); 
        return $F; 
    } 
 
    protected function doFaktura(Document $row) 
    { 
        $row->setStatus(Document::STATUS_OFFEN); 
        $row->setTreatedBy($this->getCurrentUser()); 
        // generate PDF 
        if ($row->getPdfFile()===null) 
        { 
            $F = $this->createPdf($row); 
            $row->setPdfFile($F); 
        } 
    } 
 
    public function editAction(Request $request,$id) 
    { 
        $this->init(); 
        $repo = $this->managerRegistry->getRepository(Document::class); 
        /** @var Document $row */ 
        $row = $repo->findOneBy(['id' =>$id]); 
        if (!is_object($row)) 
        { 
            $this->addFlash('warning','Ungültige ID oder Zugriff verweigert.'); 
            return $this->redirectToRoute('acc-billing'); 
        } 
        $form = $this->createForm(DocumentForm::class,$row,[]); 
        $form->handleRequest($request); 
        if ( ($form->isSubmitted()) && ($form->isValid()) ) 
        { 
            $this->ensureUserHasRole(Role::ACCOUNTING_EDIT); 
 
            try { 
                if ($form->has('extraPdf')) 
                { 
                    if (!$this->handleFileUpload($form,$row,"extraPdf",File::TYPE_FILE)) 
                    { 
                        throw new \RuntimeException('Upload fehlgeschlagen.'); 
                    } 
                } 
            } 
            catch (\Throwable $ex) 
            { 
                $this->addFlash('error',$ex->getMessage()); 
                return $this->redirectToRoute('acc-billing-edit',['id'=>$row->getId()]); 
            } 
 
 
            $this->addFlash('success','Änderungen gespeichert.'); 
 
            $row->setTotalNet(    $row->recalculateExtraItems() + ($row->getBilling()!==null ?  $row->getBilling()->recalculateTotal() : 0)); 
 
            if ($form->has('set_paid') && $form->get('set_paid')->isClicked()) 
            { 
                $row->setStatus(Document::STATUS_BEZAHLT); 
                $this->addFlash('success','Dokument wurde bezahlt.'); 
            } 
            else 
            if ($form->has('set_open') && $form->get('set_open')->isClicked()) 
            { 
                $this->doFaktura($row); 
                $this->addFlash('success','Dokument wurde auf den Status "offen" aktualisiert.'); 
            } 
            else 
            if ($form->has('set_canceled') && $form->get('set_canceled')->isClicked()) 
            { 
                try 
                { 
                    $repo->cancelDocument($row); 
                    $this->addFlash('success','Dokument storniert'); 
                } 
                catch (\Throwable $ex) 
                { 
                    $this->addFlash('danger','Stornieren fehlgeschlagen. '.$ex->getMessage()); 
                } 
            } 
            else 
            if ($form->has('set_mahnung') && $form->get('set_mahnung')->isClicked()) 
            { 
                switch ($row->getStatus()) 
                { 
                    case Document::STATUS_OFFEN: 
                        $row->setStatus(Document::STATUS_MAHNSTUFE1); 
                        $row->setReminderPdf( $this->createPdf($row) ); 
                        break; 
                    case Document::STATUS_MAHNSTUFE1: 
                        $row->setStatus(Document::STATUS_MAHNSTUFE2); 
                        $row->setMonition1Pdf( $this->createPdf($row) ); 
                        break; 
                    case Document::STATUS_MAHNSTUFE2: 
                        $row->setStatus(Document::STATUS_MAHNSTUFE3); 
                        $row->setMonition2Pdf( $this->createPdf($row) ); 
                        break; 
                    default: 
                        throw $this->createAccessDeniedException('Ungültige Transition'); 
                } 
            } 
 
 
            $repo->flush(); 
 
            return $this->redirectToRoute('acc-billing-edit',['id'=>$row->getId()]); 
        } 
 
 
        return $this->render('@DiplixKMG/Accounting/Billing/edit.html.twig',[ 
            'form' => $form->createView(), 
            'statusMap' => Document::$statusMap, 
            'typeMap' => Document::$typeMap, 
            'row' => $row 
        ]); 
    } 
 
    public function extraPdfAction(Request $request, $id,$type) 
    { 
        $this->init(); 
        $repo = $this->managerRegistry->getRepository(Document::class); 
        /** @var Document $row */ 
        $row = $repo->findOneBy(['id' => $id]); 
        if (!is_object($row)) { 
            $this->addFlash('warning', 'Ungültige ID oder Zugriff verweigert.'); 
            return $this->redirectToRoute('acc-billing', ['type' => $row->getType()]); 
        } 
        $pa  = PropertyAccess::createPropertyAccessor(); 
        /** @var File $file */ 
        $file = $pa->getValue($row,$type); 
        if (!($file instanceof File)) 
        { 
            throw  $this->createAccessDeniedException('Ungültiger Zugriff'); 
        } 
 
        return $this->fileRepo->getDownloadResponseFor($request,$file->getId()); 
    } 
 
    public function pdfAction(Request $request, $id) 
    { 
        $this->init(); 
        $repo = $this->managerRegistry->getRepository(Document::class); 
        /** @var Document $row */ 
        $row = $repo->findOneBy(['id' =>$id]); 
        if (!is_object($row)) 
        { 
            $this->addFlash('warning','Ungültige ID oder Zugriff verweigert.'); 
            return $this->redirectToRoute('acc-billing',['type'=>""]); 
        } 
 
        // serve the existing pdf if there is one, skip only if forced to do so by request param 
        if (($row->getPdfFile() !== null) && ($request->get('forceGeneration',0)===0)) 
        { 
            return $this->fileRepo->getDownloadResponseFor($request,$row->getPdfFile()->getId()); 
        } 
 
        if (($row->getBilling() === null)&&(count($row->getExtraCalculationItems())===0)) 
        { 
            $this->addFlash('warning','PDF kann nicht mehr neu erzeugt werden da die Abrechnungsdaten nicht mehr vorhanden sind und keine weiteren Posten existieren.'); 
            return $this->redirectToRoute('acc-billing',['type'=>$row->getType()]); 
        } 
        // neu erzeugen 
        $pdfFile = $this->getParameter('dx.temp_dir'). $row->generateFilename().date('Y-m-d_H_i_s').'.pdf'; 
        try { 
            $pdf = $this->newPdf($row); 
            $pdf->Output($pdfFile); 
            return $this->getDownloadResponse($pdfFile,basename($pdfFile)); 
        } 
        catch (\Throwable $ex) 
        { 
            $this->addFlash('danger','PDF-Erzeugung fehlgeschlagen: '.$ex->getMessage().($ex->getPrevious()!==null ? $ex->getPrevious()->getMessage():"")); 
            return $this->redirectToRoute('acc-billing-edit',['id'=>$row->getId()]); 
        } 
    } 
 
    public function xmlAction(Request $request, EntityManagerInterface $em, $id) 
    { 
        $this->init(); 
        $repo = $em->getRepository(Document::class); 
        /** @var Document $row */ 
        $row = $repo->findOneBy(['id' =>$id]); 
        if (!is_object($row)) 
        { 
            $this->addFlash('warning','Ungültige ID oder Zugriff verweigert.'); 
            return $this->redirectToRoute('acc-billing',["type"=>""]); 
        } 
        $zugferd = new ZugferdGenerator(); 
        $zugferd->setup($row); 
        $zugferd->setSellerFromClientConfig($this->ccp); 
        $tempPfd = $this->newPdf($row,$zugferd); // fill zugferd with line items 
        $zugferd->finalize(); 
        $xml = $zugferd->getXmlContent(); 
 
        /** @noinspection TypeUnsafeComparisonInspection */ 
        if ($request->get('visualize',0)==1) 
        { 
            $doc = ZugferdDocumentReader::readAndGuessFromContent($xml); 
            $visualizer = new ZugferdVisualizer($doc); 
            // $visualizer->setDefaultTemplate(); 
            $visualizer->setRenderer(new ZugferdVisualizerDefaultRenderer()); 
            $visualizer->setTemplate(__DIR__ . "/../../Resources/views/Accounting/zugferd_visualizer_template.php"); 
            return new Response($visualizer->renderMarkup()); 
        } 
 
        $xmlFile = $this->getParameter('dx.temp_dir'). $row->generateFilename().date('Y-m-d_H_i_s').'.xml'; 
        file_put_contents($xmlFile,$xml); 
        return $this->getDownloadResponse($xmlFile,basename($xmlFile)); 
    } 
 
 
    public function deleteAction(Request $request, $id) 
    { 
        $this->init(); 
        $this->ensureUserHasRole(Role::ACCOUNTING_EDIT); 
        $repo = $this->managerRegistry->getRepository(Document::class); 
        /** @var Document $row */ 
        $row = $repo->findOneBy(['id' =>$id]); 
        if (!is_object($row)) 
        { 
            $this->addFlash('warning','Ungültige ID oder Zugriff verweigert.'); 
            return $this->redirectToRoute('acc-billing'); 
        } 
 
        $form = null; 
        if ($row->getStatus() === Document::STATUS_ENTWURF) 
        { 
            $form = $this->createForm( DefaultDeleteForm::class,array('id' =>$id)); 
            $form->handleRequest($request); 
            if ($form->isSubmitted()) 
            { 
                if ($form->get('commit')->getData() == 1) 
                { 
                    $repo->cancelDocument($row); 
                    $this->addFlash('success','message.record-deleted'); 
                } 
                return $this->redirectToRoute('acc-billing',['type'=>$row->getType()]); 
            } 
        } 
 
        return $this->render('@DiplixKMG/Accounting/Billing/delete.html.twig',array ( 
            'row' => $row, 
            'form' => $form !== null ? $form->createView() : null, 
        )); 
    } 
 
    public function sendByMailAction(Request $request, $id) 
    { 
        $this->ensureUserHasRole(Role::ACCOUNTING_EDIT); 
        $type = $request->query->get('type',''); 
        if ($request->getMethod()!==Request::METHOD_POST) 
        { 
            throw new \RuntimeException('POST required.'); 
        } 
        $row = null; 
        $acceptsJson = in_array('application/json', $request->getAcceptableContentTypes(),false); 
        try { 
            $this->init(); 
            $repo = $this->managerRegistry->getRepository(Document::class); 
            /** @var Document $row */ 
            $row = $repo->findOneBy(['id' =>$id]); 
            if (!is_object($row)) 
            { 
                throw new \RuntimeException('Ungültige ID oder Zugriff verweigert.'); 
            } 
            if ($row->getStatus() < Document::STATUS_OFFEN) { 
                throw new \RuntimeException('Bitte fakturieren Sie das Dokument vor dem PDF-Versand.'); 
            } 
            if ($row->getStatus() >= Document::STATUS_STORNIERT) 
            { 
                throw new \RuntimeException('Ein storniertes Dokument kann nicht per eMail versendet werden.'); 
            } 
 
            $mh = $this->mailHelper; 
            $mh->setFilesystem($this->uploadsFilesystem); 
            if ($row->getType() === Document::TYPE_RECHNUNG) 
            { 
                if ($type === '') 
                { 
                    $mh->SendInvoiceToCustomerMail($row); 
                    $row->setSentToReceiver(new \DateTime()); 
                } 
                else 
                if ($type === 'reminderPdf') 
                { 
                    $mh->SendInvoiceFollowUpToCustomerMail($row, Document::STATUS_MAHNSTUFE1); 
                    $row->setReminderSentToReceiver(new \DateTime()); 
                } 
                else 
                if ($type === 'monition1Pdf') 
                { 
                    $mh->SendInvoiceFollowUpToCustomerMail($row, Document::STATUS_MAHNSTUFE2); 
                    $row->setMonition1SentToReceiver(new \DateTime()); 
                } 
                else 
                if ($type === 'monition2Pdf') 
                { 
                    $mh->SendInvoiceFollowUpToCustomerMail($row, Document::STATUS_MAHNSTUFE3); 
                    $row->setMonition2SentToReceiver(new \DateTime()); 
                } 
                else 
                { 
                    throw new \Exception('Invalid type'); 
                } 
            } 
            else 
            if ($row->getType() === Document::TYPE_GUTSCHRIFT) 
            { 
                $mh->SendGutschriftToMemberMail($row); 
                $row->setSentToReceiver(new \DateTime()); 
            } 
 
            $repo->flush($row); 
            if (!$acceptsJson) 
            { 
                $this->addFlash('success','Dokument versendet.'); 
            } 
            else 
            { 
                return new JsonResponse(['success'=>true,'message'=>'Dokument versendet']); 
            } 
        } 
        catch (\Throwable $ex) 
        { 
            if (!$acceptsJson) 
            { 
                $this->addFlash('warning',$ex->getMessage()); 
            } 
            else 
            { 
                return new JsonResponse(['success'=>false,'message'=>$ex->getMessage()]); 
            } 
        } 
        if (!$acceptsJson) 
        { 
            return $this->redirectToRoute('acc-billing-edit',['id'=>$row->getId()]); 
        } 
    } 
 
 
    public function changeStateAction(Request $request, $id, $newStatus) 
    { 
        $this->ensureUserHasRole(Role::ACCOUNTING_EDIT); 
        $newStatus = (int)$newStatus; 
        try { 
            if ($request->getMethod() !== Request::METHOD_POST) { 
                throw new \RuntimeException('POST required.'); 
            } 
            $this->init(); 
            $repo = $this->managerRegistry->getRepository(Document::class); 
            /** @var Document $row */ 
            $row = $repo->findOneBy(['id' => $id]); 
            if (!is_object($row)) 
            { 
                throw new \RuntimeException('Ungültige ID oder Zugriff verweigert.'); 
            } 
            if ($newStatus !== Document::STATUS_BEZAHLT) 
            { 
                throw new \RuntimeException('Nichtunterstützter Statuswechsel (neuer Status ungültig)'); 
            } 
            if (!in_array( $row->getStatus() , [Document::STATUS_OFFEN,Document::STATUS_MAHNSTUFE1,Document::STATUS_MAHNSTUFE2,Document::STATUS_MAHNSTUFE3])) 
            { 
                throw new \RuntimeException('Nichtunterstützter Statuswechsel (Dokument im falschen Status)'); 
            } 
            $row->setStatus($newStatus); 
            $repo->flush($row); 
 
            return new JsonResponse([ 
                'success'=>true, 
                'message'=>'Status geändert.']); 
        } 
        catch (\Throwable $ex) 
        { 
            return new JsonResponse(['success' => false, 'message' => $ex->getMessage()]); 
        } 
    } 
 
 
    public function setOpenAction(Request $request, $id) 
    { 
        $this->ensureUserHasRole(Role::ACCOUNTING_EDIT); 
        if ($request->getMethod() !== Request::METHOD_POST) { 
            throw new \RuntimeException('POST required.'); 
        } 
        $row = null; 
        $acceptsJson = in_array('application/json', $request->getAcceptableContentTypes(), false); 
        try { 
            $this->init(); 
            $repo = $this->managerRegistry->getRepository(Document::class); 
            /** @var Document $row */ 
            $row = $repo->findOneBy(['id' => $id]); 
            if (!is_object($row)) { 
                throw new \RuntimeException('Ungültige ID oder Zugriff verweigert.'); 
            } 
            if ($row->getStatus() != Document::STATUS_ENTWURF) { 
                throw new \RuntimeException(sprintf('Dokument %s ist nicht im Entwurfsstatus',$row->getNumber())); 
            } 
 
            $this->doFaktura($row); 
            $repo->flush($row); 
            if (!$acceptsJson) 
            { 
                $this->addFlash('success','Dokument wurde fakturiert.'); 
            } 
            else 
            { 
                $docUrl = $this->generateUrl('acc-billing-pdf',['id'=>$row->getId(), UrlGeneratorInterface::ABSOLUTE_URL]); 
                return new JsonResponse([ 
                    'success'=>true, 
                    'message'=>'Dokument fakturiert', 
                    'downloadUrl'=>$docUrl]); 
            } 
 
        } catch (\Throwable $ex) { 
            if (!$acceptsJson) { 
                $this->addFlash('warning', $ex->getMessage()); 
            } else { 
                return new JsonResponse(['success' => false, 'message' => $ex->getMessage()]); 
            } 
        } 
        if (!$acceptsJson) { 
            return $this->redirectToRoute('acc-billing', []); 
        } 
    } 
 
    protected static function sumUpCustomerCalcCategories(Job $j) 
    { 
        $sum = [ JobCalcItem::Extra => 0, JobCalcItem::Wartezeit => 0, JobCalcItem::Pauschale => [], JobCalcItem::KM => 0 ]; 
        /** @var JobCalcItem $item **/ 
        foreach ($j->getCustomerCalculationItems() as $item) 
        { 
            if (in_array($item->category,[JobCalcItem::Extra, JobCalcItem::Wartezeit])) 
            { 
                $sum[$item->category] += $item->totalNet; 
            } 
            else 
                if ($item->category == JobCalcItem::KM) 
                { 
                    $sum[$item->category] += $item->quantity; 
                } 
                else 
                { 
                    $sum[JobCalcItem::Pauschale] []= $item->shortCode; 
                } 
        } 
        $sum[JobCalcItem::Pauschale] = implode(',',$sum[JobCalcItem::Pauschale]); 
        return $sum; 
    } 
 
    protected function xlsLine(ExcelExportHelper $xh, Job $job, JobCalcItem $item = null) 
    { 
        if ($item === null) // first line - Job 
        { 
            $org = ""; 
            $coce = $job->getCostCenter(); 
            $match = ["3R","3G","3D"]; 
            foreach ($match as $m) 
            { 
                if (strpos($coce,$m)===0) 
                { 
                    $org = $m; 
                    $coce = \Safe\substr($coce,strlen($m)); 
                } 
            } 
 
            $details = self::sumUpCustomerCalcCategories($job); 
            $xh->addRow( 
                [ 
                    "Bestellung" => $job->getKnownOrder()->getOrderId(), 
                    "Zeit" => $job->getOrderTime()->format('d.m.Y')." ".$job->getOrderTime()->format('H:i'), 
                    "Org" => $org, 
                    "Kostenstelle" => $coce, 
                    "Besteller" => $job->getKnownOrder()->getOrdererName(), 
                    "Kommentar" => $job->getInfo(), 
                    'KM' => [$details[JobCalcItem::KM] , ExcelExportHelper::TYPE_NUMERIC], 
                    // Kunden haben i.d.R. keine Pauschalen :: 'P'  => [$details[JobCalcItem::Pauschale], ExcelExportHelper::TYPE_STRING], 
                    'WZ(€ netto)' => [round($details[JobCalcItem::Wartezeit],2) , ExcelExportHelper::XTYPE_CURRENCY], 
                    'EX(€ netto)' => [round($details[JobCalcItem::Extra],2) , ExcelExportHelper::XTYPE_CURRENCY], 
                    /////// 
                    'Posten' => '', 
                    'Menge' => '', 
                    'Einzelpreis'=>'', 
                    'MwSt.'=>'', 
                    'Summe'=>'', 
                ], 
                self::$bold 
            ); 
            return; 
        } 
        $xh->addRow([ 
            "Menge" =>    $item->quantity, 
            "Einzelpreis" => [round($item->amount,2),ExcelExportHelper::XTYPE_CURRENCY], 
            "MwSt."   =>  sprintf('%d %%',$item->vat*100), 
            "Summe" =>  [round($item->totalNet,2),ExcelExportHelper::XTYPE_CURRENCY], 
            "Posten"  => $item->name 
        ]); 
    } 
 
    public function xlsAction(Request $request, string $tempDir, $id) 
    { 
        $this->init(); 
        $repo = $this->managerRegistry->getRepository(Document::class); 
        /** @var Document $row */ 
        $row = $repo->findOneBy(['id' =>$id]); 
        if (!is_object($row)) 
        { 
            $this->addFlash('warning','Ungültige ID oder Zugriff verweigert.'); 
            return $this->redirectToRoute('acc-billing-edit',['id'=>$id]); 
        } 
        if ($row->getType()!==Document::TYPE_RECHNUNG) 
        { 
            $this->addFlash('warning','XLS nur für Rechnungen verfügbar.'); 
            return $this->redirectToRoute('acc-billing-edit',['id'=>$id]); 
        } 
        if ($row->getBilling() === null) 
        { 
            $this->addFlash('warning','Es sind keine Fahrtabrechnungsdaten verknüpft !'); 
            return $this->redirectToRoute('acc-billing-edit',['id'=>$id]); 
        } 
 
        $xh = new ExcelExportHelper(); 
        ExcelExportHelper::$xlsHeadStyle['borders'] = array('bottom' => ['style' => PHPExcel_Style_Border::BORDER_THIN]); 
        $sheetTitle = sprintf("%s zu %s %s im Leistungsmonat %02d/%4d\n\n", 
            "Beiblatt", 
            Document::$typeMap[$row->getType()], 
            $row->getNumber(), 
            $row->getAccountingMonth(),$row->getAccountingYear()); 
        $xh->setSingleSheetMode('Beiblatt Rechnung '.$row->getNumber()); 
        $xh->addIndexedRow($sheetTitle,self::$bold); 
        $xh->addIndexedRow(''); 
        $xh->addIndexedRow(''); 
 
        ////////////////////////////////////////////////////// 
        foreach ($row->getBilling()->getJobList() as $j) { 
            if ($j->isIgnoreForCustomer()) { 
                continue; 
            } 
            $items = $j->getCalcItemsFor($row->getBilling()->getType()); 
            $this->xlsLine($xh, $j); 
            foreach ($items as $it) { 
                $this->xlsLine($xh, $j, $it); 
            } 
        } 
        ////////////////////////////////////////////////////////////////// 
        $tempFn = $tempDir . uniqid("export",true) . ".xlsx"; 
        $outFn = $row->getNumber()."_".date('Y-m-d_H-i-s').'.' .$xh->getExtension(); 
        $xh->saveTo($tempFn); 
        $resp = new BinaryFileResponse( $tempFn); 
        $resp->setContentDisposition( 
            ResponseHeaderBag::DISPOSITION_ATTACHMENT, 
            $outFn 
        ); 
        return $resp; 
    } 
 
    public function newAction(Request $request, $type) 
    { 
        $this->init(); 
        $this->ensureUserHasRole(Role::ACCOUNTING_EDIT); 
        $em = $this->managerRegistry->getManager(); 
        $dor = $em->getRepository(Document::class); 
 
        // default doc 
        $D = new Document(); 
        $D->setAccountingMonth(date('m')); 
        $D->setAccountingYear(date('Y')); 
        $D->setType(  $type ); 
        $D->setTotalNet(0); 
        $D->setBilling(null); 
        $D->setDate( new \DateTime()); 
 
        $cloneFrom = $request->query->get('cloneFrom', null); 
        $clonedFrom = null; 
        // fetch if copy is wanted 
        if (!empty($cloneFrom)) 
        { 
            $clonedFrom = $dor->findOneBy(['id' =>$cloneFrom]); 
            if (!is_object($clonedFrom)) 
            { 
                $this->addFlash('warning','Ungültige ID oder Zugriff verweigert.'); 
                return $this->redirectToRoute('acc-billing-edit',['id'=>$id]); 
            } 
            $D = clone $clonedFrom; 
        } 
        // set user 
        $D->setTreatedBy($this->getCurrentUser()); 
 
        $form = $this->createForm(NewDocumentForm::class,$D,[ 
            NewDocumentForm::OPT_BDS => $this->hasUserRole(Role::BEREITSCHAFTSDISPO) 
        ]); 
        $form->handleRequest($request); 
        if ($form->isSubmitted()) 
        { 
            if ((($D->getMember()!==null) xor ($D->getCustomer()!==null))!==true) 
            { 
                $this->addFlash('warning','Bitte entweder einen Kunden oder ein Mitglied auswählen'); 
                $form->get('customer')->addError(new FormError('Auswahl nötig')); 
                $form->get('member')->addError(new FormError('Auswahl nötig')); 
            } 
 
            if ($form->isValid()) 
            { 
                $D->setNumber( $dor->getNewDocumentNumber($D->getType(), $D->getDate())); 
                $address=''; 
                if ($D->getMember()!==null) 
                    $address = $D->getMember()->getAddress(); 
                elseif ($D->getCustomer()!==null) 
                    $address = $D->getCustomer()->getInvoiceAddress(); 
 
                $D->setReceiverAddress($address); 
                if ($D->getType()===Document::TYPE_RECHNUNG) 
                { 
                    $ht = $D->getCustomer()!==null ? trim($D->getCustomer()->getPdfDefaultHeader()):''; 
                    if (empty($ht)) $ht = "Sehr geehrte Damen und Herren,\ndie in Ihrem Auftrag durchgeführten Leistungen berechnen wir wie folgt:"; 
                    $D->setHeaderText($ht); 
                    $ft = $D->getCustomer()!==null ? trim($D->getCustomer()->getPdfDefaultFooter()):''; 
                    if (empty($ft)) $ft = 'Bitte überweisen Sie den Rechnungsbetrag ohne Abzug innerhalb von 14 Tagen. Geben Sie bei Zahlungen bitte Ihre Kundennummer und die Rechnungsnummer an.'; 
                    $D->setFooterText($ft); 
                } 
                else 
                    if ($D->getType()===Document::TYPE_GUTSCHRIFT) 
                    { 
                        $D->setFooterText(sprintf("Der Gutschriftsbetrag wird auf Ihr bekanntes Konto überwiesen: %s ",$D->getMember()->getAccountingBankAccount())); 
                    } 
 
                if ($form->get('standByDispo')->getViewData() && ($D->getCustomer()!==null)) 
                { 
                    $jdd = []; 
                    $rr = $this->managerRegistry->getRepository(StandByDispoEntry::class); 
                    /** @var StandByDispoEntry[] $data */ 
                    $data = $rr->findFor($D->getCustomer()->getId(),$D->getAccountingYear(),$D->getAccountingMonth()); 
                    foreach ($data as $d) 
                    { 
                        $jdd[ $d->getDate()->format('Y-m-d') ] = [ 
                            "time" => $d->getMatrix(), 
                            "comment" => $d->getCommentMatrix() 
                        ]; 
                    } 
                    $D->setStandByDispoData($jdd); 
                    $sum = array_sum( 
                        array_map(function($el) { 
                            return array_sum($el["time"]); 
                        },$jdd) 
                    ); 
                    $JC = new JobCalcItem(); 
                    $JC->shortCode = JobCalcItem::Extra; 
                    $JC->name = sprintf("Bereitschaftsdisposition"); 
                    $JC->category = JobCalcItem::Extra; 
                    $JC->quantity = 1; 
                    $JC->amount =  round($sum * 0.4666666666666667,2); 
                    $JC->totalNet = round($JC->quantity * $JC->amount,2); 
                    $JC->vat = JobCalcItem::defaultVat(); 
                    $D->setExtraCalculationItems([$JC]); 
                    $D->setTotalNet($JC->totalNet); 
                } 
 
                $em->persist($D); 
                $em->flush(); 
                return $this->redirectToRoute("acc-billing-edit",['id'=>$D->getId()]); 
            } 
            $this->addFlash('warning','Bitte überprüfen Sie Ihre Eingabe'); 
        } 
 
        return $this->render('@DiplixKMG/Accounting/Billing/new.html.twig',[ 
            'form'=>$form->createView(), 
            'type'=>$type, 
            'typeMap'=>Document::$typeMap, 
            "clonedFrom" => $clonedFrom 
        ]); 
    } 
 
}