WIP: Implement closer BPN integration
This commit is contained in:
@@ -29,3 +29,5 @@ yarn-error.log
|
|||||||
###> liip/imagine-bundle ###
|
###> liip/imagine-bundle ###
|
||||||
/public/media/cache/
|
/public/media/cache/
|
||||||
###< liip/imagine-bundle ###
|
###< liip/imagine-bundle ###
|
||||||
|
|
||||||
|
xmlexport
|
||||||
@@ -42,6 +42,11 @@ services:
|
|||||||
method: onLogout
|
method: onLogout
|
||||||
dispatcher: security.event_dispatcher.main
|
dispatcher: security.event_dispatcher.main
|
||||||
|
|
||||||
|
App\Command\BpnImportCommand:
|
||||||
|
arguments:
|
||||||
|
$connection: '@doctrine.dbal.default_connection'
|
||||||
|
$xmlFilesPath: '%kernel.project_dir%/xmlexport'
|
||||||
|
|
||||||
App\BusProNet\ApiClient:
|
App\BusProNet\ApiClient:
|
||||||
arguments:
|
arguments:
|
||||||
$logger: '@monolog.logger.bpn'
|
$logger: '@monolog.logger.bpn'
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = \'\' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ class ApiClient
|
|||||||
{
|
{
|
||||||
public const TYPE_NOTIFICATION = 'HINWEIS';
|
public const TYPE_NOTIFICATION = 'HINWEIS';
|
||||||
public const TYPE_CUSTOMER_DATA = 'KUNDENKONTO';
|
public const TYPE_CUSTOMER_DATA = 'KUNDENKONTO';
|
||||||
public const TYPE_PRODUCTS = 'PRODUKTE';
|
|
||||||
public const TYPE_BASE_DATA_COUNTRIES = 'STAMMLAENDER';
|
public const TYPE_BASE_DATA_COUNTRIES = 'STAMMLAENDER';
|
||||||
public const TYPE_BASE_DATA_PICKUPS = 'STAMMZUSTIEGE';
|
public const TYPE_BASE_DATA_PICKUPS = 'STAMMZUSTIEGE';
|
||||||
public const TYPE_BASE_DATA_HOTELS = 'STAMMHOTELS';
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ use App\BusProNet\Model\CrmAttributesResponse;
|
|||||||
use App\BusProNet\Model\Hotel;
|
use App\BusProNet\Model\Hotel;
|
||||||
use App\BusProNet\Model\NotificationResponse;
|
use App\BusProNet\Model\NotificationResponse;
|
||||||
use App\BusProNet\Model\Pickup;
|
use App\BusProNet\Model\Pickup;
|
||||||
use App\BusProNet\Model\Product;
|
|
||||||
use App\BusProNet\Model\ProfileResponse;
|
use App\BusProNet\Model\ProfileResponse;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
@@ -59,8 +58,6 @@ class ResponseParser
|
|||||||
return $this->createPickupsResponse($xml);
|
return $this->createPickupsResponse($xml);
|
||||||
case ApiClient::TYPE_BASE_DATA_HOTELS:
|
case ApiClient::TYPE_BASE_DATA_HOTELS:
|
||||||
return $this->createHotelsResponse($xml);
|
return $this->createHotelsResponse($xml);
|
||||||
case ApiClient::TYPE_PRODUCTS:
|
|
||||||
return $this->createProductsResponse($xml);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ResponseParserException('Unable to parse XML response');
|
throw new ResponseParserException('Unable to parse XML response');
|
||||||
@@ -241,28 +238,6 @@ class ResponseParser
|
|||||||
return new BaseDataResponse($hotels);
|
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
|
private function resolveOptions(array $options): array
|
||||||
{
|
{
|
||||||
$optionsResolver = new OptionsResolver();
|
$optionsResolver = new OptionsResolver();
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,9 +27,9 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
||||||
private string $uuid;
|
private string $uuid;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\ManyToOne]
|
||||||
#[Assert\NotNull(message: 'Bitte gib die Destination an')]
|
#[Assert\NotNull(message: 'Bitte gib die Destination an')]
|
||||||
private ?int $destinationBusProId = null;
|
private ?BpnDate $destination = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[Assert\NotNull(message: 'Bitte gib das Jobprofil an')]
|
#[Assert\NotNull(message: 'Bitte gib das Jobprofil an')]
|
||||||
@@ -93,14 +93,14 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
return $this->uuid;
|
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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\Entity\Assignment;
|
use App\Entity\Assignment;
|
||||||
|
use App\Entity\BpnDate;
|
||||||
use App\Entity\Fee;
|
use App\Entity\Fee;
|
||||||
use App\Entity\JobProfile;
|
use App\Entity\JobProfile;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
@@ -25,9 +26,11 @@ class AssignmentType extends AbstractType
|
|||||||
'label' => 'Anzahl',
|
'label' => 'Anzahl',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
->add('destinationBusProId', AutocompleteChoiceType::class, [
|
->add('destination', AutocompleteEntityType::class, [
|
||||||
'label' => 'Destination',
|
'label' => 'Destination',
|
||||||
'endpoint_route' => 'app_admin_autocomplete_hotel',
|
'class' => BpnDate::class,
|
||||||
|
'label_property' => 'product',
|
||||||
|
'endpoint_route' => 'app_admin_autocomplete_bpndate',
|
||||||
])
|
])
|
||||||
->add('jobProfile', EntityType::class, [
|
->add('jobProfile', EntityType::class, [
|
||||||
'label' => 'Jobprofil',
|
'label' => 'Jobprofil',
|
||||||
|
|||||||
@@ -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, '_%');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
{{ form_row(form.jobProfile) }}
|
{{ form_row(form.jobProfile) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
|
<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) }}
|
{{ form_row(form.contact) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
|
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
|
||||||
|
|||||||
@@ -92,24 +92,6 @@ class ResponseParserTest extends TestCase
|
|||||||
$this->assertEquals('Gaststätte', $hotel->getType());
|
$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
|
private function getParserInstance(): ResponseParser
|
||||||
{
|
{
|
||||||
return new ResponseParser([
|
return new ResponseParser([
|
||||||
|
|||||||
Reference in New Issue
Block a user