<?php
declare(strict_types=1);
namespace ERP\BillingRequestsBundle\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Project;
use App\Entity\ProjectBudget;
use App\Entity\User;
use App\Entity\Workflow\WorkflowTransitionLog;
use App\Mail\Mailer;
use App\Repository\UserRepository;
use App\Services\CurrencyConverterService;
use App\Services\MoneyEmbeddableService;
use Carbon\Carbon;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use ERP\BillingRequestsBundle\Entity\BillingRequest;
use ERP\BillingRequestsBundle\Services\BillingRequestService;
use ERP\CommentBundle\Entity\Comment;
use ERP\CoreBundle\Exception\LogicException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Workflow\Event\Event;
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\Transition;
class WorkflowTransitionNotifier implements EventSubscriberInterface
{
private Mailer $mailer;
private Security $security;
private RequestStack $requestStack;
private Registry $workflowRegistry;
private EntityManagerInterface $entityManager;
private BillingRequestService $billingRequestService;
private CurrencyConverterService $currencyConverterService;
private MoneyEmbeddableService $moneyEmbeddableService;
public function __construct(
Mailer $mailer,
Security $security,
RequestStack $requestStack,
Registry $workflowRegistry,
EntityManagerInterface $entityManager,
BillingRequestService $billingRequestService,
CurrencyConverterService $currencyConverterService,
MoneyEmbeddableService $moneyEmbeddableService
) {
$this->mailer = $mailer;
$this->security = $security;
$this->requestStack = $requestStack;
$this->workflowRegistry = $workflowRegistry;
$this->entityManager = $entityManager;
$this->billingRequestService = $billingRequestService;
$this->currencyConverterService = $currencyConverterService;
$this->moneyEmbeddableService = $moneyEmbeddableService;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => [
['onCreateEnterDraft', EventPriorities::POST_WRITE],
],
'workflow.billing_request.entered' => 'onEntered',
'workflow.billing_request.enter.draft' => 'onEnterDraft',
'workflow.billing_request.leave.draft' => 'onLeaveDraft',
'workflow.billing_request.transition.cancel' => 'onExtendedTransition',
'workflow.billing_request.transition.invoicing' => 'onExtendedTransition',
'workflow.billing_request.transition.disputing' => 'onExtendedTransition',
'workflow.billing_request.guard.disputing' => 'onGuardDisputing',
];
}
public function onCreateEnterDraft(ViewEvent $event): void
{
/** @var BillingRequest $billingRequest */
$billingRequest = $event->getControllerResult();
if ($billingRequest instanceof BillingRequest && 'POST' === $event->getRequest()->getMethod()) {
$applyTransition = 'to_draft';
$workflow = $this->workflowRegistry->get($billingRequest);
$enabledTransitions = new ArrayCollection($workflow->getEnabledTransitions($billingRequest));
if ($enabledTransitions->exists(function ($key, Transition $transition) use ($applyTransition) {
return $applyTransition === $transition->getName();
})) {
/** @var Transition $transition */
$transition = $enabledTransitions
->filter(function (Transition $transition) use ($applyTransition) {
return $applyTransition === $transition->getName();
})
->first()
;
$workflow->apply($billingRequest, $transition->getName());
$this->entityManager->flush();
}
}
}
public function onEntered(Event $event): void
{
/** @var BillingRequest|null $billingRequest */
$billingRequest = $event->getSubject();
if ($billingRequest instanceof BillingRequest) {
/** @var WorkflowTransitionLog $lastTransition */
$lastTransition = $billingRequest->getWorkflowTransitionLogs()->last();
/** @var User $actor */
$user = $billingRequest->getCreatedBy();
$recipientUsersEmails = array_unique($this->billingRequestService->getNotifyees($billingRequest)->map(static function (User $recipient) {
return $recipient->getEmail();
})->toArray());
$billingRequestNumber = $billingRequest->getId();
/** @var UserRepository $userRepositorey */
$userRepository = $this->entityManager->getRepository(User::class);
if ($user->getTimezone()) {
$timeZone = $user->getTimezone()->getName();
$timeZoneLabel = sprintf('(GMT%s)', $user->getTimezone()->getOffset());
} elseif ($user->getOrganization() && $user->getOrganization()->getTimezone()) {
$timeZone = $user->getOrganization()->getTimezone()->getName();
$timeZoneLabel = sprintf('(GMT%s)', $user->getOrganization()->getTimezone()->getOffset());
} else {
$timeZone = 'UTC';
$timeZoneLabel = '(UTC)';
}
if ('created' === $lastTransition->getFrom()) {
foreach ($recipientUsersEmails as $recipientUsersEmail) {
$recipient = $userRepository->findOneBy(['email' => $recipientUsersEmail]);
$this->mailer->send(
'billing-request/created',
compact('user', 'billingRequest', 'recipient', 'timeZone', 'timeZoneLabel'),
[$recipientUsersEmail],
null,
sprintf('New Billing Request (#%s) from %s %s', $billingRequestNumber, $user->getFirstName(), $user->getLastName()),
$billingRequest->getAttachments()->toArray()
);
}
} else {
foreach ($recipientUsersEmails as $recipientUsersEmail) {
$recipient = $userRepository->findOneBy(['email' => $recipientUsersEmail]);
$this->mailer->send(
'billing-request/status-change',
compact('user', 'billingRequest', 'recipient', 'timeZone', 'timeZoneLabel'),
[$recipientUsersEmail],
null,
sprintf('Status changed on Billing Request (#%s) from %s %s', $billingRequestNumber, $user->getFirstName(), $user->getLastName())
);
}
}
}
}
public function onExtendedTransition(Event $event)
{
$resource = $event->getSubject();
$transitionName = $event->getTransition()->getName();
if (in_array($transitionName, ['invoicing', 'disputing', 'cancel'])) {
$error = false;
$requestContent = json_decode((string) $this->requestStack->getCurrentRequest()->getContent(), true);
switch ($transitionName) {
case 'invoicing':
if (empty($requestContent['invoice_number'])) {
$error = "Transitioning to {$transitionName} can't be done without providing invoice number!";
}
break;
case 'disputing':
if (empty($requestContent['dispute_reason'])) {
$error = "Transitioning to {$transitionName} can't be done without providing dispute reason!";
} else {
$user = $this->security->getUser();
if ($user instanceof User) {
$comment = new Comment();
$comment->setUser($user);
$comment->setContent("{$requestContent['dispute_reason']}");
$resource->addComment($comment);
$comment->workflow_comment = true;
}
}
break;
case 'cancel':
if (empty($requestContent['cancellation_reason'])) {
$error = "Transitioning to {$transitionName} can't be done without providing cancellation reason!";
}
break;
default:
break;
}
if ($error) {
$event->stopPropagation();
throw LogicException::withStatusCode(sprintf('%sTransition.canNot%s', get_class($resource), $transitionName), $error, 400);
}
}
}
public function onEnterDraft(Event $event): void
{
// $billingRequest = $event->getSubject();
// if ($billingRequest instanceof BillingRequest) {
// $project = $billingRequest->getProject();
//
// if ($project instanceof Project && BillingRequest::BILLING_TYPE_TIME_AND_MATERIALS === $billingRequest->getBillingType()) {
// $start = Carbon::instance($billingRequest->getDateStart());
// $end = Carbon::instance($billingRequest->getDateEnd());
//
// $defaultHourlyRate = $this->moneyEmbeddableService->getNormalizedValue($billingRequest->getDefaultHourlyRateValue(), $project->getCurrency());
//
// $amount = $this->billingRequestService->calculateTotalValue(
// $project,
// $start,
// $end,
// $defaultHourlyRate
// );
//
// $billingRequest->setValue($amount);
// }
// }
}
public function onLeaveDraft(Event $event): void
{
// $billingRequest = $event->getSubject();
// if ($billingRequest instanceof BillingRequest) {
// if ($billingRequest->getProject() instanceof Project && $billingRequest->getAutomation() && ProjectBudget::ESTIMATE_TYPE_HOURLY_QUOTED === $billingRequest->getProject()->allBudgetsHasSameEstimateType(true)) {
// $billingRequest->setValue($this->billingRequestService->getBillingRequestValue($billingRequest));
// }
// }
}
public function onGuardDisputing(GuardEvent $event): void
{
/** @var BillingRequest $billingRequest */
$billingRequest = $event->getSubject();
if ($billingRequest->getProject() && ProjectBudget::ESTIMATE_TYPE_HOURLY_QUOTED != $billingRequest->getProject()->allBudgetsHasSameEstimateType(true)) {
$event->setBlocked(true);
}
}
}