<?php
namespace App\Controller;
use App\Entity\Asset;
use App\Entity\AssetLifecyclePeriod;
use App\Entity\AssetMaintenance;
use App\Entity\AssetTransaction;
use App\Entity\Customer;
use App\Entity\Note;
use App\Entity\Repository\AssetRepository;
use App\Entity\Repository\CustomerRepository;
use App\Entity\Repository\TruckAttachmentRepository;
use App\Entity\Repository\TruckChargerRepository;
use App\Entity\Repository\TruckRepository;
use App\Entity\Truck;
use App\Entity\TruckAttachment;
use App\Entity\TruckCharger;
use App\Entity\TruckMake;
use App\Entity\TruckModel;
use App\Form\Type\AssetLifecyclePeriodType;
use App\Form\Type\AssetMaintenanceType;
use App\Form\Type\EndAssetLifecyclePeriodType;
use App\Form\Type\NoteType;
use App\Form\Type\TruckAttachmentType;
use App\Form\Type\TruckChargerType;
use App\Form\Type\TruckModelType;
use App\Form\Type\TruckType;
use App\Model\TransactionCategory;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class AssetController extends Controller
{
public function view(?string $id, AssetRepository $assetRepository): Response
{
$this->securityCheck(['ROLE_SERVICE_ADMIN', 'ROLE_ENGINEER']);
if (!$id) {
throw $this->createNotFoundException('No ID supplied.');
}
$asset = $assetRepository->find($id);
if (!$asset) {
throw $this->createNotFoundException('Asset with ID ' . $id . ' not found.');
}
return $this->render('Asset/view.html.twig', ['asset' => $asset]);
}
public function listActive($type, TruckAttachmentRepository $attachmentRepository, TruckChargerRepository $truckChargerRepository, TruckRepository $truckRepository, $page = 1): Response
{
$this->securityCheck(['ROLE_SERVICE_ADMIN', 'ROLE_ENGINEER']);
$assets = match ($type) {
'attachment' => $attachmentRepository->getActiveListingsWithArchived(),
'charger' => $truckChargerRepository->getActiveListingsWithArchived(),
default => $truckRepository->getActiveListingsWithArchived(),
};
return $this->render('Asset/listActive.html.twig', ['assets' => $assets, 'type' => $type]);
}
public function listForSaleJob(TruckAttachmentRepository $attachmentRepository, TruckChargerRepository $truckChargerRepository, TruckRepository $truckRepository): Response
{
$this->securityCheck(['ROLE_SERVICE_ADMIN', 'ROLE_ENGINEER']);
$assets = [...$attachmentRepository->getUnassignedListings(), ...$truckChargerRepository->getUnassignedListings(), ...$truckRepository->getUnassignedListings()];
return $this->render('Asset/listForSaleJob.html.twig', ['assets' => $assets]);
}
public function getContactCustomer(int $id, CustomerRepository $customerRepository): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$customer = $customerRepository->find($id);
if (!$customer) {
return $response;
}
$responseArray = [];
foreach ($customer->getContacts()->toArray() as $contact) {
$responseArray[] = ['id' => $contact->getId(), 'name' => $contact->getName()];
}
return new JsonResponse($responseArray);
}
public function newTruck(Request $request, EntityManagerInterface $em): Response
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$truck = new Truck();
$showCustomer = $request->query->get('showCustomer') ?? false;
$form = $this->createForm(TruckType::class, $truck, ['showCustomer' => (bool) $showCustomer]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$truck = $form->getData();
$newMake = $form['newMake']->getData();
$newModel = $form['newModel']->getData();
$customer = null;
$truckMake = null;
$truckModel = null;
$noteDetail = $form['detail']->getData();
if ($newMake !== null) {
$truckMake = new TruckMake();
$truckMake->setName($newMake);
$em->persist($truckMake);
}
if (!empty($newModel)) {
$truckModel = new TruckModel();
$truckModel->setName($newModel);
if ($truckMake) {
$truckModel->setMake($truckMake);
} else {
$truckModel->setMake($form['make']->getData());
}
$em->persist($truckModel);
$truck->setModel($truckModel);
}
if ($noteDetail) {
$note = new Note();
$hr = date('H');
$min = date('i');
$sec = date('s');
$date = $form['date']->getData();
$date->setTime($hr, $min, $sec);
$note->setDate($date);
$note->setAsset($truck);
$note->setDetail($form['detail']->getData());
$em->persist($note);
}
if ($showCustomer) {
$customer = $form['customer']->getData();
$clientFleet = $form['clientFleet']->getData();
} else {
$customer = $this->getRepo(Customer::class)->getDefaultCustomer();
$clientFleet = null;
}
$period = new AssetLifecyclePeriod($truck);
$period->setStart(new \DateTime());
if ($customer) {
$period->setCustomer($customer);
$period->setClientFleet($clientFleet);
$period->setType(AssetLifecyclePeriod::PERIOD_TYPE_CUSTOMER_OWNED);
$contacts = isset($form['contacts']) ? $form['contacts']->getData() : [];
foreach ($contacts as $contact) {
$contact->addAssetLifecyclePeriod($period);
$em->persist($contact);
$period->addContact($contact);
}
}
$em->persist($period);
$em->flush();
$truck->setCurrentLifecyclePeriod($period);
$this->getAssetManager()->saveAsset($truck);
$assign = $request->query->get('assign', false);
if ($truckModel) {
$modelForm = $this->createForm(TruckModelType::class, $truckModel);
$isElectric = $truckModel->getFuel() === 'Electric';
$routeParams = ['id' => $truck->getId()];
if ($assign) {
$routeParams['assign'] = 1;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'html' => $this->renderView('Truck/_newModelForm.html.twig', ['form' => $modelForm->createView(), 'formRoute' => $this->generateUrl('edit_truck_model', $routeParams), 'truckModel' => $truckModel, 'isElectric' => $isElectric, 'assign' => $assign])]);
return $response;
}
$this->setFlash('success', 'The truck with ID "' . $truck->getId() . '" has been successfully added');
if ($assign) {
return $this->redirect($this->generateUrl('transfer_asset', ['id' => $truck->getId()]));
}
if ($customer) {
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => false, 'url' => $this->generateUrl('active_assets') . '?selectedItem=' . $truck->getId()]);
} else {
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => true]);
}
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'showCustomer' => $showCustomer, 'isEdit' => false, 'html' => $this->renderView('Truck/_form.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('new_truck'), 'truck' => $truck, 'isEdit' => false, 'period' => $truck->getCurrentLifecyclePeriod()])]);
return $response;
}
public function editTruck(Request $request, EntityManagerInterface $em, $id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$truck = $this->getRepo(Truck::class)->find($id);
if (!$truck) {
$response->setData([
'success' => false,
'displayForm' => false,
'refresh' => false,
'message' => 'The truck was not found.'
]);
return $response;
}
$storedContacts = new ArrayCollection();
foreach ($truck->getContacts() ?? [] as $contact) {
$storedContacts->add($contact);
}
$storedImages = new ArrayCollection();
foreach ($truck->getImages() as $image) {
$storedImages->add($image);
}
$form = $this->createForm(TruckType::class, $truck, [
'showContacts' => $truck->getPurchaseOrder() == null || $truck->getPurchaseOrder()->getCompleted()
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$truck = $form->getData();
$newMake = $form['newMake']->getData();
$newModel = $form['newModel']->getData();
$truckMake = null;
$truckModel = null;
foreach ($storedContacts as $storedContact) {
if ($truck->getContacts()->contains($storedContact) === false) {
$storedContact->removeTruck($truck);
$em->persist($storedContact);
}
}
if ($newMake !== null) {
$truckMake = new TruckMake();
$truckMake->setName($newMake);
$em->persist($truckMake);
}
if (!empty($newModel)) {
$truckModel = new TruckModel();
$truckModel->setName($newModel);
$truckModel->setFuel($form['type']->getData());
if ($truckMake) {
$truckModel->setMake($truckMake);
} else {
$truckModel->setMake($form['make']->getData());
}
$em->persist($truckModel);
$truck->setModel($truckModel);
}
$period = $truck->getCurrentLifecyclePeriod();
// The contacts field is unmapped (mapped => false) so we must manually sync it
// Since Contact is the owning side of the ManyToMany, we must persist Contact entities
// to ensure Doctrine updates the join table when contacts are added or removed
if (isset($form['contacts']) && $period) {
// Get the new collection of contacts from the form
$newContacts = new ArrayCollection($form['contacts']->getData() ?: []);
// Use setContacts() which properly synchronizes both sides of the relationship
// This will call removeContact() for removed contacts and addContact() for new ones
$period->setContacts($newContacts);
// Persist all Contact entities involved (both added and remaining)
foreach ($period->getContacts() as $contact) {
$em->persist($contact);
}
// Also persist Contact entities that were removed to ensure join table cleanup
$originalContacts = $storedContacts; // Stored before form submission
foreach ($originalContacts as $originalContact) {
if (!$period->getContacts()->contains($originalContact)) {
$em->persist($originalContact);
}
}
}
if (($purchaseOrder = $truck->getPurchaseOrder()) && isset($form['leadTime'])) {
$purchaseOrder->setLeadTime($form['leadTime']->getData());
$purchaseOrder->setEta($form['eta']->getData());
}
if ($period) {
$period->setSalePrice(null ?? $form['stockValue']->getData());
$em->persist($period);
}
$em->flush();
// Clear the entity manager to ensure subsequent queries fetch fresh data
// This is critical for email notifications to use the updated contact list
$em->clear(AssetLifecyclePeriod::class);
$em->clear(Contact::class);
$this->getAssetManager()->saveAsset($truck);
$response->setData(['success' => true, 'displayForm' => false, 'message' => 'The truck with ID "' . $truck->getId() . '" was successfully edited.', 'refresh' => true]);
$this->setFlash('success', 'The truck with ID "' . $truck->getId() . '" has been successfully edited');
return $response;
}
$response->setData([
'success' => true,
'displayForm' => true,
'message' => null,
'isEdit' => true,
'refresh' => false,
'html' => $this->renderView('Truck/_form.html.twig', [
'form' => $form->createView(),
'period' => $truck->getCurrentLifecyclePeriod(),
'customerTruck' => $truck->getCustomer(),
'modelForm' => null,
'formRoute' => $this->generateUrl('edit_truck', ['id' => $id]),
'truck' => $truck,
'isEdit' => true
])
]);
return $response;
}
public function particulars(Request $request, EntityManagerInterface $em, $id = null, $lifecyclePeriodId = null): Response
{
$this->securityCheck(['ROLE_SERVICE_ADMIN', 'ROLE_ENGINEER']);
$asset = $this->getRepo(Asset::class)->find($id);
$attachedToTruck = null;
if ($asset->getAssetType() === 'Attachment') {
$attachedToTruck = $this->getRepo(Truck::class)->findOneBy(['attachment' => $asset->getId()]);
} elseif ($asset->getAssetType() === 'Charger') {
$attachedToTruck = $this->getRepo(Truck::class)->findOneBy(['charger' => $asset->getId()]);
}
if (!$asset->getCurrentLifecyclePeriod()) {
$this->getAssetManager()->ensureCurrentLifecyclePeriod($asset);
$em->flush();
}
$period = $asset->getCurrentLifecyclePeriod();
if ($lifecyclePeriodId) {
$specificPeriod = $this->getRepo(AssetLifecyclePeriod::class)->findOneBy(['id' => $lifecyclePeriodId, 'asset' => $asset]);
if ($specificPeriod) {
$period = $specificPeriod;
}
}
$versions = $this->getVersionRepo()->getLogEntries($asset);
$assetType = $asset->getAssetType(true);
$canEdit = $this->isGranted('ROLE_SERVICE_ADMIN') && $request->query->get('disallowEdit', false) === false;
return $this->render(
'' . $assetType . '/particulars.html.twig',
[
'asset' => $asset,
'canEdit' => $canEdit,
'attachedToTruck' => $attachedToTruck,
'versions' => $versions,
'period' => $period
]);
}
public function delete(EntityManagerInterface $em, $id = null): RedirectResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$asset = $this->getRepo(Asset::class)->find($id);
if (!$asset) {
throw $this->createNotFoundException('The asset with ID"' . $id . '" was not found.');
}
$period = $asset->getCurrentLifecyclePeriod();
$hasCustomer = $period ? (bool) $period->getCustomer() : false;
$em->remove($asset);
$em->flush();
$this->setFlash('success', 'Asset "' . $asset->getId() . '" was successfully deleted');
return $this->redirect($this->generateUrl($hasCustomer ? 'active_assets' : 'asset_register'));
}
public function getTruckModels($make = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
if ($make === null) {
throw $this->createNotFoundException('No truck make supplied.');
}
$truckModels = $this->getRepo(TruckModel::class)->getModelsForMake($make);
$models = [];
foreach ($truckModels as $truckModel) {
$models[] = ['id' => $truckModel->getId(), 'name' => $truckModel->getName(), 'fuel' => $truckModel->getFuel()];
}
$response = new JsonResponse();
return $response->setData(['models' => $models]);
}
public function editTruckModel(Request $request, EntityManagerInterface $em, $id = null): Response
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
if ($id === null) {
throw $this->createNotFoundException('No truck model supplied.');
}
$truck = $this->getRepo(Truck::class)->find($id);
$truckModel = $truck->getModel();
$response = new JsonResponse();
$form = $this->createForm(TruckModelType::class, $truckModel);
$assign = $request->query->get('assign', false);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$truckModel = $form->getData();
$em->persist($truckModel);
$em->flush();
$response->setData(['success' => true, 'displayForm' => false, 'message' => 'The truck model was successfully updated.', 'refresh' => true]);
$this->setFlash('success', 'The truck model has been successfully updated');
if ($assign) {
return $this->redirect($this->generateUrl('transfer_asset', ['id' => $id]));
}
return $response;
}
$routeParams = ['id' => $id];
if ($assign) {
$routeParams['assign'] = 1;
}
$response->setData(['success' => false, 'displayForm' => true, 'message' => null, 'isEdit' => true, 'refresh' => false, 'html' => $this->renderView('Truck/_newModelForm.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('edit_truck_model', $routeParams), 'truckModel' => $truckModel, 'isEdit' => true])]);
return $response;
}
public function listRegister($page = 1): Response
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$trucks = $this->getRepo(Truck::class)->getAllForRegister();
$attachments = $this->getRepo(TruckAttachment::class)->getAllForRegister();
$chargers = $this->getRepo(TruckCharger::class)->getAllForRegister();
return $this->render('Asset/listRegister.html.twig', [
'trucks' => $trucks,
'attachments' => $attachments,
'chargers' => $chargers
]);
}
public function getAddressForAsset($id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$assetPeriod = $this->getRepo(AssetLifecyclePeriod::class)->find($id);
if (!$assetPeriod) {
throw $this->createNotFoundException('No asset lifecycle period with ID "' . $id . '" was found.');
}
$asset = $assetPeriod->getAsset();
$siteAddress = $asset->getSiteAddress();
$response->setData(['addressId' => ($siteAddress ? $siteAddress->getId() : null)]);
return $response;
}
public function getAssetInfo($id = null, \App\Manager\ServiceChecklistManager $checklistManager): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$assetPeriod = $this->getRepo(AssetLifecyclePeriod::class)->find($id);
if (!$assetPeriod) {
throw $this->createNotFoundException('No asset lifecycle period with ID "' . $id . '" was found.');
}
$levelCAvailable = $checklistManager->isLevelCAvailable($assetPeriod);
$response->setData(['levelCAvailable' => $levelCAvailable]);
return $response;
}
public function getContactsForAsset($id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$assetPeriod = $this->getRepo(AssetLifecyclePeriod::class)->find($id);
if (!$assetPeriod) {
throw $this->createNotFoundException('No asset lifecycle period with ID "' . $id . '" was found.');
}
$contacts = $assetPeriod->getContacts() ? $assetPeriod->getContacts()->toArray() : [];
$contactIds = array_map(fn ($contact) => $contact->getId(), $contacts);
$response->setData(['contacts' => $contactIds]);
return $response;
}
public function newNote(Request $request, EntityManagerInterface $em, $id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
if ($id === null) {
throw $this->createNotFoundException('No asset ID was supplied');
}
$asset = $this->getRepo(Asset::class)->find($id);
if (!$asset) {
throw $this->createNotFoundException('The asset with ID "' . $id . '" was not found.');
}
$response = new JsonResponse();
$note = new Note();
$form = $this->createForm(NoteType::class, $note, [
'includeReason' => false,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$note = $form->getData();
$this->getAssetManager()->ensureCurrentLifecyclePeriod($asset);
if ($asset->getCustomer()) {
$note->setAssetLifecyclePeriod($asset->getCurrentLifecyclePeriod());
} else {
$note->setAsset($asset);
}
$hr = date('H');
$min = date('i');
$sec = date('s');
$date = $note->getDate();
$date->setTime($hr, $min, $sec);
$note->setDate($date);
$em->persist($note);
$em->flush();
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => true]);
$this->setFlash('success', 'The note "' . $note->getId() . '" has been successfully created for asset "' . $asset->getNameString() . '".');
return $response;
}
$response->setData(['success' => false, 'displayForm' => true, 'refresh' => false, 'isEdit' => false, 'html' => $this->renderView('Asset/_newNoteForm.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('new_asset_note', ['id' => $id])])]);
return $response;
}
public function newMaintenance(Request $request, EntityManagerInterface $em, $id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
if ($id === null) {
throw $this->createNotFoundException('No asset ID was supplied');
}
$asset = $this->getRepo(Asset::class)->find($id);
if (!$asset) {
throw $this->createNotFoundException('The asset with ID "' . $id . '" was not found.');
}
$response = new JsonResponse();
$assetMaintenance = new AssetMaintenance();
$form = $this->createForm(AssetMaintenanceType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$assetMaintenance = $form->getData();
$this->getAssetManager()->ensureCurrentLifecyclePeriod($asset);
$assetMaintenance->setAssetLifecyclePeriod($asset->getCurrentLifecyclePeriod());
$transaction = new AssetTransaction();
$transaction->setAssetLifecyclePeriod($asset->getCurrentLifecyclePeriod());
$transaction->setMaintenance($assetMaintenance);
$transaction->setAmount($assetMaintenance->getAmount());
$transaction->setTransactionCategory(TransactionCategory::OUT_TRUCK_MAINTENANCE_EXPENDITURE);
$assetMaintenance->setTransaction($transaction);
$em->persist($assetMaintenance);
$em->persist($transaction);
$em->flush();
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => true]);
$this->setFlash('success', 'The maintenance "' . $assetMaintenance->getDetail() . '" has been successfully created for asset "' . $asset->getNameString() . '".');
return $response;
}
$response->setData(['success' => false, 'displayForm' => true, 'refresh' => false, 'isEdit' => false, 'html' => $this->renderView('Asset/_newMaintenanceForm.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('new_asset_maintenance', ['id' => $id])])]);
return $response;
}
public function editMaintenance(Request $request, EntityManagerInterface $em, $id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
if ($id === null) {
throw $this->createNotFoundException('No truck model supplied.');
}
$assetMaintenance = $this->getRepo(AssetMaintenance::class)->find($id);
$response = new JsonResponse();
$form = $this->createForm(AssetMaintenanceType::class, $assetMaintenance);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$assetMaintenance = $form->getData();
$em->persist($assetMaintenance);
$transaction = $assetMaintenance->getTransaction();
$transaction->setAmount($assetMaintenance->getAmount());
$em->persist($transaction);
$em->flush();
$response->setData(['success' => true, 'displayForm' => false, 'message' => 'The truck maintenance "' . $assetMaintenance->getDetail() . '" was successfully updated.', 'refresh' => true]);
$this->setFlash('success', 'The truck maintenance "' . $assetMaintenance->getDetail() . '" was successfully updated.');
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'message' => null, 'isEdit' => true, 'refresh' => false, 'html' => $this->renderView('Asset/_newMaintenanceForm.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('edit_maintenance', ['id' => $id]), 'maintenance' => $assetMaintenance, 'isEdit' => true])]);
return $response;
}
public function viewMaintenance(?string $id): Response
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
if (!$id) {
throw $this->createNotFoundException('No truck ID supplied.');
}
$maintenance = $this->getRepo(AssetMaintenance::class)->find($id);
if (!$maintenance) {
throw $this->createNotFoundException('Truck with ID ' . $id . ' not found.');
}
$versions = $this->getVersionRepo()->getLogEntries($maintenance);
return $this->render('Asset/viewMaintenance.html.twig', ['maintenance' => $maintenance, 'versions' => $versions]);
}
public function deleteMaintenance(EntityManagerInterface $em, $id = null): RedirectResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$maintenance = $this->getRepo(AssetMaintenance::class)->find($id);
if (!$maintenance) {
throw $this->createNotFoundException('The truck maintenance with ID"' . $id . '" was not found.');
}
$maintenanceDetail = $maintenance->getDetail();
$truckId = $maintenance->getTruck()->getId();
$em->remove($maintenance);
$em->flush();
$this->setFlash('success', 'Maintenance "' . $maintenanceDetail . '" was successfully deleted');
return $this->redirect($this->generateUrl('view_truck', ['id' => $truckId]));
}
public function getTruckList(): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$trucks = $this->getRepo(Truck::class)->getActiveListings();
$response = new JsonResponse();
return $response->setData($trucks);
}
public function newTruckAttachment(Request $request, EntityManagerInterface $em): Response
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$attachment = new TruckAttachment();
$showCustomer = $request->query->get('showCustomer') ?? false;
$form = $this->createForm(TruckAttachmentType::class, $attachment, ['showCustomer' => (bool) $showCustomer]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$noteDetail = $form['detail']->getData();
if ($noteDetail) {
$note = new Note();
$hr = date('H');
$min = date('i');
$sec = date('s');
$date = $form['date']->getData();
$date->setTime($hr, $min, $sec);
$note->setDate($date);
$note->setAsset($attachment);
$note->setDetail($form['detail']->getData());
$em->persist($note);
}
if ($showCustomer) {
$customer = $form['customer']->getData();
$clientFleet = $form['clientFleet']->getData();
} else {
$customer = $this->getRepo(Customer::class)->getDefaultCustomer();
$clientFleet = null;
}
if ($customer) {
$period = new AssetLifecyclePeriod($attachment);
$period->setCustomer($customer);
$period->setClientFleet($clientFleet);
$period->setStart(new \DateTime());
$period->setType(AssetLifecyclePeriod::PERIOD_TYPE_CUSTOMER_OWNED);
$em->persist($period);
$em->flush();
$attachment->setCurrentLifecyclePeriod($period);
}
$this->getAssetManager()->saveAsset($attachment);
$this->setFlash('success', 'Attachment created successfully.');
if ($request->query->get('assign', false)) {
return $this->redirect($this->generateUrl('transfer_asset', ['id' => $attachment->getId()]));
}
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => true]);
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'showCustomer' => $showCustomer, 'html' => $this->renderView('TruckAttachment/_form.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('new_truck_attachment'), 'attachment' => $attachment])]);
return $response;
}
public function editTruckAttachment(Request $request, $id, EntityManagerInterface $em): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$attachment = $this->getRepo(TruckAttachment::class)->find($id);
if (!$attachment) {
$response->setData(['success' => false, 'displayForm' => false, 'refresh' => false, 'message' => 'The truck attachment was not found.']);
return $response;
}
$form = $this->createForm(TruckAttachmentType::class, $attachment);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getAssetManager()->saveAsset($attachment);
$this->setFlash('success', 'Attachment updated successfully.');
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => true]);
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'html' => $this->renderView('TruckAttachment/_form.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('edit_truck_attachment', ['id' => $id]), 'attachment' => $attachment, 'isEdit' => true])]);
return $response;
}
public function newTruckCharger(Request $request, EntityManagerInterface $em): Response
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$charger = new TruckCharger();
$showCustomer = $request->query->get('showCustomer') ?? false;
$form = $this->createForm(TruckChargerType::class, $charger, ['showCustomer' => (bool) $showCustomer]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$noteDetail = $form['detail']->getData();
if ($noteDetail) {
$note = new Note();
$hr = date('H');
$min = date('i');
$sec = date('s');
$date = $form['date']->getData();
$date->setTime($hr, $min, $sec);
$note->setDate($date);
$note->setAsset($charger);
$note->setDetail($form['detail']->getData());
$em->persist($note);
}
if ($showCustomer) {
$customer = $form['customer']->getData();
$clientFleet = $form['clientFleet']->getData();
} else {
$customer = $this->getRepo(Customer::class)->getDefaultCustomer();
$clientFleet = null;
}
if ($customer) {
$period = new AssetLifecyclePeriod($charger);
$period->setCustomer($customer);
$period->setClientFleet($clientFleet);
$period->setStart(new \DateTime());
$period->setType(AssetLifecyclePeriod::PERIOD_TYPE_CUSTOMER_OWNED);
$em->persist($period);
$em->flush();
$charger->setCurrentLifecyclePeriod($period);
}
$this->getAssetManager()->saveAsset($charger);
$this->setFlash('success', 'Charger created successfully.');
if ($request->query->get('assign', false)) {
return $this->redirect($this->generateUrl('transfer_asset', ['id' => $charger->getId()]));
}
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => true]);
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'showCustomer' => $showCustomer, 'html' => $this->renderView('TruckCharger/_form.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('new_truck_charger'), 'charger' => $charger])]);
return $response;
}
public function editTruckCharger(Request $request, $id, EntityManagerInterface $em): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$charger = $this->getRepo(TruckCharger::class)->find($id);
if (!$charger) {
$response->setData(['success' => false, 'displayForm' => false, 'refresh' => false, 'message' => 'The truck charger was not found.']);
return $response;
}
$form = $this->createForm(TruckChargerType::class, $charger);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getAssetManager()->saveAsset($charger);
$this->setFlash('success', 'Charger updated successfully.');
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => true]);
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'html' => $this->renderView('TruckCharger/_form.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('edit_truck_charger', ['id' => $id]), 'charger' => $charger, 'isEdit' => true])]);
return $response;
}
public function returnAsset(Request $request, $id, EntityManagerInterface $em): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$asset = $this->getRepo(Asset::class)->find($id);
if (!$asset || !$asset->getCurrentLifecyclePeriod() || !$asset->getCurrentLifecyclePeriod()->getCustomer()) {
$error = $asset ? 'The asset is not assigned.' : 'The asset was not found.';
$response->setData(['success' => false, 'displayForm' => false, 'refresh' => false, 'message' => $error]);
return $response;
}
$period = $asset->getCurrentLifecyclePeriod();
$parentPeriod = $period->getParentPeriod();
$form = $this->createForm(EndAssetLifecyclePeriodType::class, $period);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$endDate = date_timestamp_get($period->getEnd());
if ($endDate > time()) {
$period->setScheduledLifecyclePeriodCleanup(true);
} else {
if ($parentPeriod) {
$asset->setCurrentLifecyclePeriod($parentPeriod);
} else {
$asset->setCurrentLifecyclePeriod(null);
$this->getAssetManager()->ensureCurrentLifecyclePeriod($asset);
}
}
if ($period->isContractHire() && $period->getResidualValue()) {
$asset->setStockValue($period->getResidualValue());
} elseif ($period->isPurchase() && $period->getTradeInValue()) {
$asset->setStockValue($period->getTradeInValue());
}
if (method_exists($asset, 'getAttachment')) {
if ($attachment = $asset->getAttachment()) {
$attachmentPeriod = $attachment->getCurrentLifecyclePeriod();
$parentPeriod = $attachmentPeriod->getParentPeriod();
$attachmentPeriod->setEnd($period->getEnd());
if ($endDate > time()) {
$attachmentPeriod->setScheduledLifecyclePeriodCleanup(true);
} else {
if ($parentPeriod) {
$attachment->setCurrentLifecyclePeriod($parentPeriod);
} else {
$attachment->setCurrentLifecyclePeriod(null);
$this->getAssetManager()->ensureCurrentLifecyclePeriod($attachment);
}
}
}
if ($charger = $asset->getCharger()) {
$chargerPeriod = $charger->getCurrentLifecyclePeriod();
$parentPeriod = $chargerPeriod->getParentPeriod();
$chargerPeriod->setEnd($period->getEnd());
if ($endDate > time()) {
$chargerPeriod->setScheduledLifecyclePeriodCleanup(true);
} else {
if ($parentPeriod) {
$charger->setCurrentLifecyclePeriod($parentPeriod);
} else {
$charger->setCurrentLifecyclePeriod(null);
$this->getAssetManager()->ensureCurrentLifecyclePeriod($charger);
}
}
}
}
$em->flush();
$this->setFlash('success', 'Asset returned successfully.');
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => false, 'url' => $this->generateUrl('view_asset', ['id' => $id])]);
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'html' => $this->renderView('Asset/_returnForm.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('return_asset', ['id' => $id])])]);
return $response;
}
public function transferAsset(Request $request, $id, EntityManagerInterface $em, $processForm = true): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$asset = $this->getRepo(Asset::class)->find($id);
if (!$asset) {
$response->setData(['success' => false, 'displayForm' => false, 'refresh' => false, 'message' => 'The asset was not found.']);
return $response;
}
$period = new AssetLifecyclePeriod($asset);
if ($asset->getCurrentLifecyclePeriod() && $asset->getCurrentLifecyclePeriod()->getCustomer() && !$asset->getCurrentLifecyclePeriod()->getCustomer()->getIsDefault()) {
$period->setType(AssetLifecyclePeriod::PERIOD_TYPE_CASUAL);
}
$form = $this->createForm(AssetLifecyclePeriodType::class, $period);
$form->handleRequest($request);
if ($processForm && $form->isSubmitted() && $form->isValid()) {
$period->cleanup();
$currentPeriod = $asset->getCurrentLifecyclePeriod();
// The form's setContacts() method is automatically called due to by_reference => false
// and it properly synchronizes both sides of the ManyToMany relationship
// We just need to persist the Contact entities (owning side) to ensure Doctrine detects changes
foreach ($period->getContacts() as $contact) {
$em->persist($contact);
}
if ($currentPeriod) {
if ($currentPeriod->getType() !== 0 && $period->getType() === 2) {
$period->setParentPeriod($currentPeriod);
} else {
if (!$currentPeriod->getEnd()) {
$currentPeriod->setEnd(new \DateTime());
}
}
$asset->setCurrentLifecyclePeriod(null);
}
$em->persist($period);
$asset->setCurrentLifecyclePeriod($period);
if ($asset instanceof Truck) {
$attachment = $asset->getAttachment() ? $asset->getAttachment()->getId() : null;
$charger = $asset->getCharger() ? $asset->getCharger()->getId() : null;
if ($attachment) {
$attachment = $this->getRepo(TruckAttachment::class)->find($attachment);
$attachmentCurrentPeriod = $attachment->getCurrentLifecyclePeriod();
$attachmentPeriod = new AssetLifecyclePeriod();
$attachmentPeriod->setAsset($attachment);
$attachmentPeriod->setStart($period->getStart());
$attachmentPeriod->setEnd($period->getEnd());
$attachmentPeriod->setUnderwriter($period->getUnderwriter());
$attachmentPeriod->setCustomer($period->getCustomer());
$attachmentPeriod->setType($period->getType());
if ($attachmentCurrentPeriod) {
if ($attachmentCurrentPeriod->getType() !== 0 && $attachmentPeriod->getType() === 2) {
$attachmentPeriod->setParentPeriod($attachmentCurrentPeriod);
} else {
if (!$attachmentCurrentPeriod->getEnd()) {
$attachmentCurrentPeriod->setEnd(new \DateTime());
}
}
$attachment->setCurrentLifecyclePeriod(null);
}
$em->persist($attachmentPeriod);
$attachment->setCurrentLifecyclePeriod($attachmentPeriod);
}
if ($charger) {
$charger = $this->getRepo(TruckCharger::class)->find($charger);
$chargerCurrentPeriod = $charger->getCurrentLifecyclePeriod();
$chargerPeriod = new AssetLifecyclePeriod();
$chargerPeriod->setAsset($charger);
$chargerPeriod->setStart($period->getStart());
$chargerPeriod->setEnd($period->getEnd());
$chargerPeriod->setUnderwriter($period->getUnderwriter());
$chargerPeriod->setCustomer($period->getCustomer());
$chargerPeriod->setType($period->getType());
if ($chargerCurrentPeriod) {
if ($chargerCurrentPeriod->getType() !== 0 && $chargerPeriod->getType() === 2) {
$chargerPeriod->setParentPeriod($chargerCurrentPeriod);
} else {
if (!$chargerCurrentPeriod->getEnd()) {
$chargerCurrentPeriod->setEnd(new \DateTime());
}
}
$charger->setCurrentLifecyclePeriod(null);
}
$em->persist($chargerPeriod);
$charger->setCurrentLifecyclePeriod($chargerPeriod);
}
}
$em->flush();
$this->setFlash('success', 'Asset transferred successfully.');
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => false, 'url' => $this->generateUrl('view_asset', ['id' => $id])]);
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'html' => $this->renderView('Asset/_transferForm.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('transfer_asset', ['id' => $id]), 'reloadFormRoute' => $this->generateUrl('transfer_asset_form', ['id' => $id])])]);
return $response;
}
public function editLifecyclePeriod(Request $request, $id, EntityManagerInterface $em, $processForm = true): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$period = $this->getRepo(AssetLifecyclePeriod::class)->find($id);
$asset = $this->getRepo(Asset::class)->find($period->getAsset()->getId());
$isTruck = $asset->getAssetType() === 'Truck';
$attachment = $isTruck && $asset->getAttachment() ? $asset->getAttachment()->getCurrentLifecyclePeriod() : null;
$charger = $isTruck && $asset->getCharger() ? $asset->getCharger()->getCurrentLifecyclePeriod() : null;
if (!$period) {
$response->setData(['success' => false, 'displayForm' => false, 'refresh' => false, 'message' => 'The asset status was not found.']);
return $response;
}
// Store original contacts before form submission to track removals
$originalContacts = new ArrayCollection();
foreach ($period->getContacts() as $contact) {
$originalContacts->add($contact);
}
$form = $this->createForm(AssetLifecyclePeriodType::class, $period);
$form->handleRequest($request);
if ($processForm && $form->isSubmitted() && $form->isValid()) {
$period->cleanup();
// The form's setContacts() method is automatically called due to by_reference => false
// and it properly synchronizes both sides of the ManyToMany relationship
// Persist Contact entities (owning side) to ensure Doctrine detects the changes
foreach ($period->getContacts() as $contact) {
$em->persist($contact);
}
// Also persist contacts that were removed to ensure join table cleanup
foreach ($originalContacts as $contact) {
if (!$period->getContacts()->contains($contact)) {
$em->persist($contact);
}
}
$em->persist($period);
if ($attachment) {
$periodAttachment = $this->getRepo(AssetLifecyclePeriod::class)->find($attachment);
$periodAttachment->setStart($form['start']->getData());
$periodAttachment->setEnd($form['end']->getData());
$periodAttachment->setEnd($form['end']->getData());
$periodAttachment->setUnderwriter($form['underwriter']->getData());
$periodAttachment->setCustomer($form['customer']->getData());
$periodAttachment->setType($form['type']->getData());
$em->persist($periodAttachment);
}
if ($charger) {
$periodCharger = $this->getRepo(AssetLifecyclePeriod::class)->find($charger);
$periodCharger->setStart($form['start']->getData());
$periodCharger->setEnd($form['end']->getData());
$periodCharger->setUnderwriter(isset($form['underwriter']) ? $form['underwriter']->getData() : '');
$periodCharger->setCustomer($form['customer']->getData());
$periodCharger->setType($form['type']->getData());
$em->persist($periodCharger);
}
$em->flush();
// Clear the entity manager to ensure subsequent queries fetch fresh data
// This is critical for email notifications to use the updated contact list
$em->clear(AssetLifecyclePeriod::class);
$em->clear(Contact::class);
$this->setFlash('success', 'Asset status updated successfully.');
$response->setData(['success' => true, 'displayForm' => false, 'refresh' => false, 'url' => $this->generateUrl('view_asset', ['id' => $period->getAsset()->getId()])]);
return $response;
}
$response->setData(['success' => true, 'displayForm' => true, 'refresh' => false, 'html' => $this->renderView('Asset/_transferForm.html.twig', ['form' => $form->createView(), 'formRoute' => $this->generateUrl('edit_asset_lifecycle_period', ['id' => $id]), 'reloadFormRoute' => $this->generateUrl('edit_asset_lifecycle_period_form', ['id' => $id])])]);
return $response;
}
public function autocomplete(Request $request): JsonResponse
{
$searchData = $request->query->all();
$response = new JsonResponse([]);
if (!array_key_exists('type', $searchData) || !array_key_exists('search', $searchData) || !array_key_exists('customer', $searchData)) {
return $response;
}
$type = null;
switch ($searchData['type']) {
case 'Truck':
$type = Truck::class;
break;
case 'TruckAttachment':
$type = TruckAttachment::class;
break;
case 'TruckCharger':
$type = TruckCharger::class;
break;
}
$assets = $this->getRepo($type)->findItemsForAutocomplete($searchData['search'], $searchData['customer']);
$responseData = array_map(fn ($asset): array => ['id' => $asset->getCurrentLifecyclePeriod()->getId(), 'name' => $asset->getCurrentLifecyclePeriod()->getClientFleet() . ' - ' . $asset->getNameString()], $assets);
$response->setData($responseData);
return $response;
}
/**
* @Route("/assets/archive/{id}", name="archive_asset", methods={"POST"})
*/
public function archiveAsset(EntityManagerInterface $em, $id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$asset = $this->getRepo(Asset::class)->find($id);
if (!$asset) {
$response->setData(['success' => false, 'message' => 'Asset not found.']);
return $response;
}
$asset->setArchived(true);
$asset->setArchivedAt(new \DateTime());
$em->persist($asset);
$em->flush();
$this->setFlash('success', 'Asset "' . $asset->getNameString() . '" has been successfully archived.');
$response->setData(['success' => true, 'message' => 'Asset archived successfully.']);
return $response;
}
/**
* @Route("/assets/unarchive/{id}", name="unarchive_asset", methods={"POST"})
*/
public function unarchiveAsset(EntityManagerInterface $em, $id = null): JsonResponse
{
$this->securityCheck('ROLE_SERVICE_ADMIN');
$response = new JsonResponse();
$asset = $this->getRepo(Asset::class)->find($id);
if (!$asset) {
$response->setData(['success' => false, 'message' => 'Asset not found.']);
return $response;
}
$asset->setArchived(false);
$asset->setArchivedAt(null);
$em->persist($asset);
$em->flush();
$this->setFlash('success', 'Asset "' . $asset->getNameString() . '" has been successfully unarchived.');
$response->setData(['success' => true, 'message' => 'Asset unarchived successfully.']);
return $response;
}
}