feat: import work hours from SaaS and exchange signed time sheet
This commit is contained in:
1 parent
7cf39850e6
commit
1a7c272414
73 files changed
+10488
-72
No files matched your search
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin\WorkMonth;
|
||||
|
||||
use App\Repository\TeamerWorkMonthRepository;
|
||||
use App\Service\Common\WorkMonthFilterHandler;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* Every imported work month with the state of its Arbeitszeitnachweis.
|
||||
*
|
||||
* Deliberately not the existing document list: UploadRepository::getUploadListQuery() inner
|
||||
* joins a disposition, so a document hanging off a month can never appear there, and it is
|
||||
* the month rather than the file that carries the status anyway.
|
||||
*/
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TeamerWorkMonthRepository $workMonthRepository,
|
||||
private readonly WorkMonthFilterHandler $filterHandler,
|
||||
private readonly PaginatorInterface $paginator,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/admin/work-month', name: 'app_admin_work_month_index')]
|
||||
#[IsGranted('ROLE_TEAM_ADMIN')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$filterDto = $this->filterHandler->getFilterSettings();
|
||||
|
||||
$pagination = $this->paginator->paginate(
|
||||
$this->workMonthRepository->getListQuery($filterDto),
|
||||
$request->query->getInt('page', 1),
|
||||
50,
|
||||
);
|
||||
|
||||
return $this->render('admin/work_month/index.html.twig', [
|
||||
'pagination' => $pagination,
|
||||
'filterDto' => $filterDto,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin\WorkMonth;
|
||||
|
||||
use App\Entity\TeamerWorkMonth;
|
||||
use App\Service\Pdf\WorkMonthRenderer;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* The same sheet the teamer gets, for the office.
|
||||
*
|
||||
* This is what a month's hours look like in full: there is no separate day-by-day screen,
|
||||
* because it would only restate the document that actually gets signed.
|
||||
*/
|
||||
class PdfController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WorkMonthRenderer $renderer,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/admin/work-month/pdf/{uuid}', name: 'app_admin_work_month_pdf')]
|
||||
#[IsGranted('ROLE_TEAM_ADMIN')]
|
||||
public function index(TeamerWorkMonth $workMonth): Response
|
||||
{
|
||||
$this->logger->info('Download work month pdf', [
|
||||
'work_month' => $workMonth->getUuid(),
|
||||
'teamer' => $workMonth->getTeamer()?->getFullName(),
|
||||
]);
|
||||
|
||||
$response = new Response($this->renderer->render($workMonth));
|
||||
$response->headers->set('Content-Type', 'application/pdf');
|
||||
$response->headers->set('Content-Disposition', $response->headers->makeDisposition(
|
||||
ResponseHeaderBag::DISPOSITION_INLINE,
|
||||
$this->renderer->filename($workMonth),
|
||||
));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin\WorkMonth;
|
||||
|
||||
use App\Config\EmailTextKey;
|
||||
use App\Controller\Traits\ReturnUrlTrait;
|
||||
use App\Email\Mailer;
|
||||
use App\Entity\TeamerWorkMonth;
|
||||
use App\Entity\Upload;
|
||||
use App\Entity\User;
|
||||
use App\Enum\WorkMonthStatus;
|
||||
use App\Form\WorkMonthReviewType;
|
||||
use App\Htmx\HxRedirectResponse;
|
||||
use App\Model\WorkMonthReviewDto;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* The office's verdict on a signed Arbeitszeitnachweis.
|
||||
*
|
||||
* A rejection does not undo the upload: the file stays on the month and the teamer uploads a
|
||||
* new one over it, which puts the month back into "eingereicht".
|
||||
*/
|
||||
class ReviewController extends AbstractController
|
||||
{
|
||||
use ReturnUrlTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly Mailer $mailer,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/admin/work-month/review/{uuid}', name: 'app_admin_work_month_review')]
|
||||
#[IsGranted('ROLE_TEAM_ADMIN')]
|
||||
public function index(TeamerWorkMonth $workMonth, Request $request): Response
|
||||
{
|
||||
if (WorkMonthStatus::SUBMITTED !== $workMonth->getStatus()) {
|
||||
throw $this->createNotFoundException('This work month is not waiting for a review');
|
||||
}
|
||||
|
||||
$returnUrl = $this->getReturnUrl($request, 'app_admin_work_month_index');
|
||||
$formData = new WorkMonthReviewDto($workMonth);
|
||||
$form = $this->createForm(WorkMonthReviewType::class, $formData);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
$status = $formData->getStatus();
|
||||
|
||||
$workMonth
|
||||
->setStatus($status)
|
||||
->setReviewedAt(new \DateTimeImmutable())
|
||||
->setReviewedBy($user->getInitials())
|
||||
->setReviewComment($formData->getComment())
|
||||
;
|
||||
|
||||
// Keep the file's own status in step, so the existing upload badge and the
|
||||
// UploadVoter's CHECK rule stay meaningful for it.
|
||||
$workMonth->getCurrentDocument()?->setStatus(
|
||||
WorkMonthStatus::APPROVED === $status ? Upload::STATUS_CHECKED : Upload::STATUS_REJECTED
|
||||
);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
if (WorkMonthStatus::REJECTED === $status) {
|
||||
$this->notifyRejection($workMonth, (string) $formData->getComment());
|
||||
}
|
||||
|
||||
$this->addFlash(
|
||||
'success',
|
||||
WorkMonthStatus::APPROVED === $status
|
||||
? 'Der Arbeitszeitnachweis wurde bestätigt'
|
||||
: 'Der Arbeitszeitnachweis wurde abgelehnt'
|
||||
);
|
||||
|
||||
$this->logger->info('Review work month', [
|
||||
'work_month' => $workMonth->getUuid(),
|
||||
'teamer' => $workMonth->getTeamer()?->getFullName(),
|
||||
'period' => sprintf('%04d-%02d', $workMonth->getYear(), $workMonth->getMonth()),
|
||||
'status' => $status->value,
|
||||
'by' => $user->getInitials(),
|
||||
]);
|
||||
|
||||
return new HxRedirectResponse($returnUrl);
|
||||
}
|
||||
|
||||
return $this->render('admin/work_month/modal_review.html.twig', [
|
||||
'workMonth' => $workMonth,
|
||||
'form' => $form->createView(),
|
||||
'returnUrl' => $returnUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
private function notifyRejection(TeamerWorkMonth $workMonth, string $comment): void
|
||||
{
|
||||
$email = $workMonth->getTeamer()?->getCommunication()?->getEmail();
|
||||
|
||||
if (null === $email || '' === trim($email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->mailer->createAndSendText(EmailTextKey::WORK_MONTH_REJECTED, [
|
||||
'workMonth' => $workMonth->getPeriodLabel(),
|
||||
'comment' => $comment,
|
||||
], [
|
||||
'to' => $email,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin\WorkMonth;
|
||||
|
||||
use App\Controller\Traits\ReturnUrlTrait;
|
||||
use App\Entity\TeamerWorkMonth;
|
||||
use App\Enum\WorkHoursSyncOutcome;
|
||||
use App\Service\Saas\WorkHoursSynchronizer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* Fetching one month again by hand, for a correction the schedule has already passed by.
|
||||
*
|
||||
* A month the teamer has signed is frozen, so re-fetching it only records that the SaaS now
|
||||
* disagrees. Releasing it is a separate, deliberate step: it throws away the signature and
|
||||
* sends the teamer back to the start.
|
||||
*/
|
||||
class SyncController extends AbstractController
|
||||
{
|
||||
use ReturnUrlTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly WorkHoursSynchronizer $synchronizer,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/admin/work-month/sync/{uuid}', name: 'app_admin_work_month_sync', methods: ['POST'])]
|
||||
#[IsGranted('ROLE_TEAM_ADMIN')]
|
||||
public function sync(TeamerWorkMonth $workMonth, Request $request): Response
|
||||
{
|
||||
if (null === $teamer = $workMonth->getTeamer()) {
|
||||
throw $this->createNotFoundException('No teamer on this work month');
|
||||
}
|
||||
|
||||
$result = $this->synchronizer->syncTeamer($teamer, $workMonth->getYear(), $workMonth->getMonth());
|
||||
|
||||
match ($result->getOutcome()) {
|
||||
WorkHoursSyncOutcome::IMPORTED => $this->addFlash('success', 'Die Arbeitszeiten wurden aktualisiert'),
|
||||
WorkHoursSyncOutcome::LOCKED => $this->addFlash('success', $result->getMessage() ?? 'Der Monat ist bereits eingereicht'),
|
||||
WorkHoursSyncOutcome::SKIPPED => $this->addFlash('error', $result->getMessage() ?? 'Der Abruf war nicht möglich'),
|
||||
WorkHoursSyncOutcome::FAILED => $this->addFlash('error', 'Der Abruf ist fehlgeschlagen: '.$result->getMessage()),
|
||||
};
|
||||
|
||||
return $this->redirect($this->getReturnUrl($request, 'app_admin_work_month_index'));
|
||||
}
|
||||
|
||||
#[Route('/admin/work-month/unlock/{uuid}', name: 'app_admin_work_month_unlock', methods: ['POST'])]
|
||||
#[IsGranted('ROLE_TEAM_ADMIN')]
|
||||
public function unlock(TeamerWorkMonth $workMonth, Request $request): Response
|
||||
{
|
||||
$this->synchronizer->unlock($workMonth);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Der Monat wurde wieder geöffnet und muss neu unterschrieben werden');
|
||||
|
||||
$this->logger->info('Unlock work month', [
|
||||
'work_month' => $workMonth->getUuid(),
|
||||
'teamer' => $workMonth->getTeamer()?->getFullName(),
|
||||
'period' => sprintf('%04d-%02d', $workMonth->getYear(), $workMonth->getMonth()),
|
||||
]);
|
||||
|
||||
return $this->redirect($this->getReturnUrl($request, 'app_admin_work_month_index'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Common;
|
||||
|
||||
use App\Controller\Traits\ReturnUrlTrait;
|
||||
use App\Form\WorkMonthFilterType;
|
||||
use App\Htmx\HxRedirectResponse;
|
||||
use App\Service\Common\WorkMonthFilterHandler;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class WorkMonthFilterController extends AbstractController
|
||||
{
|
||||
use ReturnUrlTrait;
|
||||
|
||||
public function __construct(private readonly WorkMonthFilterHandler $filterHandler)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route('/common/work-month/filter', name: 'app_common_work_month_filter')]
|
||||
#[IsGranted('ROLE_TEAM_ADMIN')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$formData = $this->filterHandler->getFilterSettings();
|
||||
$form = $this->createForm(WorkMonthFilterType::class, $formData);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->filterHandler->handleRequest($form);
|
||||
$returnUrl = $this->getReturnUrl($request, 'app_admin_work_month_index');
|
||||
|
||||
return new HxRedirectResponse($returnUrl);
|
||||
}
|
||||
|
||||
return $this->render('common/modal_work_month_filter.html.twig', [
|
||||
'filterForm' => $form->createView(),
|
||||
'filterDto' => $form->getData(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/common/work-month/filter/reset', name: 'app_common_work_month_filter_reset')]
|
||||
#[IsGranted('ROLE_TEAM_ADMIN')]
|
||||
public function reset(Request $request): Response
|
||||
{
|
||||
$this->filterHandler->resetFilterSettings();
|
||||
$returnUrl = $this->getReturnUrl($request, 'app_admin_work_month_index');
|
||||
|
||||
return $this->redirect($returnUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Teamer\WorkMonth;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\TeamerWorkMonthRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* The months a teamer has to sign off, newest first.
|
||||
*
|
||||
* Only months the import has actually produced appear here; the current month is not among
|
||||
* them until the 10th of the month after it.
|
||||
*/
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TeamerWorkMonthRepository $workMonthRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/teamer/arbeitszeiten', name: 'app_teamer_work_month_index')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
if (null === $teamer = $user->getTeamer()) {
|
||||
throw $this->createNotFoundException('No teamer for this user');
|
||||
}
|
||||
|
||||
return $this->render('teamer/work_month/index.html.twig', [
|
||||
'workMonths' => $this->workMonthRepository->findForTeamer($teamer),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Teamer\WorkMonth;
|
||||
|
||||
use App\Entity\TeamerWorkMonth;
|
||||
use App\Entity\User;
|
||||
use App\Service\Pdf\WorkMonthRenderer;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* The Arbeitszeitnachweis to print and sign.
|
||||
*
|
||||
* Built on demand and never stored, exactly as the contract and the invoice are. What keeps
|
||||
* a regenerated sheet identical to the signed one is the lock in WorkHoursSynchronizer: once
|
||||
* a month is submitted, its hours stop moving.
|
||||
*/
|
||||
class PdfController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WorkMonthRenderer $renderer,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/teamer/arbeitszeiten/pdf/{uuid}', name: 'app_teamer_work_month_pdf')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(TeamerWorkMonth $workMonth): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
// the month is addressed by uuid, so this is what keeps one teamer out of another's
|
||||
if ($user->getTeamer() !== $workMonth->getTeamer()) {
|
||||
throw $this->createNotFoundException('Not this teamer\'s work month');
|
||||
}
|
||||
|
||||
$this->logger->info('Download work month pdf', [
|
||||
'user' => $user->getUserIdentifier(),
|
||||
'work_month' => $workMonth->getUuid(),
|
||||
]);
|
||||
|
||||
$response = new Response($this->renderer->render($workMonth));
|
||||
$response->headers->set('Content-Type', 'application/pdf');
|
||||
$response->headers->set('Content-Disposition', $response->headers->makeDisposition(
|
||||
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
|
||||
$this->renderer->filename($workMonth),
|
||||
));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Teamer\WorkMonth;
|
||||
|
||||
use App\Entity\TeamerWorkMonth;
|
||||
use App\Entity\Upload;
|
||||
use App\Entity\User;
|
||||
use App\Enum\WorkMonthStatus;
|
||||
use App\Htmx\HxRedirectResponse;
|
||||
use App\Model\UploadDto;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Flagception\Manager\FeatureManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* The signed Arbeitszeitnachweis coming back for one month.
|
||||
*
|
||||
* Uploads accumulate rather than replace - Upload is not soft-deletable, and a rejection and
|
||||
* the re-upload answering it both belong on the record. TeamerWorkMonth::getCurrentDocument()
|
||||
* is what decides which one counts.
|
||||
*/
|
||||
class UploadController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UploadHandler $uploadHandler,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly FeatureManagerInterface $featureManager,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/teamer/arbeitszeiten/upload/{uuid}', name: 'app_teamer_work_month_upload')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(TeamerWorkMonth $workMonth, Request $request): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
// the month is addressed by uuid, so this is what keeps one teamer out of another's
|
||||
if ($user->getTeamer() !== $workMonth->getTeamer()) {
|
||||
throw $this->createNotFoundException('Not this teamer\'s work month');
|
||||
}
|
||||
|
||||
if (WorkMonthStatus::APPROVED === $workMonth->getStatus()) {
|
||||
throw $this->createNotFoundException('This work month is already approved');
|
||||
}
|
||||
|
||||
// Dummy form without fields, as elsewhere: the files travel in the upload session.
|
||||
$form = $this->createFormBuilder()->getForm();
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$count = $this->processUpload($workMonth, $user);
|
||||
|
||||
if (0 === $count) {
|
||||
$this->addFlash('error', 'Es wurde keine Datei hochgeladen');
|
||||
} else {
|
||||
$this->addFlash('success', 'Der Arbeitszeitnachweis wurde hochgeladen');
|
||||
}
|
||||
|
||||
return new HxRedirectResponse($this->generateUrl('app_teamer_work_month_index'));
|
||||
}
|
||||
|
||||
return $this->render('teamer/work_month/modal_upload.html.twig', [
|
||||
'workMonth' => $workMonth,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function processUpload(TeamerWorkMonth $workMonth, User $user): int
|
||||
{
|
||||
$uploadSession = $this->uploadHandler->getUploadSession();
|
||||
|
||||
// Only this dropzone's files: the session is shared, so it must not be destroyed whole.
|
||||
$uploadDtos = $uploadSession->getUploadsByType(Upload::TYPE_WORK_MONTH);
|
||||
|
||||
$uploads = [];
|
||||
|
||||
/** @var UploadDto $uploadDto */
|
||||
foreach ($uploadDtos as $uploadDto) {
|
||||
$upload = Upload::fromUploadDto($uploadDto, $user, Upload::TYPE_WORK_MONTH);
|
||||
|
||||
// Unlike a season document this one is reviewed, so it enters the checking states
|
||||
// the UploadVoter lets an administrative user act on.
|
||||
$upload->setStatus(Upload::STATUS_NEW);
|
||||
|
||||
$workMonth->addDocument($upload);
|
||||
$this->entityManager->persist($upload);
|
||||
|
||||
$uploads[] = $upload;
|
||||
}
|
||||
|
||||
$this->uploadHandler->moveUploadSessionFilesFromOrphanage(Upload::TYPE_WORK_MONTH, $uploadSession);
|
||||
$this->uploadHandler->removeUploadSessionUploadsByType(Upload::TYPE_WORK_MONTH);
|
||||
|
||||
if ([] === $uploads) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (true === $this->featureManager->isActive('sanitize_uploads')) {
|
||||
// Teamers photograph signed sheets, so what arrives is not necessarily a PDF.
|
||||
foreach ($uploads as $upload) {
|
||||
$this->uploadHandler->ensurePdf($upload);
|
||||
}
|
||||
}
|
||||
|
||||
$workMonth
|
||||
->setStatus(WorkMonthStatus::SUBMITTED)
|
||||
->setSubmittedAt(new \DateTimeImmutable())
|
||||
// A new submission answers an earlier rejection; the old verdict must not stand.
|
||||
->setReviewedAt(null)
|
||||
->setReviewedBy(null)
|
||||
->setReviewComment(null)
|
||||
;
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->logger->info('Upload work month document', [
|
||||
'user' => $user->getUserIdentifier(),
|
||||
'work_month' => $workMonth->getUuid(),
|
||||
'period' => sprintf('%04d-%02d', $workMonth->getYear(), $workMonth->getMonth()),
|
||||
'count' => count($uploads),
|
||||
]);
|
||||
|
||||
return count($uploads);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user