WIP: Extend and improve profile editing
This commit is contained in:
@@ -1,15 +1,12 @@
|
|||||||
import { Controller } from '@hotwired/stimulus'
|
import { Controller } from '@hotwired/stimulus'
|
||||||
import { useFetch } from '../mixins/use_fetch'
|
import { useFetch } from '../mixins/use_fetch'
|
||||||
import Dropzone from 'dropzone'
|
import Dropzone from 'dropzone'
|
||||||
import 'dropzone/dist/dropzone.css'
|
|
||||||
Dropzone.autoDiscover = false
|
Dropzone.autoDiscover = false
|
||||||
|
|
||||||
/* stimulusFetch: 'lazy' */
|
/* stimulusFetch: 'lazy' */
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
|
|
||||||
static targets = [
|
static targets = ['dropzone', 'errors', 'previewTemplate']
|
||||||
'dropzone',
|
|
||||||
]
|
|
||||||
|
|
||||||
static values = {
|
static values = {
|
||||||
endpointUpload: String,
|
endpointUpload: String,
|
||||||
@@ -19,6 +16,7 @@ export default class extends Controller {
|
|||||||
acceptedFiles: String,
|
acceptedFiles: String,
|
||||||
chunking: Boolean,
|
chunking: Boolean,
|
||||||
params: Object,
|
params: Object,
|
||||||
|
language: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
connect () {
|
connect () {
|
||||||
@@ -30,14 +28,26 @@ export default class extends Controller {
|
|||||||
maxFiles: this.maxFilesValue,
|
maxFiles: this.maxFilesValue,
|
||||||
maxFilesize: this.maxFilesizeValue,
|
maxFilesize: this.maxFilesizeValue,
|
||||||
acceptedFiles: this.acceptedFilesValue,
|
acceptedFiles: this.acceptedFilesValue,
|
||||||
chunking: this.chunkingValue,
|
chunking: true,
|
||||||
chunkSize: 10000000,
|
chunkSize: 10000000,
|
||||||
parallelUploads: 3,
|
parallelUploads: 3,
|
||||||
retryChunks: true,
|
retryChunks: true,
|
||||||
disablePreviews: false,
|
disablePreviews: false,
|
||||||
|
previewTemplate: this.previewTemplateTarget.innerHTML,
|
||||||
createImageThumbnails: true,
|
createImageThumbnails: true,
|
||||||
autoProcessQueue: true,
|
autoProcessQueue: true,
|
||||||
addRemoveLinks: true,
|
addRemoveLinks: false,
|
||||||
|
dictFileTooBig: this.languageValue.fileTooBig,
|
||||||
|
dictInvalidFileType: this.languageValue.invalidFileType,
|
||||||
|
dictMaxFilesExceeded: this.languageValue.maxFilesExceeded,
|
||||||
|
})
|
||||||
|
|
||||||
|
dropzone.on('addedfile', file => {
|
||||||
|
this.errorsTarget.classList.add('hidden')
|
||||||
|
})
|
||||||
|
|
||||||
|
dropzone.on('removedfile', (file) => {
|
||||||
|
fetch(`${this.endpointDeleteValue}?uuid=${file.upload.uuid}`, { method: 'delete', credentials: 'include' })
|
||||||
})
|
})
|
||||||
|
|
||||||
dropzone.on('sending', (file, xhr, formData) => {
|
dropzone.on('sending', (file, xhr, formData) => {
|
||||||
@@ -56,8 +66,10 @@ export default class extends Controller {
|
|||||||
window.dispatchEvent(new CustomEvent('dropzone:complete'))
|
window.dispatchEvent(new CustomEvent('dropzone:complete'))
|
||||||
})
|
})
|
||||||
|
|
||||||
dropzone.on('removedfile', (file) => {
|
dropzone.on('error', (file, errorMessage) => {
|
||||||
fetch(`${this.endpointDeleteValue}?uuid=${file.upload.uuid}`, { method: 'delete', credentials: 'include' })
|
this.errorsTarget.innerText = errorMessage
|
||||||
|
this.errorsTarget.classList.remove('hidden')
|
||||||
|
dropzone.removeFile(file)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?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 Version20230930150355 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 disposition_requirement DROP FOREIGN KEY FK_F3C6374CC26E160B');
|
||||||
|
$this->addSql('ALTER TABLE assignment_pickup DROP FOREIGN KEY FK_79BF19E0C26E160B');
|
||||||
|
$this->addSql('ALTER TABLE assignment_pickup DROP FOREIGN KEY FK_79BF19E0D19302F8');
|
||||||
|
$this->addSql('DROP TABLE pickup');
|
||||||
|
$this->addSql('DROP TABLE assignment_pickup');
|
||||||
|
$this->addSql('DROP INDEX IDX_F3C6374CC26E160B ON disposition_requirement');
|
||||||
|
$this->addSql('ALTER TABLE disposition_requirement DROP pickup_id');
|
||||||
|
$this->addSql('ALTER TABLE job_profile CHANGE required_licenses required_licenses JSON NOT NULL COMMENT \'(DC2Type:json)\'');
|
||||||
|
$this->addSql('ALTER TABLE teamer ADD pickups JSON NOT NULL COMMENT \'(DC2Type:json)\'');
|
||||||
|
$this->addSql('ALTER TABLE user CHANGE roles roles 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('CREATE TABLE pickup (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, bus_pro_id INT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||||
|
$this->addSql('CREATE TABLE assignment_pickup (assignment_id INT NOT NULL, pickup_id INT NOT NULL, INDEX IDX_79BF19E0D19302F8 (assignment_id), INDEX IDX_79BF19E0C26E160B (pickup_id), PRIMARY KEY(assignment_id, pickup_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||||
|
$this->addSql('ALTER TABLE assignment_pickup ADD CONSTRAINT FK_79BF19E0C26E160B FOREIGN KEY (pickup_id) REFERENCES pickup (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE assignment_pickup ADD CONSTRAINT FK_79BF19E0D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE user CHANGE roles roles JSON NOT NULL COMMENT \'(DC2Type:json)\'');
|
||||||
|
$this->addSql('ALTER TABLE teamer DROP pickups');
|
||||||
|
$this->addSql('ALTER TABLE disposition_requirement ADD pickup_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE disposition_requirement ADD CONSTRAINT FK_F3C6374CC26E160B FOREIGN KEY (pickup_id) REFERENCES pickup (id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_F3C6374CC26E160B ON disposition_requirement (pickup_id)');
|
||||||
|
$this->addSql('ALTER TABLE job_profile CHANGE required_licenses required_licenses JSON NOT NULL COMMENT \'(DC2Type:json)\'');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 Version20230930150821 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 teamer ADD language VARCHAR(255) NOT NULL, ADD size VARCHAR(2) NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE teamer DROP language, DROP size');
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "html",
|
"name": "myep-team",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
+68
-156
@@ -2,24 +2,29 @@
|
|||||||
|
|
||||||
namespace App\BusProNet;
|
namespace App\BusProNet;
|
||||||
|
|
||||||
use App\BusProNet\Model\BaseResponse;
|
use App\BusProNet\Model\BaseDataResponse;
|
||||||
|
use App\BusProNet\Model\CrmAttributesResponse;
|
||||||
|
use App\BusProNet\Model\NotificationResponse;
|
||||||
|
use App\BusProNet\Model\ProfileResponse;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
use Symfony\Contracts\Cache\CacheInterface;
|
|
||||||
use Symfony\Contracts\Cache\ItemInterface;
|
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
class ApiClient
|
class ApiClient
|
||||||
{
|
{
|
||||||
|
public const TYPE_NOTIFICATION = 'HINWEIS';
|
||||||
|
public const TYPE_CUSTOMER_DATA = 'KUNDENKONTO';
|
||||||
|
public const TYPE_BASE_DATA_COUNTRIES = 'STAMMLAENDER';
|
||||||
|
public const TYPE_BASE_DATA_PICKUPS = 'STAMMZUSTIEGE';
|
||||||
|
|
||||||
private array $config;
|
private array $config;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly HttpClientInterface $httpClient,
|
private readonly HttpClientInterface $httpClient,
|
||||||
private readonly SerializerInterface $serializer,
|
private readonly SerializerInterface $serializer,
|
||||||
private readonly ResponseParser $responseParser,
|
private readonly ResponseParser $responseParser,
|
||||||
private readonly CacheInterface $cache,
|
|
||||||
private readonly LoggerInterface $logger,
|
private readonly LoggerInterface $logger,
|
||||||
array $options
|
array $options
|
||||||
) {
|
) {
|
||||||
@@ -29,41 +34,26 @@ class ApiClient
|
|||||||
/**
|
/**
|
||||||
* @throws ApiClientException
|
* @throws ApiClientException
|
||||||
*/
|
*/
|
||||||
public function getProfile(string $email, string $password): BaseResponse
|
public function getProfile(string $email, string $password): ProfileResponse
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'anfrage' => [
|
'anfrage' => [
|
||||||
'user' => $this->config['bpn_username'],
|
'user' => $this->config['bpn_username'],
|
||||||
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], 'KUNDENKONTO'),
|
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
|
||||||
'satz' => ['@typ' => 'KUNDENKONTO'],
|
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
|
||||||
'art' => 'Adressdaten',
|
'art' => 'Adressdaten',
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
'passwort' => md5($password),
|
'passwort' => md5($password),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$body = $this
|
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
|
||||||
->serializer
|
|
||||||
->serialize($data, 'xml');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = $this->httpClient->request('GET', $this->config['bpn_url'], [
|
|
||||||
'query' => [
|
|
||||||
'operation' => $body,
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$xml = $response->getContent();
|
|
||||||
|
|
||||||
return $this->responseParser->parseXmlString($xml);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
|
||||||
throw new ApiClientException($e->getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateProfile(User $user, string $password): BaseResponse
|
/**
|
||||||
|
* @throws ApiClientException
|
||||||
|
*/
|
||||||
|
public function updateProfile(User $user, string $password): ProfileResponse
|
||||||
{
|
{
|
||||||
if (null === $teamer = $user->getTeamer()) {
|
if (null === $teamer = $user->getTeamer()) {
|
||||||
throw new ApiClientException('Invalid argument');
|
throw new ApiClientException('Invalid argument');
|
||||||
@@ -72,8 +62,8 @@ class ApiClient
|
|||||||
$data = [
|
$data = [
|
||||||
'anfrage' => [
|
'anfrage' => [
|
||||||
'user' => $this->config['bpn_username'],
|
'user' => $this->config['bpn_username'],
|
||||||
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], 'KUNDENKONTO'),
|
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
|
||||||
'satz' => ['@typ' => 'KUNDENKONTO'],
|
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
|
||||||
'art' => 'Adressdaten_Ändern',
|
'art' => 'Adressdaten_Ändern',
|
||||||
'email' => $user->getEmail(),
|
'email' => $user->getEmail(),
|
||||||
'passwort' => md5($password),
|
'passwort' => md5($password),
|
||||||
@@ -82,79 +72,71 @@ class ApiClient
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$body = $this
|
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
|
||||||
->serializer
|
|
||||||
->serialize($data, 'xml');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = $this->httpClient->request('GET', $this->config['bpn_url'], [
|
|
||||||
'query' => [
|
|
||||||
'operation' => $body,
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$xml = $response->getContent();
|
|
||||||
|
|
||||||
return $this->responseParser->parseXmlString($xml);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
|
||||||
throw new ApiClientException($e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetPassword(string $email): BaseResponse
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'anfrage' => [
|
|
||||||
'user' => $this->config['bpn_username'],
|
|
||||||
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], 'KUNDENKONTO'),
|
|
||||||
'satz' => ['@typ' => 'KUNDENKONTO'],
|
|
||||||
'art' => 'Passwort_Anfrage',
|
|
||||||
'email' => $email,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$body = $this
|
|
||||||
->serializer
|
|
||||||
->serialize($data, 'xml');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = $this->httpClient->request('GET', $this->config['bpn_url'], [
|
|
||||||
'query' => [
|
|
||||||
'operation' => $body,
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$xml = $response->getContent();
|
|
||||||
|
|
||||||
return $this->responseParser->parseXmlString($xml);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
|
||||||
throw new ApiClientException($e->getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws ApiClientException
|
* @throws ApiClientException
|
||||||
*/
|
*/
|
||||||
public function getCrmAttributes(string $email, string $password): BaseResponse
|
public function resetPassword(string $email): NotificationResponse
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'anfrage' => [
|
'anfrage' => [
|
||||||
'user' => $this->config['bpn_username'],
|
'user' => $this->config['bpn_username'],
|
||||||
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], 'KUNDENKONTO'),
|
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
|
||||||
'satz' => ['@typ' => 'KUNDENKONTO'],
|
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
|
||||||
|
'art' => 'Passwort_Anfrage',
|
||||||
|
'email' => $email,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ApiClientException
|
||||||
|
*/
|
||||||
|
public function getCrmAttributes(string $email, string $password): CrmAttributesResponse
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'anfrage' => [
|
||||||
|
'user' => $this->config['bpn_username'],
|
||||||
|
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
|
||||||
|
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
|
||||||
'art' => 'SelektionCRM',
|
'art' => 'SelektionCRM',
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
'passwort' => md5($password),
|
'passwort' => md5($password),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ApiClientException
|
||||||
|
*/
|
||||||
|
public function getBaseData(string $type): BaseDataResponse
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'anfrage' => [
|
||||||
|
'user' => $this->config['bpn_username'],
|
||||||
|
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], $type),
|
||||||
|
'satz' => ['@typ' => $type],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->sendRequest($type, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ApiClientException
|
||||||
|
*/
|
||||||
|
private function sendRequest(string $type, array $data): mixed
|
||||||
|
{
|
||||||
$body = $this
|
$body = $this
|
||||||
->serializer
|
->serializer
|
||||||
->serialize($data, 'xml');
|
->serialize($data, 'xml')
|
||||||
|
;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = $this->httpClient->request('GET', $this->config['bpn_url'], [
|
$response = $this->httpClient->request('GET', $this->config['bpn_url'], [
|
||||||
@@ -165,7 +147,7 @@ class ApiClient
|
|||||||
|
|
||||||
$xml = $response->getContent();
|
$xml = $response->getContent();
|
||||||
|
|
||||||
return $this->responseParser->parseXmlString($xml);
|
return $this->responseParser->parseXmlString($type, $xml);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,76 +155,6 @@ class ApiClient
|
|||||||
throw new ApiClientException($e->getMessage());
|
throw new ApiClientException($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCountries(): BaseResponse
|
|
||||||
{
|
|
||||||
return $this->cache->get('bpn_countries', function (ItemInterface $item) {
|
|
||||||
$item->expiresAfter(3600);
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'anfrage' => [
|
|
||||||
'user' => $this->config['bpn_username'],
|
|
||||||
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], 'STAMMLAENDER'),
|
|
||||||
'satz' => ['@typ' => 'STAMMLAENDER'],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$body = $this
|
|
||||||
->serializer
|
|
||||||
->serialize($data, 'xml');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = $this->httpClient->request('GET', $this->config['bpn_url'], [
|
|
||||||
'query' => [
|
|
||||||
'operation' => $body,
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$xml = $response->getContent();
|
|
||||||
|
|
||||||
return $this->responseParser->parseXmlString($xml);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
|
||||||
throw new ApiClientException($e->getMessage());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPickups(): BaseResponse
|
|
||||||
{
|
|
||||||
return $this->cache->get('bpn_pickups', function (ItemInterface $item) {
|
|
||||||
$item->expiresAfter(3600);
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'anfrage' => [
|
|
||||||
'user' => $this->config['bpn_username'],
|
|
||||||
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], 'STAMMZUSTIEGE'),
|
|
||||||
'satz' => ['@typ' => 'STAMMZUSTIEGE'],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$body = $this
|
|
||||||
->serializer
|
|
||||||
->serialize($data, 'xml');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = $this->httpClient->request('GET', $this->config['bpn_url'], [
|
|
||||||
'query' => [
|
|
||||||
'operation' => $body,
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$xml = $response->getContent();
|
|
||||||
|
|
||||||
return $this->responseParser->parseXmlString($xml);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->error('API error', ['error' => $e->getMessage()]);
|
|
||||||
throw new ApiClientException($e->getMessage());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createKey(string $username, string $password, string $type): string
|
private function createKey(string $username, string $password, string $type): string
|
||||||
{
|
{
|
||||||
$date = (new \DateTimeImmutable())->format('Ymd');
|
$date = (new \DateTimeImmutable())->format('Ymd');
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\BusProNet\DataProvider;
|
||||||
|
|
||||||
|
use App\BusProNet\ApiClient;
|
||||||
|
use App\BusProNet\ApiClientException;
|
||||||
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
|
|
||||||
|
class Countries
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ApiClient $apiClient, private readonly CacheInterface $cache)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$countries = $this->cache->get('bpn_countries', function (ItemInterface $item) {
|
||||||
|
$item->expiresAfter(3600);
|
||||||
|
|
||||||
|
return $this
|
||||||
|
->apiClient
|
||||||
|
->getBaseData(ApiClient::TYPE_BASE_DATA_COUNTRIES)
|
||||||
|
->getItems()
|
||||||
|
;
|
||||||
|
});
|
||||||
|
} catch (ApiClientException $e) {
|
||||||
|
$countries = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $countries;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\BusProNet\DataProvider;
|
||||||
|
|
||||||
|
use App\BusProNet\ApiClient;
|
||||||
|
use App\BusProNet\ApiClientException;
|
||||||
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
|
|
||||||
|
class Pickups
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ApiClient $apiClient, private readonly CacheInterface $cache)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$pickups = $this->cache->get('bpn_pickups', function (ItemInterface $item) {
|
||||||
|
$item->expiresAfter(3600);
|
||||||
|
|
||||||
|
return $this
|
||||||
|
->apiClient
|
||||||
|
->getBaseData(ApiClient::TYPE_BASE_DATA_PICKUPS)
|
||||||
|
->getItems()
|
||||||
|
;
|
||||||
|
});
|
||||||
|
} catch (ApiClientException $e) {
|
||||||
|
$pickups = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pickups;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\BusProNet\Model;
|
||||||
|
|
||||||
|
class BaseDataResponse
|
||||||
|
{
|
||||||
|
private array $items = [];
|
||||||
|
|
||||||
|
public function getItems(): array
|
||||||
|
{
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setItems(array $items): static
|
||||||
|
{
|
||||||
|
$this->items = $items;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\BusProNet\Model;
|
|
||||||
|
|
||||||
class CountriesResponse extends BaseResponse
|
|
||||||
{
|
|
||||||
private array $countries = [];
|
|
||||||
|
|
||||||
public function getCountries(): array
|
|
||||||
{
|
|
||||||
return $this->countries;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCountries(array $countries): static
|
|
||||||
{
|
|
||||||
$this->countries = $countries;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\BusProNet\Model;
|
namespace App\BusProNet\Model;
|
||||||
|
|
||||||
class CrmAttributesResponse extends BaseResponse
|
class CrmAttributesResponse
|
||||||
{
|
{
|
||||||
private ?array $attributeGroups = null;
|
private ?array $attributeGroups = null;
|
||||||
private bool $admin = false;
|
private bool $admin = false;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\BusProNet\Model;
|
namespace App\BusProNet\Model;
|
||||||
|
|
||||||
class BaseResponse
|
class NotificationResponse
|
||||||
{
|
{
|
||||||
private ?int $code;
|
private ?int $code;
|
||||||
private ?string $message;
|
private ?string $message;
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\BusProNet\Model;
|
|
||||||
|
|
||||||
class PickupsResponse extends BaseResponse
|
|
||||||
{
|
|
||||||
private array $pickups = [];
|
|
||||||
|
|
||||||
public function getPickups(): array
|
|
||||||
{
|
|
||||||
return $this->pickups;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPickups(array $pickups): static
|
|
||||||
{
|
|
||||||
$this->pickups = $pickups;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\BusProNet\Model;
|
namespace App\BusProNet\Model;
|
||||||
|
|
||||||
class ProfileResponse extends BaseResponse
|
class ProfileResponse
|
||||||
{
|
{
|
||||||
private ?int $addressId = null;
|
private ?int $addressId = null;
|
||||||
private ?int $personId = null;
|
private ?int $personId = null;
|
||||||
|
|||||||
@@ -3,16 +3,15 @@
|
|||||||
namespace App\BusProNet;
|
namespace App\BusProNet;
|
||||||
|
|
||||||
use App\BusProNet\Model\Address;
|
use App\BusProNet\Model\Address;
|
||||||
|
use App\BusProNet\Model\BaseDataResponse;
|
||||||
|
use App\BusProNet\Model\NotificationResponse;
|
||||||
use App\BusProNet\Model\Communication;
|
use App\BusProNet\Model\Communication;
|
||||||
use App\BusProNet\Model\CountriesResponse;
|
|
||||||
use App\BusProNet\Model\Country;
|
use App\BusProNet\Model\Country;
|
||||||
use App\BusProNet\Model\CrmAttribute;
|
use App\BusProNet\Model\CrmAttribute;
|
||||||
use App\BusProNet\Model\CrmAttributesResponse;
|
|
||||||
use App\BusProNet\Model\CrmAttributeGroup;
|
use App\BusProNet\Model\CrmAttributeGroup;
|
||||||
|
use App\BusProNet\Model\CrmAttributesResponse;
|
||||||
use App\BusProNet\Model\Pickup;
|
use App\BusProNet\Model\Pickup;
|
||||||
use App\BusProNet\Model\PickupsResponse;
|
|
||||||
use App\BusProNet\Model\ProfileResponse;
|
use App\BusProNet\Model\ProfileResponse;
|
||||||
use App\BusProNet\Model\BaseResponse;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
class ResponseParser
|
class ResponseParser
|
||||||
@@ -27,37 +26,46 @@ class ResponseParser
|
|||||||
/**
|
/**
|
||||||
* @throws ResponseParserException
|
* @throws ResponseParserException
|
||||||
*/
|
*/
|
||||||
public function parseXmlString(string $content): BaseResponse
|
public function parseXmlString(string $type, string $content): mixed
|
||||||
{
|
{
|
||||||
$xml = simplexml_load_string($content);
|
$xml = simplexml_load_string($content);
|
||||||
$type = (string)$xml->xpath('satz/@typ')[0];
|
|
||||||
|
|
||||||
switch ($type) {
|
// Override type when present in XML to catch error responses
|
||||||
case 'HINWEIS':
|
$responseType = $type;
|
||||||
return $this->createBaseResponse($xml);
|
$responseTypeXml = $xml->xpath('satz/@typ');
|
||||||
case 'KUNDENKONTO':
|
if (0 < count($responseTypeXml)) {
|
||||||
|
$responseType = (string)$xml->xpath('satz/@typ')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($responseType) {
|
||||||
|
case ApiClient::TYPE_NOTIFICATION:
|
||||||
|
return $this->createNotificationResponse($xml);
|
||||||
|
case ApiClient::TYPE_CUSTOMER_DATA:
|
||||||
$subType = (string) $xml->xpath('art')[0];
|
$subType = (string) $xml->xpath('art')[0];
|
||||||
switch ($subType) {
|
switch ($subType) {
|
||||||
case 'Adressdaten':
|
case 'Adressdaten':
|
||||||
|
case 'Adressdaten_Ändern':
|
||||||
return $this->createProfileResponse($xml);
|
return $this->createProfileResponse($xml);
|
||||||
case 'SelektionCRM':
|
case 'SelektionCRM':
|
||||||
|
case 'SelektionCRM_Ändern':
|
||||||
return $this->createCrmAttributesResponse($xml);
|
return $this->createCrmAttributesResponse($xml);
|
||||||
}
|
}
|
||||||
case 'STAMMLAENDER':
|
break;
|
||||||
|
case ApiClient::TYPE_BASE_DATA_COUNTRIES:
|
||||||
return $this->createCountriesResponse($xml);
|
return $this->createCountriesResponse($xml);
|
||||||
case 'STAMMZUSTIEGE':
|
case ApiClient::TYPE_BASE_DATA_PICKUPS:
|
||||||
return $this->createPickupsResponse($xml);
|
return $this->createPickupsResponse($xml);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ResponseParserException('Unable to parse XML response');
|
throw new ResponseParserException('Unable to parse XML response');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createBaseResponse(\SimpleXMLElement $xml): BaseResponse
|
public function createNotificationResponse(\SimpleXMLElement $xml): NotificationResponse
|
||||||
{
|
{
|
||||||
$code = (int) $xml->xpath('satz/nr')[0];
|
$code = (int) $xml->xpath('satz/nr')[0];
|
||||||
$message = (string) $xml->xpath('satz/text')[0];
|
$message = (string) $xml->xpath('satz/text')[0];
|
||||||
|
|
||||||
return new BaseResponse($code, $message);
|
return new NotificationResponse($code, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createProfileResponse(\SimpleXMLElement $xml): ProfileResponse
|
public function createProfileResponse(\SimpleXMLElement $xml): ProfileResponse
|
||||||
@@ -163,7 +171,7 @@ class ResponseParser
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createCountriesResponse(\SimpleXMLElement $xml): CountriesResponse
|
public function createCountriesResponse(\SimpleXMLElement $xml): BaseDataResponse
|
||||||
{
|
{
|
||||||
$countries = [];
|
$countries = [];
|
||||||
|
|
||||||
@@ -178,17 +186,17 @@ class ResponseParser
|
|||||||
$countries[] = $country;
|
$countries[] = $country;
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = new CountriesResponse();
|
$response = new BaseDataResponse();
|
||||||
$response->setCountries($countries);
|
$response->setItems($countries);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPickupsResponse(\SimpleXMLElement $xml): PickupsResponse
|
public function createPickupsResponse(\SimpleXMLElement $xml): BaseDataResponse
|
||||||
{
|
{
|
||||||
$pickups = [];
|
$pickups = [];
|
||||||
|
|
||||||
foreach ($xml->xpath('zustiege/zustieg') as $item) {
|
foreach ($xml->xpath('zustieg') as $item) {
|
||||||
$pickup = new Pickup();
|
$pickup = new Pickup();
|
||||||
$pickup
|
$pickup
|
||||||
->setId((int)$item->attributes()['id'])
|
->setId((int)$item->attributes()['id'])
|
||||||
@@ -200,8 +208,8 @@ class ResponseParser
|
|||||||
$pickups[] = $pickup;
|
$pickups[] = $pickup;
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = new PickupsResponse();
|
$response = new BaseDataResponse();
|
||||||
$response->setPickups($pickups);
|
$response->setItems($pickups);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
private ?User $owner = null;
|
private ?User $owner = null;
|
||||||
|
|
||||||
#[ORM\ManyToMany(targetEntity: Pickup::class)]
|
|
||||||
private Collection $pickups;
|
|
||||||
|
|
||||||
#[ORM\ManyToMany(targetEntity: Fee::class)]
|
#[ORM\ManyToMany(targetEntity: Fee::class)]
|
||||||
private Collection $fees;
|
private Collection $fees;
|
||||||
|
|
||||||
@@ -64,7 +61,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->uuid = Uuid::v4();
|
$this->uuid = Uuid::v4();
|
||||||
$this->pickups = new ArrayCollection();
|
|
||||||
$this->fees = new ArrayCollection();
|
$this->fees = new ArrayCollection();
|
||||||
$this->applications = new ArrayCollection();
|
$this->applications = new ArrayCollection();
|
||||||
$this->dispositions = new ArrayCollection();
|
$this->dispositions = new ArrayCollection();
|
||||||
@@ -165,30 +161,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, Pickup>
|
|
||||||
*/
|
|
||||||
public function getPickups(): Collection
|
|
||||||
{
|
|
||||||
return $this->pickups;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addPickup(Pickup $pickup): static
|
|
||||||
{
|
|
||||||
if (!$this->pickups->contains($pickup)) {
|
|
||||||
$this->pickups->add($pickup);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removePickup(Pickup $pickup): static
|
|
||||||
{
|
|
||||||
$this->pickups->removeElement($pickup);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, Fee>
|
* @return Collection<int, Fee>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ class DispositionRequirement
|
|||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
private ?Training $training = null;
|
private ?Training $training = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
private ?Pickup $pickup = null;
|
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||||
private ?\DateTimeImmutable $pickupDate = null;
|
private ?\DateTimeImmutable $pickupDate = null;
|
||||||
|
|
||||||
@@ -43,18 +40,6 @@ class DispositionRequirement
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPickup(): ?Pickup
|
|
||||||
{
|
|
||||||
return $this->pickup;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPickup(?Pickup $pickup): static
|
|
||||||
{
|
|
||||||
$this->pickup = $pickup;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPickupDate(): ?\DateTimeImmutable
|
public function getPickupDate(): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->pickupDate;
|
return $this->pickupDate;
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use App\Repository\PickupRepository;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: PickupRepository::class)]
|
|
||||||
class Pickup
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\GeneratedValue]
|
|
||||||
#[ORM\Column]
|
|
||||||
private ?int $id = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
private ?string $name = null;
|
|
||||||
|
|
||||||
#[ORM\Column]
|
|
||||||
private ?int $busProId = null;
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): ?string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setName(string $name): static
|
|
||||||
{
|
|
||||||
$this->name = $name;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBusProId(): ?int
|
|
||||||
{
|
|
||||||
return $this->busProId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBusProId(int $busProId): static
|
|
||||||
{
|
|
||||||
$this->busProId = $busProId;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -114,6 +114,16 @@ class Teamer implements TimestampableEntityInterface
|
|||||||
#[ORM\OneToOne(mappedBy: 'teamer')]
|
#[ORM\OneToOne(mappedBy: 'teamer')]
|
||||||
private ?User $user = null;
|
private ?User $user = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private array $pickups = [];
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
#[Assert\NotBlank(message: 'Bitte gib deine Sprache(n) an', groups: ['profile', 'profile_preflight'])]
|
||||||
|
private ?string $language = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 2)]
|
||||||
|
private ?string $size = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->uuid = Uuid::v4();
|
$this->uuid = Uuid::v4();
|
||||||
@@ -615,4 +625,40 @@ class Teamer implements TimestampableEntityInterface
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPickups(): array
|
||||||
|
{
|
||||||
|
return $this->pickups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPickups(array $pickups): static
|
||||||
|
{
|
||||||
|
$this->pickups = $pickups;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLanguage(): ?string
|
||||||
|
{
|
||||||
|
return $this->language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLanguage(?string $language): static
|
||||||
|
{
|
||||||
|
$this->language = $language;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSize(): ?string
|
||||||
|
{
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSize(string $size): static
|
||||||
|
{
|
||||||
|
$this->size = $size;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\BusProNet\ApiClient;
|
use App\BusProNet\DataProvider\Countries;
|
||||||
use App\Form\ChoiceLoader\BpnCountryChoiceLoader;
|
use App\Form\ChoiceLoader\BpnCountryChoiceLoader;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||||
@@ -12,7 +12,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
|
|
||||||
class BpnCountryType extends AbstractType
|
class BpnCountryType extends AbstractType
|
||||||
{
|
{
|
||||||
public function __construct(private readonly ApiClient $apiClient)
|
public function __construct(private readonly Countries $countries)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
public function getParent(): string
|
public function getParent(): string
|
||||||
@@ -29,7 +29,7 @@ class BpnCountryType extends AbstractType
|
|||||||
'choice_loader' => function (Options $options) {
|
'choice_loader' => function (Options $options) {
|
||||||
return ChoiceList::loader(
|
return ChoiceList::loader(
|
||||||
$this,
|
$this,
|
||||||
new BpnCountryChoiceLoader($this->apiClient, $options['property']),
|
new BpnCountryChoiceLoader($this->countries, $options['property']),
|
||||||
[$options['property']]
|
[$options['property']]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\BusProNet\DataProvider\Pickups;
|
||||||
|
use App\Form\ChoiceLoader\BpnPickupsChoiceLoader;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class BpnPickupType extends AbstractType
|
||||||
|
{
|
||||||
|
public function __construct(private readonly Pickups $pickups)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return ChoiceType::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'choice_loader' => new BpnPickupsChoiceLoader($this->pickups),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,29 +2,22 @@
|
|||||||
|
|
||||||
namespace App\Form\ChoiceLoader;
|
namespace App\Form\ChoiceLoader;
|
||||||
|
|
||||||
use App\BusProNet\ApiClient;
|
use App\BusProNet\DataProvider\Countries;
|
||||||
use App\BusProNet\ApiClientException;
|
use App\BusProNet\Model\Country;
|
||||||
use App\BusProNet\Model\CountriesResponse;
|
|
||||||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||||
|
|
||||||
class BpnCountryChoiceLoader implements ChoiceLoaderInterface
|
class BpnCountryChoiceLoader implements ChoiceLoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private readonly ApiClient $apiClient, private readonly string $property)
|
public function __construct(private readonly Countries $countries, private readonly string $property)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
public function loadChoiceList(callable $value = null): ChoiceListInterface
|
public function loadChoiceList(callable $value = null): ChoiceListInterface
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
/** @var CountriesResponse $response */
|
|
||||||
$response = $this->apiClient->getCountries();
|
|
||||||
$countries = $response->getCountries();
|
|
||||||
} catch (ApiClientException $e) {
|
|
||||||
$countries = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$choices = [];
|
$choices = [];
|
||||||
|
/** @var Country[] $countries */
|
||||||
|
$countries = $this->countries->getAll();
|
||||||
|
|
||||||
foreach ($countries as $country) {
|
foreach ($countries as $country) {
|
||||||
$key = 'nationality' === $this->property ? $country->getNationality() : $country->getName();
|
$key = 'nationality' === $this->property ? $country->getNationality() : $country->getName();
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form\ChoiceLoader;
|
||||||
|
|
||||||
|
use App\BusProNet\DataProvider\Pickups;
|
||||||
|
use App\BusProNet\Model\Pickup;
|
||||||
|
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||||
|
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||||
|
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||||
|
|
||||||
|
class BpnPickupsChoiceLoader implements ChoiceLoaderInterface
|
||||||
|
{
|
||||||
|
public function __construct(private readonly Pickups $pickups)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function loadChoiceList(callable $value = null): ChoiceListInterface
|
||||||
|
{
|
||||||
|
$choices = [];
|
||||||
|
/** @var Pickup[] $pickups */
|
||||||
|
$pickups = $this->pickups->getAll();
|
||||||
|
|
||||||
|
foreach ($pickups as $pickup) {
|
||||||
|
$choices[$pickup->getCity()] = $pickup->getBusProId();
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($choices);
|
||||||
|
|
||||||
|
return new ArrayChoiceList($choices);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadChoicesForValues(array $values, callable $value = null): array
|
||||||
|
{
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadValuesForChoices(array $choices, callable $value = null): array
|
||||||
|
{
|
||||||
|
return $choices;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ use App\Service\Upload\UploadHandler;
|
|||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@@ -79,6 +80,32 @@ class TeamerProfileType extends AbstractType
|
|||||||
'label' => false,
|
'label' => false,
|
||||||
'error_bubbling' => false,
|
'error_bubbling' => false,
|
||||||
])
|
])
|
||||||
|
->add('pickups', CollectionType::class, [
|
||||||
|
'label' => 'Zustiege',
|
||||||
|
'entry_type' => BpnPickupType::class,
|
||||||
|
'allow_add' => true,
|
||||||
|
'allow_delete' => true,
|
||||||
|
])
|
||||||
|
->add('language', TextType::class, [
|
||||||
|
'label' => 'Sprache(n)',
|
||||||
|
])
|
||||||
|
->add('size', ChoiceType::class, [
|
||||||
|
'label' => 'Klamottengröße',
|
||||||
|
'choices' => [
|
||||||
|
'S' => 'S',
|
||||||
|
'M' => 'M',
|
||||||
|
'L' => 'L',
|
||||||
|
'XL' => 'XL',
|
||||||
|
'XXL' => 'XXL',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->add('status', ChoiceType::class, [
|
||||||
|
'label' => 'Status',
|
||||||
|
'choices' => [
|
||||||
|
'Neuteamer' => Teamer::STATUS_NEW,
|
||||||
|
'Bestandsteamer' => Teamer::STATUS_EXISTING,
|
||||||
|
],
|
||||||
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,38 @@
|
|||||||
<div {{ stimulus_controller('upload-collection', {
|
<div {{ stimulus_controller('upload-collection', {
|
||||||
'endpointUpload': endpoint_upload,
|
'endpointUpload': endpoint_upload,
|
||||||
'endpointDelete': path('app_upload_delete'),
|
'endpointDelete': path('app_upload_delete'),
|
||||||
'maxFiles': 1500,
|
'maxFiles': max_files|default(10),
|
||||||
'maxFilesize': 1000,
|
'maxFilesize': max_filesize|default(1),
|
||||||
'acceptedFiles': null,
|
'acceptedFiles': accepted_files|default(null),
|
||||||
'chunking': true,
|
'params': upload_session_params|default(null),
|
||||||
'params': upload_session_params,
|
|
||||||
'language': {
|
'language': {
|
||||||
'fileTooBig': 'uploader.file_too_big'|trans({ '{maxFilesize}': 1000 }),
|
'fileTooBig': 'uploader.file_too_big'|trans({ '%maxFilesize%': 1 }),
|
||||||
'invalidFileType': 'uploader.invalid_file_type'|trans,
|
'invalidFileType': 'uploader.invalid_file_type'|trans,
|
||||||
'maxFilesExceeded': 'uploader.max_files_exceeded'|trans({ '{maxFiles}': 1500 })
|
'maxFilesExceeded': 'uploader.max_files_exceeded'|trans({ '%maxFiles%': 10 })
|
||||||
}
|
}
|
||||||
}) }}>
|
}) }}>
|
||||||
<div {{ stimulus_target('upload-collection', 'dropzone') }} class="dropzone"></div>
|
<div {{ stimulus_target('upload-collection', 'dropzone') }} class="w-full flex flex-col space-y-4">
|
||||||
|
<div class="border border-dashed border-gray-400 rounded flex flex-col justify-center items-center p-4 pointer-events-none">
|
||||||
|
{{ icon('upload', 'w-6 h-6') }}
|
||||||
|
<span class="block text-sm text-center">{{ 'uploader.drop_files'|trans }} <strong>{{ 'uploader.click_here'|trans }}</strong></span>
|
||||||
|
</div>
|
||||||
|
<div class="border border-red-500 rounded-md p-4 text-red-500 hidden" {{ stimulus_target('upload-collection', 'errors') }}></div>
|
||||||
|
<template {{ stimulus_target('upload-collection', 'previewTemplate') }}>
|
||||||
|
<div class="bg-gray-100 border rounded p-2">
|
||||||
|
<div class="pb-2">
|
||||||
|
<span class="block bg-gray-400 h-1 w-0" data-dz-uploadprogress></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2 px-2">
|
||||||
|
<img data-dz-thumbnail
|
||||||
|
src="{{ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m6.75 12l-3-3m0 0l-3 3m3-3v6m-1.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>'|data_uri }}"
|
||||||
|
class="block h-8 w-auto"
|
||||||
|
alt="Upload">
|
||||||
|
<span class="flex-1" data-dz-name></span>
|
||||||
|
<button type="button" data-dz-remove>
|
||||||
|
{{ icon('delete', 'w-6 h-6 pointer-events-none') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,15 +6,24 @@
|
|||||||
<a href="{{ path(app.user.defaultRoute) }}">
|
<a href="{{ path(app.user.defaultRoute) }}">
|
||||||
<img class="h-8 w-auto" src="{{ asset('build/images/logo.svg') }}" alt="My E&P Team">
|
<img class="h-8 w-auto" src="{{ asset('build/images/logo.svg') }}" alt="My E&P Team">
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ path('app_security_logout') }}" class="hidden lg:block">
|
<div class="flex items-center space-x-4">
|
||||||
{{ icon('logout', 'w-8 h-8 text-gray-700') }}
|
{% if app.user.teamer.photo %}
|
||||||
</a>
|
<a href="{{ path('app_teamer_profile_index') }}" title="Mein Profil">
|
||||||
<button type="button"
|
<img src="{{ asset(teamer.photo.filename | imagine_filter('profile')) }}"
|
||||||
class="lg:hidden"
|
class="h-8 w-auto rounded-full border-2 border-primary"
|
||||||
{{ stimulus_controller('mobilenav', [], [], { 'mobilenav': '#mobilenav' }) }}
|
alt="{{ teamer.firstName }}">
|
||||||
{{ stimulus_action('mobilenav', 'trigger', null, { 'mode': 'open' }) }}>
|
</a>
|
||||||
{{ icon('menu', 'w-8 h-8 text-gray-700') }}
|
{% endif %}
|
||||||
</button>
|
<a href="{{ path('app_security_logout') }}" class="hidden lg:block">
|
||||||
|
{{ icon('logout', 'w-8 h-8 text-gray-700') }}
|
||||||
|
</a>
|
||||||
|
<button type="button"
|
||||||
|
class="lg:hidden"
|
||||||
|
{{ stimulus_controller('mobilenav', [], [], { 'mobilenav': '#mobilenav' }) }}
|
||||||
|
{{ stimulus_action('mobilenav', 'trigger', null, { 'mode': 'open' }) }}>
|
||||||
|
{{ icon('menu', 'w-8 h-8 text-gray-700') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden lg:block">
|
<div class="hidden lg:block">
|
||||||
{% block main_menu %}{% endblock %}
|
{% block main_menu %}{% endblock %}
|
||||||
|
|||||||
@@ -7,13 +7,30 @@
|
|||||||
{% for child in form.children %}
|
{% for child in form.children %}
|
||||||
{% for error in child.vars.errors %}
|
{% for error in child.vars.errors %}
|
||||||
<li>
|
<li>
|
||||||
{{error.message}}
|
{{ error.message }}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
|
{% for error in form.vars.errors %}
|
||||||
|
<li>
|
||||||
|
{{ error.message }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro collectionRow(form) %}
|
||||||
|
<div {{ stimulus_target('form-collection', 'field') }}>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
<button type="button" {{ stimulus_action('form-collection', 'removeItem') }}>
|
||||||
|
{{ icon('delete', 'w-4 h-4 mt-2 pointer-events-none') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{ form_errors(form) }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
|
|
||||||
@@ -41,7 +58,12 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="hover:bg-gray-50 block rounded-md py-2 pr-2 pl-10 w-full text-left text-sm leading-6 font-semibold text-gray-700" {{ stimulus_target('tabs', 'button') }} {{ stimulus_action('tabs', 'select', null, { 'tab': 3 }) }}>
|
<button type="button" class="hover:bg-gray-50 block rounded-md py-2 pr-2 pl-10 w-full text-left text-sm leading-6 font-semibold text-gray-700" {{ stimulus_target('tabs', 'button') }} {{ stimulus_action('tabs', 'select', null, { 'tab': 3 }) }}>
|
||||||
Sonstiges/Foto
|
Sonstiges
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="hover:bg-gray-50 block rounded-md py-2 pr-2 pl-10 w-full text-left text-sm leading-6 font-semibold text-gray-700" {{ stimulus_target('tabs', 'button') }} {{ stimulus_action('tabs', 'select', null, { 'tab': 4 }) }}>
|
||||||
|
Foto
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -70,6 +92,7 @@
|
|||||||
Persönliche Daten
|
Persönliche Daten
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex flex-col space-y-4">
|
<div class="flex flex-col space-y-4">
|
||||||
|
{{ form_row(form.status) }}
|
||||||
{{ form_row(form.salutation) }}
|
{{ form_row(form.salutation) }}
|
||||||
{{ form_row(form.academicTitle) }}
|
{{ form_row(form.academicTitle) }}
|
||||||
{{ form_row(form.firstName) }}
|
{{ form_row(form.firstName) }}
|
||||||
@@ -119,30 +142,48 @@
|
|||||||
<div class="flex flex-col space-y-4 pb-8">
|
<div class="flex flex-col space-y-4 pb-8">
|
||||||
{{ form_row(form.taxId) }}
|
{{ form_row(form.taxId) }}
|
||||||
{{ form_row(form.healthInsuranceCompany) }}
|
{{ form_row(form.healthInsuranceCompany) }}
|
||||||
|
{{ form_row(form.language) }}
|
||||||
|
{{ form_row(form.size) }}
|
||||||
{{ form_row(form.remarks) }}
|
{{ form_row(form.remarks) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div {{ stimulus_controller('form-collection', { 'prototype': _self.collectionRow(form.pickups.vars.prototype)|json_encode }) }}>
|
||||||
|
<h4 class="font-bold">
|
||||||
|
Buszustiege
|
||||||
|
</h4>
|
||||||
|
<div {{ stimulus_target('form-collection', 'fields')}} class="flex flex-col space-y-4 mb-4">
|
||||||
|
{% do form.pickups.setRendered %}
|
||||||
|
{%- for pickup in form.pickups -%}{{- _self.collectionRow(pickup) -}}{%- endfor -%}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button type="button"
|
||||||
|
{{ stimulus_action('form-collection', 'addItem') }}
|
||||||
|
title="Zustieg hinzufügen">
|
||||||
|
{{ icon('plus', 'w-4 h-4 pointer-events-none') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pb-8" {{ stimulus_target('tabs', 'tab') }}>
|
||||||
|
|
||||||
|
{% if teamer.photo %}
|
||||||
|
<div class="pb-8">
|
||||||
|
<h2 class="text-xl font-bold pb-2">
|
||||||
|
Mein Foto
|
||||||
|
</h2>
|
||||||
|
<img src="{{ asset(teamer.photo.filename | imagine_filter('profile')) }}"
|
||||||
|
class="w-48 h-auto border-2 border-primary"
|
||||||
|
alt="{{ teamer.firstName }}">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<h2 class="text-xl font-bold pb-2">
|
<h2 class="text-xl font-bold pb-2">
|
||||||
Fotoupload
|
Fotoupload
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid md:grid-cols-2 gap-8">
|
{% include '_partials/_upload_collection_form.html.twig' with {
|
||||||
<div>
|
'endpoint_upload': path('_uploader_upload_photo'),
|
||||||
{% include '_partials/_upload_collection_form.html.twig' with {
|
'upload_session_params': form.vars.upload_session_params,
|
||||||
'endpoint_upload': path('_uploader_upload_photo'),
|
} %}
|
||||||
'upload_session_params': form.vars.upload_session_params,
|
|
||||||
} %}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{% if teamer.photo %}
|
|
||||||
<img src="{{ asset(teamer.photo.filename | imagine_filter('profile')) }}"
|
|
||||||
class="w-full h-auto rounded-full border-2 border-primary"
|
|
||||||
alt="{{ teamer.firstName }}">
|
|
||||||
{% else %}
|
|
||||||
<svg class="w-full h-auto text-gray-300" viewBox="0 0 10 10" fill="currentColor">
|
|
||||||
<circle r="5" cx="5" cy="5"/>
|
|
||||||
</svg>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn">
|
<button type="submit" class="btn">
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
uploader:
|
||||||
|
drop_files: Dateien hier ablegen oder
|
||||||
|
click_here: hier klicken
|
||||||
|
file_too_big: Die maximale Dateigröße beträgt %maxFilesize%MB
|
||||||
|
invalid_file_type: Der Dateityp ist nicht erlaubt
|
||||||
|
max_files_exceeded: Die maximale Anzahl von Dateien is %maxFilesExceeded%
|
||||||
Reference in New Issue
Block a user