associate location on ms calendar remote

This commit is contained in:
Julien Fastré 2022-07-01 12:12:48 +02:00
parent 014e281d13
commit 2a6974610f
18 changed files with 359 additions and 28 deletions

View File

@ -413,6 +413,11 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
return null !== $this->calendarRange;
}
public function hasLocation(): bool
{
return null !== $this->getLocation();
}
/**
* return true if the user is invited.
*/

View File

@ -18,6 +18,8 @@ use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Remove a calendar range when it is removed from local calendar.
*
* @AsMessageHandler
*/
class CalendarRangeRemoveToRemoteHandler implements MessageHandlerInterface

View File

@ -19,6 +19,8 @@ use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Write calendar range creation / update to the remote calendar.
*
* @AsMessageHandler
*/
class CalendarRangeToRemoteHandler implements MessageHandlerInterface
@ -43,6 +45,10 @@ class CalendarRangeToRemoteHandler implements MessageHandlerInterface
{
$range = $this->calendarRangeRepository->find($calendarRangeMessage->getCalendarRangeId());
if (null === $range) {
return;
}
$this->remoteCalendarConnector->syncCalendarRange($range);
$range->preventEnqueueChanges = true;

View File

@ -24,6 +24,8 @@ use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Write calendar creation / update to the remote calendar.
*
* @AsMessageHandler
*/
class CalendarToRemoteHandler implements MessageHandlerInterface
@ -60,6 +62,10 @@ class CalendarToRemoteHandler implements MessageHandlerInterface
{
$calendar = $this->calendarRepository->find($calendarMessage->getCalendarId());
if (null === $calendar) {
return;
}
if (null !== $calendarMessage->getPreviousCalendarRangeId()) {
$previousCalendarRange = $this->calendarRangeRepository
->find($calendarMessage->getPreviousCalendarRangeId());

View File

@ -18,6 +18,8 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Sync the local invitation to the remote calendar.
*
* @AsMessageHandler
*/
class InviteUpdateHandler implements MessageHandlerInterface

View File

@ -23,7 +23,7 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* Handle notification of chagnes from MSGraph.
* Handle notification of changes made by users directly on Outlook calendar.
*
* @AsMessageHandler
*/

View File

@ -0,0 +1,41 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Templating\Entity\AddressRender;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
class AddressConverter
{
private AddressRender $addressRender;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(AddressRender $addressRender, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->addressRender = $addressRender;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addressToRemote(Address $address): array
{
return [
'city' => $address->getPostcode()->getName(),
'postalCode' => $address->getPostcode()->getCode(),
'countryOrRegion' => $this->translatableStringHelper->localize($address->getPostcode()->getCountry()->getName()),
'street' => $address->isNoAddress() ? '' :
implode(', ', $this->addressRender->renderLines($address, false, false)),
'state' => '',
];
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph;
use Chill\MainBundle\Entity\Location;
class LocationConverter
{
private AddressConverter $addressConverter;
public function __construct(AddressConverter $addressConverter)
{
$this->addressConverter = $addressConverter;
}
public function locationToRemote(Location $location): array
{
$results = [];
if ($location->hasAddress()) {
$results['address'] = $this->addressConverter->addressToRemote($location->getAddress());
if ($location->getAddress()->hasAddressReference() && $location->getAddress()->getAddressReference()->hasPoint()) {
$results['coordinates'] = [
'latitude' => $location->getAddress()->getAddressReference()->getPoint()->getLat(),
'longitude' => $location->getAddress()->getAddressReference()->getPoint()->getLon(),
];
}
}
if (null !== $location->getName()) {
$results['displayName'] = $location->getName();
}
return $results;
}
}

View File

@ -19,6 +19,7 @@ use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use DateTimeImmutable;
use DateTimeZone;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@ -35,6 +36,11 @@ class RemoteEventConverter
*/
public const REMOTE_DATETIMEZONE_FORMAT = 'Y-m-d\\TH:i:s.u?P';
/**
* Same as above, but sometimes the date is expressed with only 6 milliseconds.
*/
public const REMOTE_DATETIMEZONE_FORMAT_ALT = 'Y-m-d\\TH:i:s.uP';
private const REMOTE_DATE_FORMAT = 'Y-m-d\TH:i:s.u0';
private const REMOTE_DATETIME_WITHOUT_TZ_FORMAT = 'Y-m-d\TH:i:s.u?';
@ -43,15 +49,26 @@ class RemoteEventConverter
private EngineInterface $engine;
private LocationConverter $locationConverter;
private LoggerInterface $logger;
private PersonRenderInterface $personRender;
private DateTimeZone $remoteDateTimeZone;
private TranslatorInterface $translator;
public function __construct(EngineInterface $engine, PersonRenderInterface $personRender, TranslatorInterface $translator)
{
public function __construct(
EngineInterface $engine,
LocationConverter $locationConverter,
LoggerInterface $logger,
PersonRenderInterface $personRender,
TranslatorInterface $translator
) {
$this->engine = $engine;
$this->locationConverter = $locationConverter;
$this->logger = $logger;
$this->translator = $translator;
$this->personRender = $personRender;
$this->defaultDateTimeZone = (new DateTimeImmutable())->getTimezone();
@ -86,12 +103,13 @@ class RemoteEventConverter
],
],
'isReminderOn' => false,
'location' => $this->locationConverter->locationToRemote($calendarRange->getLocation()),
];
}
public function calendarToEvent(Calendar $calendar): array
{
return array_merge(
$result = array_merge(
[
'subject' => '[Chill] ' .
implode(
@ -120,9 +138,16 @@ class RemoteEventConverter
),
],
'responseRequested' => true,
'isReminderOn' => false,
],
$this->calendarToEventAttendeesOnly($calendar)
);
if ($calendar->hasLocation()) {
$result['location'] = $this->locationConverter->locationToRemote($calendar->getLocation());
}
return $result;
}
public function calendarToEventAttendeesOnly(Calendar $calendar): array
@ -203,7 +228,27 @@ class RemoteEventConverter
public function getLastModifiedDate(array $event): DateTimeImmutable
{
return DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $event['lastModifiedDateTime']);
$date = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $event['lastModifiedDateTime']);
if (false === $date) {
$date = DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT_ALT, $event['lastModifiedDateTime']);
}
if (false === $date) {
$this->logger->error(self::class . ' Could not convert lastModifiedDate', [
'actual' => $event['lastModifiedDateTime'],
'format' => self::REMOTE_DATETIMEZONE_FORMAT,
'format_alt' => self::REMOTE_DATETIMEZONE_FORMAT_ALT,
]);
throw new RuntimeException(sprintf(
'could not convert lastModifiedDate: %s, expected format: %s',
$event['lastModifiedDateTime'],
self::REMOTE_DATETIMEZONE_FORMAT . ' and ' . self::REMOTE_DATETIMEZONE_FORMAT_ALT
));
}
return $date;
}
/**

View File

@ -28,6 +28,11 @@ class RemoteEvent
*/
public string $id;
/**
* @Serializer\Groups({"read"})
*/
public bool $isAllDay;
/**
* @Serializer\Groups({"read"})
*/
@ -38,11 +43,6 @@ class RemoteEvent
*/
public string $title;
/**
* @Serializer\Groups({"read"})
*/
public bool $isAllDay;
public function __construct(string $id, string $title, string $description, DateTimeImmutable $startDate, DateTimeImmutable $endDate, bool $isAllDay = false)
{
$this->id = $id;

View File

@ -157,15 +157,16 @@ export default {
endDateInput.value = null !== range ? datetimeToISO(range.end) : "";
let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange");
calendarRangeInput.value = null !== range ? Number(range.extendedProps.calendarRangeId) : "";
let location = getters.getLocationById(range.extendedProps.locationId);
if (null === location) {
console.error("location not found!", range.extendedProps.locationId);
}
dispatch('updateLocation', location);
if (null !== range) {
let location = getters.getLocationById(range.extendedProps.locationId);
if (null === location) {
console.error("location not found!", range.extendedProps.locationId);
}
dispatch('updateLocation', location);
const userId = range.extendedProps.userId;
if (state.activity.mainUser !== null && state.activity.mainUser.id !== userId) {
dispatch('setMainUser', state.usersData.get(userId).user);

View File

@ -1,7 +1,7 @@
<template>
<teleport to="body">
<Teleport to="body">
<modal v-if="showModal"
@close="showModal = false">
@close="closeModal">
<template v-slot:header>
<h3>{{ 'Modifier le lieu' }}</h3>
@ -20,7 +20,7 @@
</template>
</modal>
</teleport>
</Teleport>
</template>
<script setup lang="ts">
@ -59,6 +59,10 @@ const saveAndClose = function(e: Event): void {
.then(_ => {showModal.value = false;})
}
const closeModal = function(_: any): void {
showModal.value = false;
}
defineExpose({startEdit});
</script>

View File

@ -0,0 +1,67 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Tests\RemoteCalendar\Connector\MSGraph;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\AddressConverter;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Templating\Entity\AddressRender;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Templating\EngineInterface;
/**
* @internal
* @coversNothing
*/
final class AddressConverterTest extends TestCase
{
use ProphecyTrait;
public function testConvertAddress()
{
$country = (new Country())->setName(['fr' => 'Belgique']);
$postalCode = (new PostalCode())->setName('Houte-Si-Plout')->setCode('4122')
->setCountry($country);
$address = (new Address())->setPostcode($postalCode)->setStreet("Rue de l'Église")
->setStreetNumber('15B')->setBuildingName('Résidence de la Truite');
$actual = $this->buildAddressConverter()->addressToRemote($address);
$this->assertArrayHasKey('city', $actual);
$this->assertStringContainsString($actual['city'], 'Houte-Si-Plout');
$this->assertArrayHasKey('postalCode', $actual);
$this->assertStringContainsString($actual['postalCode'], '4122');
$this->assertArrayHasKey('countryOrRegion', $actual);
$this->assertStringContainsString('Belgique', $actual['countryOrRegion']);
$this->assertArrayHasKey('street', $actual);
$this->assertStringContainsString('Rue de l\'Église', $actual['street']);
$this->assertStringContainsString('15B', $actual['street']);
$this->assertStringContainsString('Résidence de la Truite', $actual['street']);
}
private function buildAddressConverter(): AddressConverter
{
$engine = $this->prophesize(EngineInterface::class);
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
$translatableStringHelper->localize(Argument::type('array'))->will(static function ($args): string {
return ($args[0] ?? ['fr' => 'not provided'])['fr'] ?? 'not provided';
});
$addressRender = new AddressRender($engine->reveal(), $translatableStringHelper->reveal());
return new AddressConverter($addressRender, $translatableStringHelper->reveal());
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Tests\RemoteCalendar\Connector\MSGraph;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\AddressConverter;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\LocationConverter;
use Chill\MainBundle\Doctrine\Model\Point;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Entity\Location;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
/**
* @internal
* @coversNothing
*/
final class LocationConverterTest extends TestCase
{
use ProphecyTrait;
public function testConvertToRemoteWithAddressWithoutPoint(): void
{
$location = (new Location())->setName('display')->setAddress($address = new Address());
$address->setAddressReference($reference = new AddressReference());
$actual = $this->buildLocationConverter()->locationToRemote($location);
$this->assertArrayHasKey('address', $actual);
$this->assertArrayNotHasKey('coordinates', $actual);
$this->assertArrayHasKey('displayName', $actual);
$this->assertEquals('display', $actual['displayName']);
}
public function testConvertToRemoteWithAddressWithPoint(): void
{
$location = (new Location())->setName('display')->setAddress($address = new Address());
$address->setAddressReference($reference = new AddressReference());
$reference->setPoint($point = Point::fromLonLat(5.3134, 50.3134));
$actual = $this->buildLocationConverter()->locationToRemote($location);
$this->assertArrayHasKey('address', $actual);
$this->assertArrayHasKey('coordinates', $actual);
$this->assertEquals(['latitude' => 50.3134, 'longitude' => 5.3134], $actual['coordinates']);
$this->assertArrayHasKey('displayName', $actual);
$this->assertEquals('display', $actual['displayName']);
}
public function testConvertToRemoteWithoutAddressWithoutPoint(): void
{
$location = (new Location())->setName('display');
$actual = $this->buildLocationConverter()->locationToRemote($location);
$this->assertArrayNotHasKey('address', $actual);
$this->assertArrayNotHasKey('coordinates', $actual);
$this->assertArrayHasKey('displayName', $actual);
$this->assertEquals('display', $actual['displayName']);
}
private function buildLocationConverter(): LocationConverter
{
$addressConverter = $this->prophesize(AddressConverter::class);
$addressConverter->addressToRemote(Argument::type(Address::class))->willReturn(['street' => 'dummy']);
return new LocationConverter($addressConverter->reveal());
}
}

View File

@ -359,6 +359,11 @@ class Address
return $this->validTo;
}
public function hasAddressReference(): bool
{
return null !== $this->getAddressReference();
}
public function isNoAddress(): bool
{
return $this->getIsNoAddress();

View File

@ -168,6 +168,11 @@ class AddressReference
return $this->updatedAt;
}
public function hasPoint(): bool
{
return null !== $this->getPoint();
}
public function setCreatedAt(?DateTimeImmutable $createdAt): self
{
$this->createdAt = $createdAt;

View File

@ -180,6 +180,11 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
return $this->updatedBy;
}
public function hasAddress(): bool
{
return null !== $this->getAddress();
}
public function setActive(bool $active): self
{
$this->active = $active;

View File

@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Entity;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Component\Templating\EngineInterface;
use function array_merge;
@ -33,9 +33,9 @@ class AddressRender implements ChillEntityRenderInterface
private EngineInterface $templating;
private TranslatableStringHelper $translatableStringHelper;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(EngineInterface $templating, TranslatableStringHelper $translatableStringHelper)
public function __construct(EngineInterface $templating, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->templating = $templating;
$this->translatableStringHelper = $translatableStringHelper;
@ -65,7 +65,7 @@ class AddressRender implements ChillEntityRenderInterface
*
* @return string[]
*/
public function renderLines($addr): array
public function renderLines(Address $addr, bool $includeCityLine = true, bool $includeCountry = true): array
{
$lines = [];
@ -75,14 +75,26 @@ class AddressRender implements ChillEntityRenderInterface
$lines[] = $this->renderBuildingLine($addr);
$lines[] = $this->renderStreetLine($addr);
$lines[] = $this->renderDeliveryLine($addr);
$lines[] = $this->renderCityLine($addr);
$lines[] = $this->renderCountryLine($addr);
if ($includeCityLine) {
$lines[] = $this->renderCityLine($addr);
}
if ($includeCountry) {
$lines[] = $this->renderCountryLine($addr);
}
} else {
$lines[] = $this->renderBuildingLine($addr);
$lines[] = $this->renderDeliveryLine($addr);
$lines[] = $this->renderStreetLine($addr);
$lines[] = $this->renderCityLine($addr);
$lines[] = $this->renderCountryLine($addr);
if ($includeCityLine) {
$lines[] = $this->renderCityLine($addr);
}
if ($includeCountry) {
$lines[] = $this->renderCountryLine($addr);
}
}
}