<?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.5006666,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
]);
}
}