Skip to content

Commit

Permalink
Merge pull request #4916 from coopcycle/import-deliveries-in-tour
Browse files Browse the repository at this point in the history
Add "tour.name" field in delivery import to automatically add delivery to a tour
  • Loading branch information
Atala authored Mar 6, 2025
2 parents a96a964 + 261f85e commit dd41169
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 23 deletions.
2 changes: 2 additions & 0 deletions app/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@ services:

AppBundle\Entity\TaskListRepository: ~

AppBundle\Entity\TourRepository: ~

AppBundle\Entity\Task\RecurrenceRuleRepository: ~

AppBundle\Service\Routing\Osrm: ~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __construct(EntityManagerInterface $objectManager)
$this->objectManager = $objectManager;
}

public function getTaskList(Task $task, UserInterface $courier)
public function getTaskList(Task $task, UserInterface $courier) : TaskList
{
// FIXME
// 1. if task->assignedOn is set, we have explictly set the assignment date -> good, we get the proper TaskList
Expand Down
9 changes: 8 additions & 1 deletion src/Entity/Listener/DeliveryListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
use AppBundle\Entity\Delivery;
use AppBundle\Entity\Task;
use AppBundle\Entity\TaskList\Item;
use AppBundle\Entity\TourRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\Event\LifecycleEventArgs;

class DeliveryListener
{
public function __construct(
protected EntityManagerInterface $entityManager,
protected TaskListProvider $taskListProvider
protected TaskListProvider $taskListProvider,
protected TourRepository $tourRepository
)
{

Expand Down Expand Up @@ -69,6 +71,11 @@ public function postPersist(Delivery $delivery)
$courier = $delivery->getStore()->getDefaultCourier();

foreach ($delivery->getTasks() as $task) {
// Ignore tasks that are part of a tour, that is the tour itself that should be assigned
if ($this->tourRepository->findOneByTask($task)) {
continue;
}

$taskList = $this->taskListProvider->getTaskList($task, $courier);
$taskList->appendTask($task);
}
Expand Down
26 changes: 23 additions & 3 deletions src/Entity/TourRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@

use AppBundle\Entity\Task;
use AppBundle\Entity\TaskCollectionItem;
use Doctrine\ORM\EntityRepository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\Persistence\ManagerRegistry;

class TourRepository extends EntityRepository
class TourRepository extends ServiceEntityRepository
{
public function findOneByTask(Task $task)
public function __construct(
ManagerRegistry $registry
)
{
parent::__construct($registry, Tour::class);
}

public function findOneByTask(Task $task): ?Tour
{
return $this->createQueryBuilder('to')
->join(TaskCollectionItem::class, 'tci', Expr\Join::WITH, 'tci.parent = to.id')
Expand All @@ -18,4 +27,15 @@ public function findOneByTask(Task $task)
->getQuery()
->getOneOrNullResult();
}

public function findByNameAndDate(string $name, \DateTime $date): ?Tour
{
return $this->createQueryBuilder('to')
->where('to.name = :name')
->andWhere('to.date = :date')
->setParameter('name', $name)
->setParameter('date', $date)
->getQuery()
->getOneOrNullResult();
}
}
28 changes: 26 additions & 2 deletions src/MessageHandler/ImportDeliveriesHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use AppBundle\Entity\Store;
use AppBundle\Entity\Delivery\ImportQueue as DeliveryImportQueue;
use AppBundle\Entity\Sylius\UsePricingRules;
use AppBundle\Entity\Tour;
use AppBundle\Entity\TourRepository;
use AppBundle\Exception\Pricing\NoRuleMatchedException;
use AppBundle\Message\ImportDeliveries;
use AppBundle\Pricing\PricingManager;
Expand All @@ -32,7 +34,9 @@ public function __construct(
private PricingManager $pricingManager,
private LiveUpdates $liveUpdates,
private DeliveryManager $deliveryManager,
private LoggerInterface $logger)
private LoggerInterface $logger,
private TourRepository $tourRepository
)
{
}

Expand Down Expand Up @@ -65,7 +69,9 @@ public function __invoke(ImportDeliveries $message)

$result = $this->spreadsheetParser->parse($tempnam, $message->getOptions());

foreach ($result->getData() as $rowNumber => $delivery) {
foreach ($result->getData() as $rowNumber => $deliveryImportData) {

$delivery = $deliveryImportData['delivery'];

// Validate data
$violations = $this->validator->validate($delivery);
Expand Down Expand Up @@ -95,6 +101,24 @@ public function __invoke(ImportDeliveries $message)
$errorMessage = $this->translator->trans('delivery.price.error.priceCalculation', [], 'validators');
$result->addErrorToRow($rowNumber, $errorMessage);
}

if ($deliveryImportData['tourName']) {
foreach ($delivery->getTasks() as $task) {
$tourName = $deliveryImportData['tourName'];
$date = $task->getAfter();
$tour = $this->tourRepository->findByNameAndDate($tourName, $date);

if (is_null($tour)) {
$tour = new Tour();
$tour->setName($tourName);
$tour->setDate($date);
$this->entityManager->persist($tour);
$this->entityManager->flush();
}

$tour->addTask($task);
}
}
}

$this->entityManager->flush();
Expand Down
16 changes: 11 additions & 5 deletions src/Spreadsheet/DeliverySpreadsheetParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ public function __construct(
private SlugifyInterface $slugify,
private TranslatorInterface $translator,
private SettingsManager $settingsManager
)
{ }
) {}

/**
* @inheritdoc
Expand Down Expand Up @@ -147,8 +146,13 @@ public function parseData(array $data, array $options = []): SpreadsheetParseRes
$this->applyTags($delivery->getDropoff(), $record['dropoff.tags']);
}

$tourName = isset($record['tour.name']) && !empty($record['tour.name']) ? $record['tour.name'] : null;

if (!$parseResult->rowHasErrors($rowNumber)) {
$parseResult->addData($rowNumber, $delivery);
$parseResult->addData(
$rowNumber,
['delivery' => $delivery, 'tourName' => $tourName]
);
}

}
Expand Down Expand Up @@ -262,7 +266,8 @@ public function getExampleData(): array
'dropoff.packages' => 'small-box=1 big-box=2',
'dropoff.tags' => 'warn heavy',
'dropoff.metadata' => 'external_system_id=10',
'weight' => '5.5'
'weight' => '5.5',
'tour.name' => 'my tour name'
],
[
'pickup.address' => '24 rue de rivoli paris',
Expand All @@ -280,7 +285,8 @@ public function getExampleData(): array
'dropoff.timeslot' => '2019-12-12 12:00 - 2019-12-12 13:00',
'dropoff.packages' => 'small-box=1 big-box=2',
'dropoff.tags' => 'warn',
'weight' => '8.0'
'weight' => '8.0',
'tour.name' => 'another tour name'
],
];
}
Expand Down
2 changes: 1 addition & 1 deletion src/Spreadsheet/SpreadsheetParseResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* A class to keep the relation between a row from a file
* and the entity that is created or the errors that occurs
* when the import and parse of that fail happens.
* during parsing.
*/
class SpreadsheetParseResult
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pickup.address,pickup.address.name,pickup.address.description,pickup.address.telephone,pickup.comments,pickup.timeslot,dropoff.address,dropoff.address.name,dropoff.address.description,dropoff.address.telephone,dropoff.comments,dropoff.timeslot,dropoff.packages,weight,pickup.metadata,dropoff.metadata,tour.name
"Via Pippo Spano 17, 50129, Firenze",,,,,16/01/2024 09:00 - 16/01/2024 18:00,"Via Pippo Spano 17, 50129, Firenze",,,, ,16/01/2024 09:00 - 16/01/2024 18:00,,,blu=bla foo=fly,foo=bar,test route
"Via Pippo Spano 17, 50129, Firenze",,,,,16/01/2024 09:00 - 16/01/2024 18:00,"Via Pippo Spano 17, 50129, Firenze",,,, ,16/01/2024 09:00 - 16/01/2024 18:00,,,blu=bla foo=fly,foo=bar,test route
32 changes: 22 additions & 10 deletions tests/AppBundle/Spreadsheet/DeliverySpreadsheetParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@
use AppBundle\Spreadsheet\AbstractSpreadsheetParser;
use AppBundle\Spreadsheet\DeliverySpreadsheetParser;
use Cocur\Slugify\SlugifyInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityManager;
use Doctrine\Persistence\ObjectRepository;
use Exception;
use Prophecy\Argument;
use libphonenumber\PhoneNumberUtil;
use Symfony\Contracts\Translation\TranslatorInterface;
use TypeError;

class DeliverySpreadsheetParserTest extends TestCase
{
protected $settingManager;
protected $geocoder;

protected function createParser(): AbstractSpreadsheetParser
{
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
$this->entityManager = $this->prophesize(EntityManager::class);
$this->slugify = $this->prophesize(SlugifyInterface::class);
$this->settingManager = $this->prophesize(SettingsManager::class);

Expand Down Expand Up @@ -56,9 +58,7 @@ protected function createParser(): AbstractSpreadsheetParser

$this->packageRepository = $this->prophesize(ObjectRepository::class);

$this->entityManager
->getRepository(Package::class)
->willReturn($this->packageRepository->reveal());
$this->entityManager->getRepository(Package::class)->willReturn($this->packageRepository->reveal());

return new DeliverySpreadsheetParser(
$this->geocoder->reveal(),
Expand All @@ -80,7 +80,7 @@ public function testCsvWithEmptyLines()
$this->geocoder->geocode(null)->shouldNotBeCalled(); // not called with empty lines

/** @var Delivery */
$delivery = array_shift($data);
$delivery = array_shift($data)['delivery'];
$this->assertEquals($delivery->getPickup()->getAddress()->getStreetAddress(), 'street address');
$this->assertEquals($delivery->getDropoff()->getAddress()->getStreetAddress(), 'street address');

Expand All @@ -93,7 +93,7 @@ public function testWithInvalidPickupAddressWithCreateFlag()
$data = $parseResult->getData();

/** @var Delivery */
$delivery = array_shift($data);
$delivery = array_shift($data)['delivery'];
$this->assertEquals($delivery->getPickup()->getAddress()->getStreetAddress(), 'INVALID ADDRESS');
$this->assertEquals($delivery->getPickup()->getAddress()->getGeo()->getLatitude(), 48.8534);
$this->assertEquals($delivery->getPickup()->getAddress()->getGeo()->getLongitude(), 2.3488);
Expand Down Expand Up @@ -124,7 +124,7 @@ public function testWithAddressThatThrowsAndCreateDeliveryAnyway()
$data = $parseResult->getData();

/** @var Delivery */
$delivery = array_shift($data);
$delivery = array_shift($data)['delivery'];

$this->assertEquals($delivery->getDropoff()->getAddress()->getGeo()->getLatitude(), 48.8534);
$this->assertEquals($delivery->getDropoff()->getAddress()->getGeo()->getLongitude(), 2.3488);
Expand Down Expand Up @@ -153,9 +153,21 @@ public function testWithMetadata()
$data = $parseResult->getData();

/** @var Delivery */
$delivery = array_shift($data);
$delivery = array_shift($data)['delivery'];
$this->assertEquals($delivery->getPickup()->getMetadata()['foo'], 'fly');
$this->assertEquals($delivery->getPickup()->getMetadata()['blu'], 'bla');
$this->assertEquals($delivery->getDropoff()->getMetadata()['foo'], 'bar');
}

public function testWithTour()
{
$filename = realpath(__DIR__ . '/../Resources/spreadsheet/deliveries_with_tour.csv');

$parseResult = $this->parser->parse($filename);
$data = $parseResult->getData();

/** @var Delivery */
$result = array_shift($data);
$this->assertEquals($result['tourName'], 'test route');
}
}

0 comments on commit dd41169

Please sign in to comment.