feat: call off single disposition as admin with optional notification
This commit is contained in:
@@ -29,3 +29,8 @@ yarn-error.log
|
|||||||
###> liip/imagine-bundle ###
|
###> liip/imagine-bundle ###
|
||||||
/public/media/cache/
|
/public/media/cache/
|
||||||
###< liip/imagine-bundle ###
|
###< liip/imagine-bundle ###
|
||||||
|
|
||||||
|
###> friendsofphp/php-cs-fixer ###
|
||||||
|
/.php-cs-fixer.php
|
||||||
|
/.php-cs-fixer.cache
|
||||||
|
###< friendsofphp/php-cs-fixer ###
|
||||||
|
|||||||
+3
-1
@@ -113,7 +113,8 @@
|
|||||||
],
|
],
|
||||||
"post-update-cmd": [
|
"post-update-cmd": [
|
||||||
"@auto-scripts"
|
"@auto-scripts"
|
||||||
]
|
],
|
||||||
|
"php-cs-fixer": "php-cs-fixer --rules=@Symfony"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/symfony": "*"
|
"symfony/symfony": "*"
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"deployer/deployer": "^7.3",
|
"deployer/deployer": "^7.3",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.84",
|
||||||
"marcocesarato/php-conventional-changelog": "^1.17",
|
"marcocesarato/php-conventional-changelog": "^1.17",
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^9.5",
|
||||||
"symfony/browser-kit": "6.4.*",
|
"symfony/browser-kit": "6.4.*",
|
||||||
|
|||||||
Generated
+1030
-1
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ framework:
|
|||||||
- ended
|
- ended
|
||||||
- checking_invoice
|
- checking_invoice
|
||||||
- completed
|
- completed
|
||||||
|
- called_off
|
||||||
transitions:
|
transitions:
|
||||||
upload_contract:
|
upload_contract:
|
||||||
from: new
|
from: new
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250722125647 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE disposition ADD called_off_by VARCHAR(64) DEFAULT NULL, ADD called_off_reason LONGTEXT DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE disposition DROP called_off_by, DROP called_off_reason');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Administrative\Disposition;
|
||||||
|
|
||||||
|
use App\Entity\Assignment;
|
||||||
|
use App\Entity\Disposition;
|
||||||
|
use App\Event\DispositionCalledOffEvent;
|
||||||
|
use App\Form\DispositionCallOffType;
|
||||||
|
use App\Htmx\HxRedirectResponse;
|
||||||
|
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;
|
||||||
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
class CallOffController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly EventDispatcherInterface $eventDispatcher,
|
||||||
|
private readonly LoggerInterface $logger,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/administrative/disposition/call-off/{uuid}', name: 'app_administrative_disposition_call_off')]
|
||||||
|
#[IsGranted('CALL_OFF', subject: 'disposition')]
|
||||||
|
public function index(Disposition $disposition, Request $request): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(DispositionCallOffType::class, $disposition, ['hx_post' => $request->getUri()]);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$disposition->setStatus(Assignment::STATUS_CALLED_OFF);
|
||||||
|
$sendNotification = $form->get('sendNotification')->getData();
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this
|
||||||
|
->eventDispatcher
|
||||||
|
->dispatch(new DispositionCalledOffEvent($disposition, $sendNotification), DispositionCalledOffEvent::NAME)
|
||||||
|
;
|
||||||
|
|
||||||
|
$assignment = $disposition->getAssignment();
|
||||||
|
|
||||||
|
$this->logger->info('Call off disposition', [
|
||||||
|
'assignment_id' => $disposition->getId(),
|
||||||
|
'destination' => (string) $assignment->getDestination(),
|
||||||
|
'job_profile' => $assignment->getJobProfile()->getName(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Der Einsatz wurde abgesagt');
|
||||||
|
|
||||||
|
return new HxRedirectResponse($this->generateUrl('app_administrative_assignment_detail', [
|
||||||
|
'uuid' => $assignment->getUuid(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('administrative/disposition/modal_call_off.html.twig', [
|
||||||
|
'disposition' => $disposition,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -487,6 +487,13 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getActiveDispositions(): Collection
|
||||||
|
{
|
||||||
|
return $this->dispositions->filter(function (Disposition $disposition) {
|
||||||
|
return Disposition::STATUS_CALLED_OFF !== $disposition->getStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function getConfirmedDispositions(): Collection
|
public function getConfirmedDispositions(): Collection
|
||||||
{
|
{
|
||||||
return $this->dispositions->filter(function (Disposition $disposition) {
|
return $this->dispositions->filter(function (Disposition $disposition) {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
|||||||
public const STATUS_CHECKING_INVOICE = 'checking_invoice';
|
public const STATUS_CHECKING_INVOICE = 'checking_invoice';
|
||||||
public const STATUS_PAID = 'paid';
|
public const STATUS_PAID = 'paid';
|
||||||
public const STATUS_COMPLETED = 'completed';
|
public const STATUS_COMPLETED = 'completed';
|
||||||
|
public const STATUS_CALLED_OFF = 'called_off';
|
||||||
|
|
||||||
|
public const CALLED_OFF_BY_TEAMER = 'teamer';
|
||||||
|
public const CALLED_OFF_BY_OFFICE = 'office';
|
||||||
|
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
@@ -59,6 +63,12 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
|||||||
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
||||||
private ?Feedback $feedback = null;
|
private ?Feedback $feedback = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 64, nullable: true)]
|
||||||
|
private ?string $calledOffBy = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $calledOffReason = null;
|
||||||
|
|
||||||
public function __construct(Application $application)
|
public function __construct(Application $application)
|
||||||
{
|
{
|
||||||
$this->uuid = Uuid::v4();
|
$this->uuid = Uuid::v4();
|
||||||
@@ -243,4 +253,28 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
|||||||
|
|
||||||
return $period->isStarted();
|
return $period->isStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCalledOffBy(): ?string
|
||||||
|
{
|
||||||
|
return $this->calledOffBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCalledOffBy(?string $calledOffBy): static
|
||||||
|
{
|
||||||
|
$this->calledOffBy = $calledOffBy;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCalledOffReason(): ?string
|
||||||
|
{
|
||||||
|
return $this->calledOffReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCalledOffReason(?string $calledOffReason): static
|
||||||
|
{
|
||||||
|
$this->calledOffReason = $calledOffReason;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Event;
|
||||||
|
|
||||||
|
use App\Entity\Disposition;
|
||||||
|
use Symfony\Contracts\EventDispatcher\Event;
|
||||||
|
|
||||||
|
class DispositionCalledOffEvent extends Event
|
||||||
|
{
|
||||||
|
public const NAME = 'disposition.called_off';
|
||||||
|
|
||||||
|
public function __construct(private readonly Disposition $disposition, private readonly bool $sendNotification = false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDisposition(): Disposition
|
||||||
|
{
|
||||||
|
return $this->disposition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSendNotification(): bool
|
||||||
|
{
|
||||||
|
return $this->sendNotification;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use App\Entity\Application;
|
|||||||
use App\Entity\Upload;
|
use App\Entity\Upload;
|
||||||
use App\Event\ApplicationStatusEvent;
|
use App\Event\ApplicationStatusEvent;
|
||||||
use App\Event\AssignmentCalledOffEvent;
|
use App\Event\AssignmentCalledOffEvent;
|
||||||
|
use App\Event\DispositionCalledOffEvent;
|
||||||
use App\Event\DispositionCreatedEvent;
|
use App\Event\DispositionCreatedEvent;
|
||||||
use App\Event\DocumentConfirmedEvent;
|
use App\Event\DocumentConfirmedEvent;
|
||||||
use App\Event\DocumentRejectedEvent;
|
use App\Event\DocumentRejectedEvent;
|
||||||
@@ -21,7 +22,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Mailer $mailer,
|
private readonly Mailer $mailer,
|
||||||
private readonly UserRepository $userRepository,
|
private readonly UserRepository $userRepository,
|
||||||
private readonly ContractRenderer $contractRenderer
|
private readonly ContractRenderer $contractRenderer,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
DispositionCreatedEvent::NAME => 'onDispositionCreated',
|
DispositionCreatedEvent::NAME => 'onDispositionCreated',
|
||||||
|
DispositionCalledOffEvent::NAME => 'onDispositionCalledOff',
|
||||||
DocumentUploadedEvent::NAME => 'onDocumentUploaded',
|
DocumentUploadedEvent::NAME => 'onDocumentUploaded',
|
||||||
DocumentRejectedEvent::NAME => 'onDocumentRejected',
|
DocumentRejectedEvent::NAME => 'onDocumentRejected',
|
||||||
ApplicationStatusEvent::NAME => 'onApplicationStatus',
|
ApplicationStatusEvent::NAME => 'onApplicationStatus',
|
||||||
@@ -38,7 +40,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify teamer about accepted application
|
* Notify teamer about accepted application.
|
||||||
*/
|
*/
|
||||||
public function onDispositionCreated(DispositionCreatedEvent $event): void
|
public function onDispositionCreated(DispositionCreatedEvent $event): void
|
||||||
{
|
{
|
||||||
@@ -97,7 +99,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify managers about uploaded documents
|
* Notify managers about uploaded documents.
|
||||||
*/
|
*/
|
||||||
public function onDocumentUploaded(DocumentUploadedEvent $event): void
|
public function onDocumentUploaded(DocumentUploadedEvent $event): void
|
||||||
{
|
{
|
||||||
@@ -142,7 +144,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify teamer about rejected document
|
* Notify teamer about rejected document.
|
||||||
*/
|
*/
|
||||||
public function onDocumentRejected(DocumentRejectedEvent $event): void
|
public function onDocumentRejected(DocumentRejectedEvent $event): void
|
||||||
{
|
{
|
||||||
@@ -168,7 +170,7 @@ class EmailNotificationSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify teamer about rejected application
|
* Notify teamer about rejected application.
|
||||||
*/
|
*/
|
||||||
public function onApplicationStatus(ApplicationStatusEvent $event): void
|
public function onApplicationStatus(ApplicationStatusEvent $event): void
|
||||||
{
|
{
|
||||||
@@ -189,6 +191,31 @@ class EmailNotificationSubscriber implements EventSubscriberInterface
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify teamer about called-off disposition (if applicable).
|
||||||
|
*/
|
||||||
|
public function onDispositionCalledOff(DispositionCalledOffEvent $event): void
|
||||||
|
{
|
||||||
|
if (false === $event->isSendNotification()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$disposition = $event->getDisposition();
|
||||||
|
$teamer = $disposition->getTeamer();
|
||||||
|
|
||||||
|
$this->mailer->createAndSendEmail([
|
||||||
|
'assignment' => $disposition->getAssignment(),
|
||||||
|
'reason' => $disposition->getCalledOffReason(),
|
||||||
|
], [
|
||||||
|
'to' => $teamer->getCommunication()->getEmail(),
|
||||||
|
'subject' => 'Dein Einsatz wurde abgesagt',
|
||||||
|
'template' => 'email/disposition_called_off.html.twig',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify all assigned teamers about called-off assignment.
|
||||||
|
*/
|
||||||
public function onAssignmentCalledOff(AssignmentCalledOffEvent $event): void
|
public function onAssignmentCalledOff(AssignmentCalledOffEvent $event): void
|
||||||
{
|
{
|
||||||
$assignment = $event->getAssignment();
|
$assignment = $event->getAssignment();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Entity\Upload;
|
|||||||
use App\Event\ApplicationCreatedEvent;
|
use App\Event\ApplicationCreatedEvent;
|
||||||
use App\Event\ApplicationDeletedEvent;
|
use App\Event\ApplicationDeletedEvent;
|
||||||
use App\Event\ApplicationStatusEvent;
|
use App\Event\ApplicationStatusEvent;
|
||||||
|
use App\Event\DispositionCalledOffEvent;
|
||||||
use App\Event\DispositionCreatedEvent;
|
use App\Event\DispositionCreatedEvent;
|
||||||
use App\Event\DispositionDeletedEvent;
|
use App\Event\DispositionDeletedEvent;
|
||||||
use App\Event\DocumentConfirmedEvent;
|
use App\Event\DocumentConfirmedEvent;
|
||||||
@@ -27,6 +28,7 @@ class StaffingStatusSubscriber implements EventSubscriberInterface
|
|||||||
ApplicationStatusEvent::NAME => 'onApplicationStatusUpdated',
|
ApplicationStatusEvent::NAME => 'onApplicationStatusUpdated',
|
||||||
DispositionCreatedEvent::NAME => 'onDispositionCreated',
|
DispositionCreatedEvent::NAME => 'onDispositionCreated',
|
||||||
DispositionDeletedEvent::NAME => 'onDispositionDeleted',
|
DispositionDeletedEvent::NAME => 'onDispositionDeleted',
|
||||||
|
DispositionCalledOffEvent::NAME => 'onDispositionCalledOff',
|
||||||
DocumentConfirmedEvent::NAME => 'onDocumentConfirmed',
|
DocumentConfirmedEvent::NAME => 'onDocumentConfirmed',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -67,6 +69,13 @@ class StaffingStatusSubscriber implements EventSubscriberInterface
|
|||||||
$this->updateStaffingStatus($assignment);
|
$this->updateStaffingStatus($assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onDispositionCalledOff(DispositionCalledOffEvent $event): void
|
||||||
|
{
|
||||||
|
$assignment = $event->getDisposition()->getAssignment();
|
||||||
|
|
||||||
|
$this->updateStaffingStatus($assignment);
|
||||||
|
}
|
||||||
|
|
||||||
public function onDocumentConfirmed(DocumentConfirmedEvent $event): void
|
public function onDocumentConfirmed(DocumentConfirmedEvent $event): void
|
||||||
{
|
{
|
||||||
$document = $event->getDocument();
|
$document = $event->getDocument();
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Disposition;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
|
||||||
|
class DispositionCallOffType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('calledOffBy', ChoiceType::class, [
|
||||||
|
'label' => 'abgesagt durch',
|
||||||
|
'choices' => [
|
||||||
|
'Teamer:in' => Disposition::CALLED_OFF_BY_TEAMER,
|
||||||
|
'E&P' => Disposition::CALLED_OFF_BY_OFFICE,
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->add('calledOffReason', TextareaType::class, [
|
||||||
|
'label' => 'Begründung',
|
||||||
|
'attr' => [
|
||||||
|
'data-controller' => 'textarea-autosize',
|
||||||
|
'data-action' => 'textarea-autosize#resize',
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank([
|
||||||
|
'message' => 'Bitte gib die Begründung ein',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->add('sendNotification', CheckboxType::class, [
|
||||||
|
'label' => 'Benachrichtigung mit Begründung versenden',
|
||||||
|
'required' => false,
|
||||||
|
'mapped' => false,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Disposition::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ class DispositionVoter extends Voter
|
|||||||
public const CONTRACT_SUPPLEMENTARY = 'CONTRACT_SUPPLEMENTARY';
|
public const CONTRACT_SUPPLEMENTARY = 'CONTRACT_SUPPLEMENTARY';
|
||||||
public const INVOICE = 'INVOICE';
|
public const INVOICE = 'INVOICE';
|
||||||
public const FEEDBACK = 'FEEDBACK';
|
public const FEEDBACK = 'FEEDBACK';
|
||||||
|
public const CALL_OFF = 'CALL_OFF';
|
||||||
|
|
||||||
public function __construct(private readonly Security $security)
|
public function __construct(private readonly Security $security)
|
||||||
{
|
{
|
||||||
@@ -37,6 +38,7 @@ class DispositionVoter extends Voter
|
|||||||
static::CONTRACT_SUPPLEMENTARY,
|
static::CONTRACT_SUPPLEMENTARY,
|
||||||
static::INVOICE,
|
static::INVOICE,
|
||||||
static::FEEDBACK,
|
static::FEEDBACK,
|
||||||
|
static::CALL_OFF,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +55,8 @@ class DispositionVoter extends Voter
|
|||||||
static::FEEDBACK => $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
static::FEEDBACK => $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
||||||
|| $this->assertHouseManagerAccess($token, $disposition),
|
|| $this->assertHouseManagerAccess($token, $disposition),
|
||||||
static::CONTRACT_SUPPLEMENTARY => $this->assertContractUploadAllowed($disposition),
|
static::CONTRACT_SUPPLEMENTARY => $this->assertContractUploadAllowed($disposition),
|
||||||
|
static::CALL_OFF => $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
||||||
|
&& Disposition::STATUS_CALLED_OFF !== $disposition->getStatus(),
|
||||||
default => false,
|
default => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -63,6 +67,10 @@ class DispositionVoter extends Voter
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Disposition::STATUS_CALLED_OFF === $disposition->getStatus()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = $token->getUser();
|
$user = $token->getUser();
|
||||||
|
|
||||||
@@ -95,6 +103,10 @@ class DispositionVoter extends Voter
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Disposition::STATUS_CALLED_OFF === $disposition->getStatus()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return null === $disposition->getDocumentByType(Upload::TYPE_CONTRACT);
|
return null === $disposition->getDocumentByType(Upload::TYPE_CONTRACT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,18 @@
|
|||||||
"config/packages/flagception.yaml"
|
"config/packages/flagception.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"friendsofphp/php-cs-fixer": {
|
||||||
|
"version": "3.84",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.0",
|
||||||
|
"ref": "be2103eb4a20942e28a6dd87736669b757132435"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".php-cs-fixer.dist.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
"knplabs/knp-menu-bundle": {
|
"knplabs/knp-menu-bundle": {
|
||||||
"version": "v3.2.0"
|
"version": "v3.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -170,7 +170,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ disposition.remarks|default('-')|nl2br }}
|
{% if disposition.calledOffReason %}
|
||||||
|
{{ disposition.calledOffReason|default('-')|nl2br }}
|
||||||
|
{% else %}
|
||||||
|
{{ disposition.remarks|default('-')|nl2br }}
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
@@ -197,6 +201,16 @@
|
|||||||
{{ icon('upload') }}
|
{{ icon('upload') }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if is_granted('CALL_OFF', disposition) %}
|
||||||
|
<button type="button"
|
||||||
|
title="Absage"
|
||||||
|
class="text-red-500"
|
||||||
|
hx-get="{{ path('app_administrative_disposition_call_off', { 'uuid': disposition.uuid }) }}"
|
||||||
|
hx-target="body"
|
||||||
|
hx-swap="beforeend">
|
||||||
|
{{ icon('cancel') }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
{% if is_granted('DELETE', disposition) %}
|
{% if is_granted('DELETE', disposition) %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="text-red-500"
|
class="text-red-500"
|
||||||
|
|||||||
@@ -139,9 +139,9 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<span>{{ assignment.dispositions|length }}/{{ assignment.availableDispositions }}</span>
|
<span>{{ assignment.activeDispositions|length }}/{{ assignment.availableDispositions }}</span>
|
||||||
<ul>
|
<ul>
|
||||||
{% for disposition in assignment.dispositions %}
|
{% for disposition in assignment.activeDispositions %}
|
||||||
<li>
|
<li>
|
||||||
<div role="button"
|
<div role="button"
|
||||||
title="Teamer:innen-Info anzeigen"
|
title="Teamer:innen-Info anzeigen"
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends 'htmx_modal.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Einsatz absagen{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ form_start(form) }}
|
||||||
|
<h2 class="font-bold text-lg pb-4">
|
||||||
|
Teamer {{ disposition.teamer }}
|
||||||
|
</h2>
|
||||||
|
<div class="flex flex-col space-y-4 pb-4">
|
||||||
|
{{ form_row(form.calledOffBy) }}
|
||||||
|
{{ form_row(form.calledOffReason) }}
|
||||||
|
{{ form_row(form.sendNotification) }}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn">
|
||||||
|
Absagen
|
||||||
|
</button>
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
{{ form_end(form) }}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends 'email/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>
|
||||||
|
Hallo aus Köln,
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
leider mussten wir deinen Einsatz '{{ assignment.destination }}' absagen, sodass dein Einsatz
|
||||||
|
nicht stattfinden kann:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ reason | nl2br }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Bei Fragen melde dich gerne unter <a href="mailto:[email protected]">[email protected]</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="{{ url('app_teamer_index') }}" class="button">
|
||||||
|
Zum Portal
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
@@ -12,6 +12,7 @@ label:
|
|||||||
confirmed: bestätigt
|
confirmed: bestätigt
|
||||||
ended: beendet
|
ended: beendet
|
||||||
completed: abgeschlossen
|
completed: abgeschlossen
|
||||||
|
called_off: abgesagt
|
||||||
document:
|
document:
|
||||||
type:
|
type:
|
||||||
invoice: Honorarnote
|
invoice: Honorarnote
|
||||||
|
|||||||
Reference in New Issue
Block a user