- api.fahrschultermin.de: full API entry point with CORS + session auth - fahrschultermin.de: frontend only, no more /api/v1 routing - Database.php: PostgreSQL support (pgsql driver + lastval()) - All repositories: use Database::lastInsertId() for PG compatibility - config/app.php: PostgreSQL connection settings + CORS config - bootstrap.php: pass full $config to Database::configure() - public/index.php: fetch-patch to redirect /api/v1 to api.fahrschultermin.de - database/schema_pgsql.sql: full PostgreSQL schema (BIGSERIAL) - database/import_pgsql.sql: 518 rows migrated from SQLite
516 lines
21 KiB
PHP
516 lines
21 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Repositories;
|
|
|
|
final class ReferenceDataRepository extends BaseRepository
|
|
{
|
|
private const DEFAULT_PLANNING_TYPES = [
|
|
['system_key' => 'private_block', 'name' => 'Privat', 'color' => '#6B7280', 'default_duration' => 60, 'category' => 'private', 'sort_order' => 900],
|
|
['system_key' => 'work_vehicle_care', 'name' => 'Fahrzeugpflege', 'color' => '#0E5D80', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 910],
|
|
['system_key' => 'work_office', 'name' => 'Buero', 'color' => '#8B5A2B', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 920],
|
|
['system_key' => 'work_transfer_drive', 'name' => 'Wechselfahrt', 'color' => '#3B7A57', 'default_duration' => 45, 'category' => 'work', 'sort_order' => 930],
|
|
['system_key' => 'work_other', 'name' => 'Sonstige Taetigkeit', 'color' => '#7C3AED', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 940],
|
|
['system_key' => 'theory_a', 'name' => 'Theorie A', 'color' => '#C2410C', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 950],
|
|
['system_key' => 'theory_b', 'name' => 'Theorie B', 'color' => '#A74826', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 960],
|
|
['system_key' => 'theory_c', 'name' => 'Theorie C', 'color' => '#B45309', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 970],
|
|
['system_key' => 'theory_d', 'name' => 'Theorie D', 'color' => '#BE123C', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 980],
|
|
['system_key' => 'theory_t', 'name' => 'Theorie T', 'color' => '#047857', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 990],
|
|
['system_key' => 'theory_bkf', 'name' => 'Theorie BKF', 'color' => '#1D4ED8', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 1000],
|
|
];
|
|
|
|
public function requirementKeyForLessonType(array $lessonType): string
|
|
{
|
|
$key = trim((string) ($lessonType['requirement_key'] ?? ''));
|
|
return $key !== '' ? $key : 'lesson_type_' . (int) $lessonType['id'];
|
|
}
|
|
|
|
public function lessonTypes(int $tenantId): array
|
|
{
|
|
$this->ensureDefaultPlanningTypes($tenantId);
|
|
$statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id ORDER BY sort_order, name');
|
|
$statement->execute(['tenant_id' => $tenantId]);
|
|
$lessonTypes = $statement->fetchAll();
|
|
$visibilityMap = $this->lessonTypeVisibilityMap($tenantId);
|
|
|
|
foreach ($lessonTypes as &$lessonType) {
|
|
$lessonType['visible_template_ids'] = $visibilityMap[(int) $lessonType['id']] ?? [];
|
|
$lessonType['effective_requirement_key'] = $this->requirementKeyForLessonType($lessonType);
|
|
}
|
|
|
|
return $lessonTypes;
|
|
}
|
|
|
|
public function createLessonType(int $tenantId, array $data): array
|
|
{
|
|
$timestamp = $this->now();
|
|
$statement = $this->db->prepare(
|
|
'INSERT INTO lesson_types
|
|
(tenant_id, name, color, default_duration, category, requirement_key, is_billable, is_counted, is_rest_relevant, fixed_duration, sort_order, created_at, updated_at)
|
|
VALUES
|
|
(:tenant_id, :name, :color, :default_duration, :category, :requirement_key, :is_billable, :is_counted, :is_rest_relevant, :fixed_duration, :sort_order, :created_at, :updated_at)'
|
|
);
|
|
$statement->execute([
|
|
'tenant_id' => $tenantId,
|
|
'name' => $data['name'],
|
|
'color' => $data['color'],
|
|
'default_duration' => (int) $data['default_duration'],
|
|
'category' => $data['category'],
|
|
'requirement_key' => $data['requirement_key'] ?: null,
|
|
'is_billable' => !empty($data['is_billable']) ? 1 : 0,
|
|
'is_counted' => !empty($data['is_counted']) ? 1 : 0,
|
|
'is_rest_relevant' => !empty($data['is_rest_relevant']) ? 1 : 0,
|
|
'fixed_duration' => !empty($data['fixed_duration']) ? 1 : 0,
|
|
'sort_order' => (int) ($data['sort_order'] ?? 0),
|
|
'created_at' => $timestamp,
|
|
'updated_at' => $timestamp,
|
|
]);
|
|
$lessonTypeId = (int) $this->lastInsertId();
|
|
$this->replaceLessonTypeVisibility($tenantId, $lessonTypeId, $data['visible_template_ids'] ?? null);
|
|
|
|
return $this->findLessonType($tenantId, $lessonTypeId);
|
|
}
|
|
|
|
public function updateLessonType(int $tenantId, int $id, array $data): ?array
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'UPDATE lesson_types
|
|
SET name = :name, color = :color, default_duration = :default_duration, category = :category,
|
|
requirement_key = :requirement_key, is_billable = :is_billable, is_counted = :is_counted,
|
|
is_rest_relevant = :is_rest_relevant, fixed_duration = :fixed_duration, sort_order = :sort_order,
|
|
updated_at = :updated_at
|
|
WHERE id = :id AND tenant_id = :tenant_id'
|
|
);
|
|
$statement->execute([
|
|
'id' => $id,
|
|
'tenant_id' => $tenantId,
|
|
'name' => $data['name'],
|
|
'color' => $data['color'],
|
|
'default_duration' => (int) $data['default_duration'],
|
|
'category' => $data['category'],
|
|
'requirement_key' => $data['requirement_key'] ?: null,
|
|
'is_billable' => !empty($data['is_billable']) ? 1 : 0,
|
|
'is_counted' => !empty($data['is_counted']) ? 1 : 0,
|
|
'is_rest_relevant' => !empty($data['is_rest_relevant']) ? 1 : 0,
|
|
'fixed_duration' => !empty($data['fixed_duration']) ? 1 : 0,
|
|
'sort_order' => (int) ($data['sort_order'] ?? 0),
|
|
'updated_at' => $this->now(),
|
|
]);
|
|
|
|
$this->replaceLessonTypeVisibility($tenantId, $id, $data['visible_template_ids'] ?? null);
|
|
|
|
return $this->findLessonType($tenantId, $id);
|
|
}
|
|
|
|
public function deleteLessonType(int $tenantId, int $id): bool
|
|
{
|
|
$lessonType = $this->findLessonTypeRecord($tenantId, $id);
|
|
if ($lessonType === null) {
|
|
return false;
|
|
}
|
|
|
|
if (($lessonType['system_key'] ?? null) === 'private_block') {
|
|
throw new \RuntimeException('Der feste Privat-Baustein kann nicht geloescht werden.');
|
|
}
|
|
|
|
$usage = $this->db->prepare('SELECT COUNT(*) AS count FROM appointments WHERE tenant_id = :tenant_id AND lesson_type_id = :lesson_type_id');
|
|
$usage->execute(['tenant_id' => $tenantId, 'lesson_type_id' => $id]);
|
|
if ((int) ($usage->fetch()['count'] ?? 0) > 0) {
|
|
throw new \RuntimeException('Diese Stundenart wird bereits in Terminen verwendet und kann nicht geloescht werden.');
|
|
}
|
|
|
|
$this->db->beginTransaction();
|
|
try {
|
|
$visibility = $this->db->prepare('DELETE FROM lesson_type_template_visibility WHERE lesson_type_id = :lesson_type_id');
|
|
$visibility->execute(['lesson_type_id' => $id]);
|
|
|
|
$delete = $this->db->prepare('DELETE FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id');
|
|
$delete->execute(['tenant_id' => $tenantId, 'id' => $id]);
|
|
|
|
$this->db->commit();
|
|
return true;
|
|
} catch (\Throwable $exception) {
|
|
$this->db->rollBack();
|
|
throw $exception;
|
|
}
|
|
}
|
|
|
|
public function reorderLessonTypes(int $tenantId, array $lessonTypeIds): array
|
|
{
|
|
$allowedIds = array_map(
|
|
static fn (array $lessonType): int => (int) $lessonType['id'],
|
|
$this->lessonTypes($tenantId)
|
|
);
|
|
|
|
$statement = $this->db->prepare(
|
|
'UPDATE lesson_types
|
|
SET sort_order = :sort_order, updated_at = :updated_at
|
|
WHERE tenant_id = :tenant_id AND id = :id'
|
|
);
|
|
|
|
$sortOrder = 1;
|
|
foreach ($lessonTypeIds as $lessonTypeId) {
|
|
$lessonTypeId = (int) $lessonTypeId;
|
|
if (!in_array($lessonTypeId, $allowedIds, true)) {
|
|
continue;
|
|
}
|
|
|
|
$statement->execute([
|
|
'sort_order' => $sortOrder,
|
|
'updated_at' => $this->now(),
|
|
'tenant_id' => $tenantId,
|
|
'id' => $lessonTypeId,
|
|
]);
|
|
$sortOrder++;
|
|
}
|
|
|
|
return $this->lessonTypes($tenantId);
|
|
}
|
|
|
|
public function findLessonType(int $tenantId, int $id): ?array
|
|
{
|
|
$statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id');
|
|
$statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
|
|
$lessonType = $statement->fetch() ?: null;
|
|
if ($lessonType === null) {
|
|
return null;
|
|
}
|
|
|
|
$lessonType['visible_template_ids'] = $this->visibleTemplateIdsForLessonType($tenantId, $id);
|
|
$lessonType['effective_requirement_key'] = $this->requirementKeyForLessonType($lessonType);
|
|
|
|
return $lessonType;
|
|
}
|
|
|
|
public function templates(int $tenantId): array
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'SELECT * FROM license_class_templates WHERE tenant_id = :tenant_id ORDER BY name'
|
|
);
|
|
$statement->execute(['tenant_id' => $tenantId]);
|
|
$templates = $statement->fetchAll();
|
|
|
|
foreach ($templates as &$template) {
|
|
$template['requirements'] = $this->templateRequirements((int) $template['id']);
|
|
$template['lesson_type_ids'] = $this->lessonTypeIdsForTemplate((int) $template['id']);
|
|
}
|
|
|
|
return $templates;
|
|
}
|
|
|
|
public function templateRequirements(int $templateId): array
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'SELECT * FROM license_template_requirements WHERE template_id = :template_id ORDER BY sort_order, phase_label, label'
|
|
);
|
|
$statement->execute(['template_id' => $templateId]);
|
|
|
|
return $statement->fetchAll();
|
|
}
|
|
|
|
public function createTemplate(int $tenantId, array $data): array
|
|
{
|
|
$timestamp = $this->now();
|
|
$statement = $this->db->prepare(
|
|
'INSERT INTO license_class_templates
|
|
(tenant_id, code, name, is_combination, created_at, updated_at)
|
|
VALUES (:tenant_id, :code, :name, :is_combination, :created_at, :updated_at)'
|
|
);
|
|
$statement->execute([
|
|
'tenant_id' => $tenantId,
|
|
'code' => $data['code'],
|
|
'name' => $data['name'],
|
|
'is_combination' => !empty($data['is_combination']) ? 1 : 0,
|
|
'created_at' => $timestamp,
|
|
'updated_at' => $timestamp,
|
|
]);
|
|
$templateId = (int) $this->lastInsertId();
|
|
|
|
$this->replaceTemplateRequirements($templateId, $data['requirements'] ?? []);
|
|
$this->attachTemplateToExistingStudentLessonTypes($tenantId, $templateId);
|
|
|
|
return $this->findTemplate($tenantId, $templateId);
|
|
}
|
|
|
|
public function updateTemplate(int $tenantId, int $id, array $data): ?array
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'UPDATE license_class_templates
|
|
SET code = :code, name = :name, is_combination = :is_combination, updated_at = :updated_at
|
|
WHERE id = :id AND tenant_id = :tenant_id'
|
|
);
|
|
$statement->execute([
|
|
'id' => $id,
|
|
'tenant_id' => $tenantId,
|
|
'code' => $data['code'],
|
|
'name' => $data['name'],
|
|
'is_combination' => !empty($data['is_combination']) ? 1 : 0,
|
|
'updated_at' => $this->now(),
|
|
]);
|
|
|
|
$this->replaceTemplateRequirements($id, $data['requirements'] ?? []);
|
|
|
|
return $this->findTemplate($tenantId, $id);
|
|
}
|
|
|
|
public function updateLessonTypeMatrix(int $tenantId, array $rows): array
|
|
{
|
|
$templates = $this->templates($tenantId);
|
|
$templateIds = array_map(static fn (array $template): int => (int) $template['id'], $templates);
|
|
$allowedLessonTypeIds = array_map(
|
|
static fn (array $lessonType): int => (int) $lessonType['id'],
|
|
array_filter($this->lessonTypes($tenantId), static fn (array $lessonType): bool => $lessonType['category'] === 'student')
|
|
);
|
|
|
|
$delete = $this->db->prepare(
|
|
'DELETE FROM lesson_type_template_visibility
|
|
WHERE template_id = :template_id
|
|
AND lesson_type_id IN (
|
|
SELECT id FROM lesson_types WHERE tenant_id = :tenant_id AND category = \'student\'
|
|
)'
|
|
);
|
|
$insert = $this->db->prepare(
|
|
'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id)
|
|
VALUES (:lesson_type_id, :template_id)'
|
|
);
|
|
|
|
foreach ($rows as $row) {
|
|
$templateId = (int) ($row['template_id'] ?? 0);
|
|
if (!in_array($templateId, $templateIds, true)) {
|
|
continue;
|
|
}
|
|
|
|
$delete->execute([
|
|
'template_id' => $templateId,
|
|
'tenant_id' => $tenantId,
|
|
]);
|
|
|
|
foreach ((array) ($row['lesson_type_ids'] ?? []) as $lessonTypeId) {
|
|
$lessonTypeId = (int) $lessonTypeId;
|
|
if (!in_array($lessonTypeId, $allowedLessonTypeIds, true)) {
|
|
continue;
|
|
}
|
|
|
|
$insert->execute([
|
|
'lesson_type_id' => $lessonTypeId,
|
|
'template_id' => $templateId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $this->templates($tenantId);
|
|
}
|
|
|
|
public function isLessonTypeVisibleForTemplate(int $tenantId, int $lessonTypeId, int $templateId): bool
|
|
{
|
|
$lessonType = $this->findLessonType($tenantId, $lessonTypeId);
|
|
if ($lessonType === null) {
|
|
return false;
|
|
}
|
|
|
|
if ($lessonType['category'] !== 'student') {
|
|
return true;
|
|
}
|
|
|
|
return in_array($templateId, array_map('intval', $lessonType['visible_template_ids'] ?? []), true);
|
|
}
|
|
|
|
private function replaceTemplateRequirements(int $templateId, array $requirements): void
|
|
{
|
|
$delete = $this->db->prepare('DELETE FROM license_template_requirements WHERE template_id = :template_id');
|
|
$delete->execute(['template_id' => $templateId]);
|
|
|
|
$insert = $this->db->prepare(
|
|
'INSERT INTO license_template_requirements
|
|
(template_id, requirement_key, label, required_units, sort_order, phase_label)
|
|
VALUES (:template_id, :requirement_key, :label, :required_units, :sort_order, :phase_label)'
|
|
);
|
|
|
|
foreach ($requirements as $index => $requirement) {
|
|
if (($requirement['label'] ?? '') === '') {
|
|
continue;
|
|
}
|
|
|
|
$insert->execute([
|
|
'template_id' => $templateId,
|
|
'requirement_key' => $requirement['requirement_key'],
|
|
'label' => $requirement['label'],
|
|
'required_units' => (int) $requirement['required_units'],
|
|
'sort_order' => (int) ($requirement['sort_order'] ?? $index),
|
|
'phase_label' => trim((string) ($requirement['phase_label'] ?? '')),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function findTemplate(int $tenantId, int $id): ?array
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'SELECT * FROM license_class_templates WHERE tenant_id = :tenant_id AND id = :id'
|
|
);
|
|
$statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
|
|
$template = $statement->fetch() ?: null;
|
|
|
|
if ($template !== null) {
|
|
$template['requirements'] = $this->templateRequirements($id);
|
|
$template['lesson_type_ids'] = $this->lessonTypeIdsForTemplate($id);
|
|
}
|
|
|
|
return $template;
|
|
}
|
|
|
|
private function replaceLessonTypeVisibility(int $tenantId, int $lessonTypeId, ?array $visibleTemplateIds): void
|
|
{
|
|
$lessonType = $this->findLessonTypeRecord($tenantId, $lessonTypeId);
|
|
if ($lessonType === null) {
|
|
return;
|
|
}
|
|
|
|
$delete = $this->db->prepare('DELETE FROM lesson_type_template_visibility WHERE lesson_type_id = :lesson_type_id');
|
|
$delete->execute(['lesson_type_id' => $lessonTypeId]);
|
|
|
|
if ($lessonType['category'] !== 'student') {
|
|
return;
|
|
}
|
|
|
|
$templateIds = $visibleTemplateIds ?? $this->templateIdsForTenant($tenantId);
|
|
$templateIds = array_values(array_unique(array_map('intval', $templateIds)));
|
|
if ($templateIds === []) {
|
|
return;
|
|
}
|
|
|
|
$allowedTemplateIds = $this->templateIdsForTenant($tenantId);
|
|
$insert = $this->db->prepare(
|
|
'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id)
|
|
VALUES (:lesson_type_id, :template_id)'
|
|
);
|
|
|
|
foreach ($templateIds as $templateId) {
|
|
if (!in_array($templateId, $allowedTemplateIds, true)) {
|
|
continue;
|
|
}
|
|
|
|
$insert->execute([
|
|
'lesson_type_id' => $lessonTypeId,
|
|
'template_id' => $templateId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function lessonTypeVisibilityMap(int $tenantId): array
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'SELECT lt.id AS lesson_type_id, vis.template_id
|
|
FROM lesson_types lt
|
|
LEFT JOIN lesson_type_template_visibility vis ON vis.lesson_type_id = lt.id
|
|
WHERE lt.tenant_id = :tenant_id AND lt.category = \'student\'
|
|
ORDER BY vis.template_id'
|
|
);
|
|
$statement->execute(['tenant_id' => $tenantId]);
|
|
|
|
$map = [];
|
|
foreach ($statement->fetchAll() as $row) {
|
|
$lessonTypeId = (int) $row['lesson_type_id'];
|
|
$map[$lessonTypeId] ??= [];
|
|
if ($row['template_id'] !== null) {
|
|
$map[$lessonTypeId][] = (int) $row['template_id'];
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
private function visibleTemplateIdsForLessonType(int $tenantId, int $lessonTypeId): array
|
|
{
|
|
$lessonType = $this->findLessonTypeRecord($tenantId, $lessonTypeId);
|
|
if ($lessonType === null || $lessonType['category'] !== 'student') {
|
|
return [];
|
|
}
|
|
|
|
$statement = $this->db->prepare(
|
|
'SELECT template_id FROM lesson_type_template_visibility
|
|
WHERE lesson_type_id = :lesson_type_id
|
|
ORDER BY template_id'
|
|
);
|
|
$statement->execute(['lesson_type_id' => $lessonTypeId]);
|
|
|
|
return array_map(
|
|
static fn (array $row): int => (int) $row['template_id'],
|
|
$statement->fetchAll()
|
|
);
|
|
}
|
|
|
|
private function lessonTypeIdsForTemplate(int $templateId): array
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'SELECT lesson_type_id FROM lesson_type_template_visibility
|
|
WHERE template_id = :template_id
|
|
ORDER BY lesson_type_id'
|
|
);
|
|
$statement->execute(['template_id' => $templateId]);
|
|
|
|
return array_map(
|
|
static fn (array $row): int => (int) $row['lesson_type_id'],
|
|
$statement->fetchAll()
|
|
);
|
|
}
|
|
|
|
private function templateIdsForTenant(int $tenantId): array
|
|
{
|
|
$statement = $this->db->prepare('SELECT id FROM license_class_templates WHERE tenant_id = :tenant_id ORDER BY id');
|
|
$statement->execute(['tenant_id' => $tenantId]);
|
|
|
|
return array_map(
|
|
static fn (array $row): int => (int) $row['id'],
|
|
$statement->fetchAll()
|
|
);
|
|
}
|
|
|
|
private function attachTemplateToExistingStudentLessonTypes(int $tenantId, int $templateId): void
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id)
|
|
SELECT id, :template_id FROM lesson_types
|
|
WHERE tenant_id = :tenant_id AND category = \'student\''
|
|
);
|
|
$statement->execute([
|
|
'template_id' => $templateId,
|
|
'tenant_id' => $tenantId,
|
|
]);
|
|
}
|
|
|
|
private function findLessonTypeRecord(int $tenantId, int $id): ?array
|
|
{
|
|
$statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id');
|
|
$statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
|
|
|
|
return $statement->fetch() ?: null;
|
|
}
|
|
|
|
private function ensureDefaultPlanningTypes(int $tenantId): void
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'INSERT OR IGNORE INTO lesson_types
|
|
(tenant_id, name, color, default_duration, category, requirement_key, is_billable, is_counted, is_rest_relevant, fixed_duration, sort_order, system_key, created_at, updated_at)
|
|
VALUES
|
|
(:tenant_id, :name, :color, :default_duration, :category, NULL, 0, 0, :is_rest_relevant, 0, :sort_order, :system_key, :created_at, :updated_at)'
|
|
);
|
|
|
|
$timestamp = $this->now();
|
|
foreach (self::DEFAULT_PLANNING_TYPES as $row) {
|
|
$statement->execute([
|
|
'tenant_id' => $tenantId,
|
|
'name' => $row['name'],
|
|
'color' => $row['color'],
|
|
'default_duration' => $row['default_duration'],
|
|
'category' => $row['category'],
|
|
'is_rest_relevant' => $row['category'] === 'private' ? 0 : 1,
|
|
'sort_order' => $row['sort_order'],
|
|
'system_key' => $row['system_key'],
|
|
'created_at' => $timestamp,
|
|
'updated_at' => $timestamp,
|
|
]);
|
|
}
|
|
}
|
|
}
|