WIP: Implement closer BPN integration

This commit is contained in:
Björn Fromme
2023-10-06 18:14:05 +02:00
parent bea5edd615
commit 25a90b04aa
23 changed files with 628 additions and 224 deletions
+2
View File
@@ -29,3 +29,5 @@ yarn-error.log
###> liip/imagine-bundle ###
/public/media/cache/
###< liip/imagine-bundle ###
xmlexport
+5
View File
@@ -42,6 +42,11 @@ services:
method: onLogout
dispatcher: security.event_dispatcher.main
App\Command\BpnImportCommand:
arguments:
$connection: '@doctrine.dbal.default_connection'
$xmlFilesPath: '%kernel.project_dir%/xmlexport'
App\BusProNet\ApiClient:
arguments:
$logger: '@monolog.logger.bpn'
+39
View File
@@ -0,0 +1,39 @@
<?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 Version20231006134112 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('CREATE TABLE bpn_date (id INT AUTO_INCREMENT NOT NULL, product_id INT NOT NULL, hotel_id INT NOT NULL, bus_pro_id INT NOT NULL, date_from DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', date_to DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', bus TINYINT(1) NOT NULL, INDEX IDX_18E376D24584665A (product_id), INDEX IDX_18E376D23243BB18 (hotel_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE bpn_hotel (id INT AUTO_INCREMENT NOT NULL, bus_pro_id INT NOT NULL, code VARCHAR(32) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE bpn_product (id INT AUTO_INCREMENT NOT NULL, bus_pro_id INT NOT NULL, name VARCHAR(255) NOT NULL, code VARCHAR(32) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE bpn_date ADD CONSTRAINT FK_18E376D24584665A FOREIGN KEY (product_id) REFERENCES bpn_date (id)');
$this->addSql('ALTER TABLE bpn_date ADD CONSTRAINT FK_18E376D23243BB18 FOREIGN KEY (hotel_id) REFERENCES bpn_hotel (id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE bpn_date DROP FOREIGN KEY FK_18E376D24584665A');
$this->addSql('ALTER TABLE bpn_date DROP FOREIGN KEY FK_18E376D23243BB18');
$this->addSql('DROP TABLE bpn_date');
$this->addSql('DROP TABLE bpn_hotel');
$this->addSql('DROP TABLE bpn_product');
}
}
+39
View File
@@ -0,0 +1,39 @@
<?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 Version20231006144941 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 bpn_date DROP FOREIGN KEY FK_18E376D24584665A');
$this->addSql('ALTER TABLE bpn_date DROP FOREIGN KEY FK_18E376D23243BB18');
$this->addSql('DROP INDEX IDX_18E376D24584665A ON bpn_date');
$this->addSql('DROP INDEX IDX_18E376D23243BB18 ON bpn_date');
$this->addSql('ALTER TABLE bpn_date ADD code VARCHAR(32) NOT NULL, ADD product VARCHAR(255) NOT NULL, ADD hotel VARCHAR(255) NOT NULL, DROP product_id, DROP hotel_id');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE bpn_date ADD product_id INT NOT NULL, ADD hotel_id INT NOT NULL, DROP code, DROP product, DROP hotel');
$this->addSql('ALTER TABLE bpn_date ADD CONSTRAINT FK_18E376D24584665A FOREIGN KEY (product_id) REFERENCES bpn_date (id)');
$this->addSql('ALTER TABLE bpn_date ADD CONSTRAINT FK_18E376D23243BB18 FOREIGN KEY (hotel_id) REFERENCES bpn_hotel (id)');
$this->addSql('CREATE INDEX IDX_18E376D24584665A ON bpn_date (product_id)');
$this->addSql('CREATE INDEX IDX_18E376D23243BB18 ON bpn_date (hotel_id)');
}
}
+33
View File
@@ -0,0 +1,33 @@
<?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 Version20231006145056 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('DROP TABLE bpn_hotel');
$this->addSql('DROP TABLE bpn_product');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE bpn_hotel (id INT AUTO_INCREMENT NOT NULL, bus_pro_id INT NOT NULL, code VARCHAR(32) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, name VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
$this->addSql('CREATE TABLE bpn_product (id INT AUTO_INCREMENT NOT NULL, bus_pro_id INT NOT NULL, name VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, code VARCHAR(32) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
}
}
+31
View File
@@ -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 Version20231006152954 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 bpn_date ADD hotel_code VARCHAR(32) NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE bpn_date DROP hotel_code');
}
}
+31
View File
@@ -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 Version20231006153242 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 bpn_date ADD pickups JSON NOT NULL COMMENT \'(DC2Type:json)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE bpn_date DROP pickups');
}
}
+31
View File
@@ -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 Version20231006155227 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 bpn_date ADD hotel_bus_pro_id INT NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE bpn_date DROP hotel_bus_pro_id');
}
}
-1
View File
@@ -16,7 +16,6 @@ class ApiClient
{
public const TYPE_NOTIFICATION = 'HINWEIS';
public const TYPE_CUSTOMER_DATA = 'KUNDENKONTO';
public const TYPE_PRODUCTS = 'PRODUKTE';
public const TYPE_BASE_DATA_COUNTRIES = 'STAMMLAENDER';
public const TYPE_BASE_DATA_PICKUPS = 'STAMMZUSTIEGE';
public const TYPE_BASE_DATA_HOTELS = 'STAMMHOTELS';
@@ -1,39 +0,0 @@
<?php
namespace App\BusProNet\DataProvider;
use App\BusProNet\ApiClient;
use App\BusProNet\ApiClientException;
use App\BusProNet\Model\Product;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class ProductDataProvider
{
public function __construct(private readonly ApiClient $apiClient, private readonly CacheInterface $cache)
{}
public function getAll(): array
{
try {
$products = $this->cache->get('bpn_products', function (ItemInterface $item) {
$item->expiresAfter(3600);
return $this
->apiClient
->getBaseData(ApiClient::TYPE_PRODUCTS)
->getItems()
;
});
} catch (ApiClientException $e) {
$products = [];
}
return $products;
}
public function get(int $busProId): ?Product
{
return $this->getAll()[$busProId] ?? null;
}
}
-46
View File
@@ -1,46 +0,0 @@
<?php
namespace App\BusProNet\Model;
class Product
{
private ?int $id = null;
private ?string $code = null;
private ?string $name = null;
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): static
{
$this->id = $id;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(?string $code): static
{
$this->code = $code;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): static
{
$this->name = $name;
return $this;
}
}
-25
View File
@@ -12,7 +12,6 @@ use App\BusProNet\Model\CrmAttributesResponse;
use App\BusProNet\Model\Hotel;
use App\BusProNet\Model\NotificationResponse;
use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Product;
use App\BusProNet\Model\ProfileResponse;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -59,8 +58,6 @@ class ResponseParser
return $this->createPickupsResponse($xml);
case ApiClient::TYPE_BASE_DATA_HOTELS:
return $this->createHotelsResponse($xml);
case ApiClient::TYPE_PRODUCTS:
return $this->createProductsResponse($xml);
}
throw new ResponseParserException('Unable to parse XML response');
@@ -241,28 +238,6 @@ class ResponseParser
return new BaseDataResponse($hotels);
}
public function createProductsResponse(\SimpleXMLElement $xml): BaseDataResponse
{
$products = [];
foreach ($xml->xpath('produkte/produkt') as $item) {
$code = (string) $item->attributes()['code'];
if (true === empty($code)) {
continue;
}
$id = (int) $item->attributes()['id'];
$product = new Product();
$product
->setId($id)
->setName((string) $item->attributes()['bezeichnung'])
->setCode($code)
;
$products[$id] = $product;
}
return new BaseDataResponse($products);
}
private function resolveOptions(array $options): array
{
$optionsResolver = new OptionsResolver();
+125
View File
@@ -0,0 +1,125 @@
<?php
namespace App\Command;
use App\BusProNet\DataProvider\HotelDataProvider;
use App\BusProNet\DataProvider\PickupDataProvider;
use Doctrine\DBAL\Connection;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
#[AsCommand(
name: 'app:bpn-import',
description: 'Imports dates, products and hotels from BusProNet',
)]
class BpnImportCommand extends Command
{
public function __construct(
private readonly Connection $connection,
private readonly HotelDataProvider $hotelDataProvider,
private readonly PickupDataProvider $pickupDataProvider,
private readonly string $xmlFilesPath
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// Get all xml files
$xmlFiles = (new Finder())
->name('Ziel_*.xml')
->in($this->xmlFilesPath)
;
$totalCount = count($xmlFiles);
if (0 === $totalCount) {
$io->error('No XML files available or found');
return Command::FAILURE;
}
// Flush dates
$this
->connection
->executeQuery('TRUNCATE TABLE bpn_date')
;
$io->info('Found '.$totalCount.' XML files');
$datesCount = 0;
// Iterate over xml files
foreach ($xmlFiles as $xmlFile) {
// Load xml file into simplexml object
$xml = simplexml_load_file($xmlFile);
// Iterate over all dates
foreach ($xml->xpath('reise/termin') as $dateXml) {
$product = (string) $dateXml->xpath('text')[0];
$busProId = (int) $dateXml->attributes()['idbuspro'];
$code = (string) $dateXml->attributes()['code'];
$dateFrom = \DateTimeImmutable::createFromFormat('d.m.Y', (string) $dateXml->attributes()['termin']);
$dateTo = \DateTimeImmutable::createFromFormat('d.m.Y', (string) $dateXml->attributes()['bis']);
$bus = false;
// Iterate over all service entries and look for 'bus'
foreach ($dateXml->xpath('lei_befoerderung/leistung') as $serviceXml) {
if ('BUS' === (string) $serviceXml->attributes()['unterart']) {
$bus = true;
break;
}
}
// Find pickups
$datePickups = [];
foreach ($dateXml->xpath('zustiege/zustieg') as $pickupXml) {
$pickupBusProId = (int) $pickupXml->attributes()['idbuspro'];
if ($pickupBusProId) {
$pickup = $this->pickupDataProvider->get($pickupBusProId);
$datePickups[] = [
'busProId' => $pickupBusProId,
'city' => $pickup->getCity(),
'street' => $pickup->getStreet(),
'time' => (string) $pickupXml->attributes()['zeit'],
];
}
}
// Iterate over all hotel entries
foreach ($dateXml->xpath('hotel') as $hotelXml) {
$hotelBusProId = (int) $hotelXml->attributes()['idbuspro'];
$hotel = $this->hotelDataProvider->get($hotelBusProId);
// Finally store collected data
$this
->connection
->insert('bpn_date', [
'code' => $code,
'bus_pro_id' => $busProId,
'date_from' => $dateFrom->format('Y-m-d'),
'date_to' => $dateTo->format('Y-m-d'),
'product' => $product,
'hotel' => $hotel->getName(),
'hotel_code' => $hotel->getCode(),
'hotel_bus_pro_id' => $hotelBusProId,
'bus' => (int) $bus,
'pickups' => json_encode($datePickups),
])
;
++$datesCount;
}
}
}
$io->info('Imported '.$datesCount.' dates');
return Command::SUCCESS;
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Controller\Admin\Autocomplete;
use App\Repository\BpnDateRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class BpnDateController extends AbstractController
{
public function __construct(private readonly BpnDateRepository $bpnDateRepository)
{}
#[Route('/admin/autocomplete/bpndate', name: 'app_admin_autocomplete_bpndate')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
public function index(Request $request): JsonResponse
{
try {
$query = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
$queryString = $query['search'];
} catch (\JsonException $e) {
throw $this->createNotFoundException();
}
$dates = $this->bpnDateRepository->getAutocompletionData($queryString);
return $this->json($dates);
}
}
@@ -1,43 +0,0 @@
<?php
namespace App\Controller\Admin\Autocomplete;
use App\BusProNet\DataProvider\HotelDataProvider;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class HotelController extends AbstractController
{
public function __construct(private readonly HotelDataProvider $hotelDataProvider)
{}
#[Route('/admin/autocomplete/hotel', name: 'app_admin_autocomplete_hotel')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
public function index(Request $request): JsonResponse
{
try {
$query = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
$queryString = $query['search'];
} catch (\JsonException $e) {
throw $this->createNotFoundException();
}
$hotels = $this->hotelDataProvider->getAll();
$data = [];
foreach ($hotels as $hotel) {
$name = $hotel->getName();
if (1 === preg_match('/'.preg_quote($queryString, '/').'/i', $name)) {
$data[] = [
'value' => $hotel->getBusProId(),
'text' => sprintf('%s (%s)', $name, $hotel->getCode()),
];
}
}
return $this->json($data);
}
}
@@ -1,43 +0,0 @@
<?php
namespace App\Controller\Admin\Autocomplete;
use App\BusProNet\DataProvider\ProductDataProvider;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class ProductController extends AbstractController
{
public function __construct(private readonly ProductDataProvider $productDataProvider)
{}
#[Route('/admin/autocomplete/product', name: 'app_admin_autocomplete_product')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
public function index(Request $request): JsonResponse
{
try {
$query = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
$queryString = $query['search'];
} catch (\JsonException $e) {
throw $this->createNotFoundException();
}
$products = $this->productDataProvider->getAll();
$data = [];
foreach ($products as $product) {
$name = $product->getName();
if (1 === preg_match('/'.preg_quote($queryString, '/').'/i', $name)) {
$data[] = [
'value' => $product->getId(),
'text' => sprintf('%s (%s)', $name, $product->getCode()),
];
}
}
return $this->json($data);
}
}
+6 -6
View File
@@ -27,9 +27,9 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
#[ORM\Column(type: 'string', length: 36, unique: true)]
private string $uuid;
#[ORM\Column(nullable: true)]
#[ORM\ManyToOne]
#[Assert\NotNull(message: 'Bitte gib die Destination an')]
private ?int $destinationBusProId = null;
private ?BpnDate $destination = null;
#[ORM\ManyToOne]
#[Assert\NotNull(message: 'Bitte gib das Jobprofil an')]
@@ -93,14 +93,14 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
return $this->uuid;
}
public function getDestinationBusProId(): ?int
public function getDestination(): ?BpnDate
{
return $this->destinationBusProId;
return $this->destination;
}
public function setDestinationBusProId(?int $destinationBusProId): static
public function setDestination(?BpnDate $destination): static
{
$this->destinationBusProId = $destinationBusProId;
$this->destination = $destination;
return $this;
}
+176
View File
@@ -0,0 +1,176 @@
<?php
namespace App\Entity;
use App\Repository\BpnDateRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: BpnDateRepository::class)]
class BpnDate
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column]
private ?int $busProId = null;
#[ORM\Column(length: 32)]
private ?string $code = null;
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
private ?\DateTimeImmutable $dateFrom = null;
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
private ?\DateTimeImmutable $dateTo = null;
#[ORM\Column]
private ?bool $bus;
#[ORM\Column(length: 255)]
private ?string $product = null;
#[ORM\Column(length: 255)]
private ?string $hotel = null;
#[ORM\Column(length: 32)]
private ?string $hotelCode = null;
#[ORM\Column]
private ?int $hotelBusProId = null;
#[ORM\Column]
private array $pickups = [];
public function __construct()
{
$this->bus = false;
}
public function getId(): ?int
{
return $this->id;
}
public function getBusProId(): ?int
{
return $this->busProId;
}
public function setBusProId(int $busProId): static
{
$this->busProId = $busProId;
return $this;
}
public function getDateFrom(): ?\DateTimeImmutable
{
return $this->dateFrom;
}
public function setDateFrom(\DateTimeImmutable $dateFrom): static
{
$this->dateFrom = $dateFrom;
return $this;
}
public function getDateTo(): ?\DateTimeImmutable
{
return $this->dateTo;
}
public function setDateTo(\DateTimeImmutable $dateTo): static
{
$this->dateTo = $dateTo;
return $this;
}
public function isBus(): ?bool
{
return $this->bus;
}
public function setBus(bool $bus): static
{
$this->bus = $bus;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): static
{
$this->code = $code;
return $this;
}
public function getProduct(): ?string
{
return $this->product;
}
public function setProduct(string $product): static
{
$this->product = $product;
return $this;
}
public function getHotel(): ?string
{
return $this->hotel;
}
public function setHotel(string $hotel): static
{
$this->hotel = $hotel;
return $this;
}
public function getHotelCode(): ?string
{
return $this->hotelCode;
}
public function setHotelCode(?string $hotelCode): static
{
$this->hotelCode = $hotelCode;
return $this;
}
public function getHotelBusProId(): ?int
{
return $this->hotelBusProId;
}
public function setHotelBusProId(?int $hotelBusProId): static
{
$this->hotelBusProId = $hotelBusProId;
return $this;
}
public function getPickups(): array
{
return $this->pickups;
}
public function setPickups(array $pickups): static
{
$this->pickups = $pickups;
return $this;
}
}
+5 -2
View File
@@ -3,6 +3,7 @@
namespace App\Form;
use App\Entity\Assignment;
use App\Entity\BpnDate;
use App\Entity\Fee;
use App\Entity\JobProfile;
use App\Entity\User;
@@ -25,9 +26,11 @@ class AssignmentType extends AbstractType
'label' => 'Anzahl',
'required' => false,
])
->add('destinationBusProId', AutocompleteChoiceType::class, [
->add('destination', AutocompleteEntityType::class, [
'label' => 'Destination',
'endpoint_route' => 'app_admin_autocomplete_hotel',
'class' => BpnDate::class,
'label_property' => 'product',
'endpoint_route' => 'app_admin_autocomplete_bpndate',
])
->add('jobProfile', EntityType::class, [
'label' => 'Jobprofil',
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace App\Repository;
use App\Entity\BpnDate;
use App\Repository\Traits\QueryHelperTrait;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<BpnDate>
*
* @method BpnDate|null find($id, $lockMode = null, $lockVersion = null)
* @method BpnDate|null findOneBy(array $criteria, array $orderBy = null)
* @method BpnDate[] findAll()
* @method BpnDate[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class BpnDateRepository extends ServiceEntityRepository
{
use QueryHelperTrait;
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, BpnDate::class);
}
public function getAutocompletionData(string $search): array
{
$qb = $this->createQueryBuilder('bpn_date');
$dates = $qb
->select('bpn_date.id', 'bpn_date.product', 'bpn_date.hotel', 'bpn_date.dateFrom', 'bpn_date.dateTo')
->where($qb->expr()->orX(
$qb->expr()->like('bpn_date.product', ':product'),
$qb->expr()->like('bpn_date.hotel', ':hotel')
))
->setParameter('product', '%'.$this->escapeLikeWildcards($search).'%')
->setParameter('hotel', '%'.$this->escapeLikeWildcards($search).'%')
->orderBy('bpn_date.product', 'ASC')
->addOrderBy('bpn_date.dateFrom', 'ASC')
->getQuery()
->getArrayResult()
;
$data = [
[
'value' => '',
'text' => '...',
],
];
foreach ($dates as $date) {
$data[] = [
'value' => $date['id'],
'text' => sprintf('%s (%s - %s)', $date['product'], $date['dateFrom']->format('d.m.y'), $date['dateTo']->format('d.m.y')),
];
}
return $data;
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Repository\Traits;
trait QueryHelperTrait
{
public function escapeLikeWildcards(string $value): string
{
return addcslashes($value, '_%');
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
{{ form_row(form.jobProfile) }}
</div>
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
{{ form_row(form.destinationBusProId) }}
{{ form_row(form.destination) }}
{{ form_row(form.contact) }}
</div>
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
-18
View File
@@ -92,24 +92,6 @@ class ResponseParserTest extends TestCase
$this->assertEquals('Gaststätte', $hotel->getType());
}
public function testParseSuccessfulProductsResponse(): void
{
$content = '<?xml version="1.0" encoding="utf-8" ?><ergebnis><satz typ="PRODUKTE" /><produkte><produkt id="389" code="ANDA" bezeichnung="Andalusien" /><produkt id="483" code="PORTFL" bezeichnung="Portugal -Algarve Flugreise" /><produkt id="490" code="MALL" bezeichnung="Mallorca Flugreise" /></produkte></ergebnis>';
$parser = $this->getParserInstance();
$response = $parser->parseXmlString(ApiClient::TYPE_PRODUCTS, $content);
$this->assertInstanceOf(BaseDataResponse::class, $response);
$products = $response->getItems();
$this->assertCount(3, $products);
$product = reset($products);
$this->assertEquals(389, $product->getId());
$this->assertEquals('ANDA', $product->getCode());
$this->assertEquals('Andalusien', $product->getName());
}
private function getParserInstance(): ResponseParser
{
return new ResponseParser([