Vervollstaendige Marktmanagement-MCP-Abdeckung

This commit is contained in:
gitea_admin committed 2026-09-19 09:24:14 +02:00
1 parent 3267a121de
commit 9bde337506
4 files changed
+348 -18

No files matched your search

+231 -16
View File
@@ -118,10 +118,28 @@ function market_management_search_markets(PDO $pdo, array $input): array
{
$search = market_management_optional_string($input, 'search');
$includeArchived = (bool) ($input['include_archived'] ?? false);
$categoryId = array_key_exists('category_id', $input)
? market_management_require_positive_id($input['category_id'], 'category_id')
: null;
$status = array_key_exists('status', $input) ? market_management_validate_status($input['status']) : null;
$place = market_management_optional_string($input, 'place');
$municipalityId = array_key_exists('municipality_reference_id', $input)
? market_management_require_positive_id($input['municipality_reference_id'], 'municipality_reference_id')
: null;
$dateFrom = market_management_optional_string($input, 'date_from');
$dateTo = market_management_optional_string($input, 'date_to');
$limit = min(100, max(1, (int) ($input['limit'] ?? 50)));
$offset = max(0, (int) ($input['offset'] ?? 0));
$sort = (string) ($input['sort'] ?? 'name');
$sortDirection = strtolower((string) ($input['sort_direction'] ?? 'asc')) === 'desc' ? 'DESC' : 'ASC';
$sortColumn = match ($sort) {
'earliest_date' => 'MIN(d.date)',
'updated_at' => 'm.updated_at',
default => 'm.name',
};
$where = [];
$params = [':limit' => $limit];
$params = [':limit' => $limit, ':offset' => $offset, ':include_archived' => $includeArchived];
if (!$includeArchived) {
$where[] = 'm.archived_at IS NULL';
}
@@ -129,18 +147,51 @@ function market_management_search_markets(PDO $pdo, array $input): array
$where[] = 'm.name ILIKE :search';
$params[':search'] = '%' . $search . '%';
}
if ($categoryId !== null) {
$where[] = 'm.category_id = :category_id';
$params[':category_id'] = $categoryId;
}
if ($status !== null) {
$where[] = 'EXISTS (SELECT 1 FROM market_occurrence os WHERE os.market_definition_id = m.id AND os.status = :status AND (:include_archived OR os.archived_at IS NULL))';
$params[':status'] = $status;
}
if ($place !== null) {
$where[] = 'EXISTS (SELECT 1 FROM market_occurrence op WHERE op.market_definition_id = m.id AND op.place ILIKE :place AND (:include_archived OR op.archived_at IS NULL))';
$params[':place'] = '%' . $place . '%';
}
if ($municipalityId !== null) {
$where[] = 'EXISTS (SELECT 1 FROM market_occurrence om WHERE om.market_definition_id = m.id AND om.municipality_reference_id = :municipality_id AND (:include_archived OR om.archived_at IS NULL))';
$params[':municipality_id'] = $municipalityId;
}
if ($dateFrom !== null || $dateTo !== null) {
$dateConditions = ['od.occurrence_id = odate.id'];
if ($dateFrom !== null) {
$dateConditions[] = 'od.date >= :date_from';
$params[':date_from'] = $dateFrom;
}
if ($dateTo !== null) {
$dateConditions[] = 'od.date <= :date_to';
$params[':date_to'] = $dateTo;
}
$where[] = 'EXISTS (SELECT 1 FROM market_occurrence odate INNER JOIN market_occurrence_day od ON ' . implode(' AND ', $dateConditions) . ' WHERE odate.market_definition_id = m.id AND (:include_archived OR odate.archived_at IS NULL))';
}
$stmt = $pdo->prepare(
'SELECT m.id, m.name, m.category_id, c.code AS category_code, c.name AS category_name,
m.archived_at, m.created_at, m.updated_at
m.archived_at, m.created_at, m.updated_at,
MIN(d.date) AS earliest_date, MAX(d.date) AS latest_date,
COUNT(DISTINCT o.id) AS occurrence_count
FROM market_definition m
INNER JOIN market_category c ON c.id = m.category_id
LEFT JOIN market_occurrence o ON o.market_definition_id = m.id AND (:include_archived OR o.archived_at IS NULL)
LEFT JOIN market_occurrence_day d ON d.occurrence_id = o.id
WHERE ' . ($where === [] ? 'TRUE' : implode(' AND ', $where)) . '
ORDER BY m.name ASC, m.id ASC
LIMIT :limit'
GROUP BY m.id, c.code, c.name
ORDER BY ' . $sortColumn . ' ' . $sortDirection . ', m.id ASC
LIMIT :limit OFFSET :offset'
);
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value, $key === ':limit' ? PDO::PARAM_INT : PDO::PARAM_STR);
$stmt->bindValue($key, $value, in_array($key, [':limit', ':offset', ':category_id', ':municipality_id'], true) ? PDO::PARAM_INT : PDO::PARAM_STR);
}
$stmt->execute();
return $stmt->fetchAll();
@@ -162,10 +213,7 @@ function market_management_get_market(PDO $pdo, int $marketId, bool $includeArch
}
$occurrences = $pdo->prepare(
'SELECT id, status, place, address, municipality_reference_id, notes,
registration_deadline, request_or_registration_date, confirmation_date,
payment_deadline, payment_status, payment_date, paid_amount,
contract_folder_url, archived_at, created_at, updated_at
'SELECT *
FROM market_occurrence
WHERE market_definition_id = :market_id
AND (:include_archived OR archived_at IS NULL)
@@ -173,9 +221,94 @@ function market_management_get_market(PDO $pdo, int $marketId, bool $includeArch
);
$occurrences->execute([':market_id' => $marketId, ':include_archived' => $includeArchived]);
$market['occurrences'] = $occurrences->fetchAll();
foreach ($market['occurrences'] as &$occurrence) {
$occurrenceId = (int) $occurrence['id'];
$organizer = $pdo->prepare('SELECT * FROM market_occurrence_organizer WHERE occurrence_id = :occurrence_id');
$organizer->execute([':occurrence_id' => $occurrenceId]);
$occurrence['organizer'] = $organizer->fetch() ?: null;
$days = $pdo->prepare('SELECT * FROM market_occurrence_day WHERE occurrence_id = :occurrence_id ORDER BY date ASC, id ASC');
$days->execute([':occurrence_id' => $occurrenceId]);
$occurrence['days'] = $days->fetchAll();
$costs = $pdo->prepare('SELECT * FROM market_occurrence_cost_item WHERE occurrence_id = :occurrence_id ORDER BY id ASC');
$costs->execute([':occurrence_id' => $occurrenceId]);
$occurrence['cost_items'] = $costs->fetchAll();
}
unset($occurrence);
return $market;
}
function market_management_search_occurrences(PDO $pdo, array $input): array
{
$where = [];
$params = [':limit' => min(100, max(1, (int) ($input['limit'] ?? 50))), ':offset' => max(0, (int) ($input['offset'] ?? 0))];
$includeArchived = (bool) ($input['include_archived'] ?? false);
$params[':include_archived'] = $includeArchived;
if (!$includeArchived) {
$where[] = 'o.archived_at IS NULL';
}
if (array_key_exists('market_definition_id', $input)) {
$where[] = 'o.market_definition_id = :market_definition_id';
$params[':market_definition_id'] = market_management_require_positive_id($input['market_definition_id'], 'market_definition_id');
}
if (array_key_exists('status', $input)) {
$where[] = 'o.status = :status';
$params[':status'] = market_management_validate_status($input['status']);
}
foreach (['place' => 'o.place', 'address' => 'o.address'] as $key => $column) {
$value = market_management_optional_string($input, $key);
if ($value !== null) {
$where[] = $column . ' ILIKE :' . $key;
$params[':' . $key] = '%' . $value . '%';
}
}
if (array_key_exists('municipality_reference_id', $input)) {
$where[] = 'o.municipality_reference_id = :municipality_reference_id';
$params[':municipality_reference_id'] = market_management_require_positive_id($input['municipality_reference_id'], 'municipality_reference_id');
}
foreach (['date_from' => '>=', 'date_to' => '<='] as $key => $operator) {
$value = market_management_optional_string($input, $key);
if ($value !== null) {
$where[] = 'EXISTS (SELECT 1 FROM market_occurrence_day fd WHERE fd.occurrence_id = o.id AND fd.date ' . $operator . ' :' . $key . ')';
$params[':' . $key] = $value;
}
}
$stmt = $pdo->prepare(
'SELECT o.*, m.name AS market_name, m.category_id, c.code AS category_code, c.name AS category_name,
MIN(d.date) AS earliest_date, MAX(d.date) AS latest_date, COUNT(d.id) AS day_count
FROM market_occurrence o
INNER JOIN market_definition m ON m.id = o.market_definition_id
INNER JOIN market_category c ON c.id = m.category_id
LEFT JOIN market_occurrence_day d ON d.occurrence_id = o.id
WHERE ' . ($where === [] ? 'TRUE' : implode(' AND ', $where)) . '
GROUP BY o.id, m.name, c.code, c.name
ORDER BY earliest_date NULLS LAST, o.id ASC
LIMIT :limit OFFSET :offset'
);
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value, in_array($key, [':limit', ':offset', ':market_definition_id', ':municipality_reference_id'], true) ? PDO::PARAM_INT : PDO::PARAM_STR);
}
$stmt->execute();
return $stmt->fetchAll();
}
function market_management_list_reference_data(PDO $pdo, array $input): array
{
$result = ['categories' => market_management_list_categories($pdo)];
$municipalities = $pdo->query(
'SELECT id, bfs_municipality_number, name, canton_code, data_version
FROM market_municipality_reference WHERE is_active = TRUE ORDER BY name ASC, id ASC'
)->fetchAll();
$result['municipalities'] = $municipalities;
$result['cantons'] = array_values(array_map(
static fn (array $row): array => ['code' => (string) $row['canton_code']],
array_values(array_reduce($municipalities, static function (array $carry, array $row): array {
$carry[(string) $row['canton_code']] = $row;
return $carry;
}, []))
));
return $result;
}
function market_management_update_market(PDO $pdo, int $marketId, array $input): array
{
$sets = [];
@@ -274,6 +407,10 @@ function market_management_create_occurrence(PDO $pdo, array $input): array
throw new InvalidArgumentException('market_definition_id verweist nicht auf einen Markt.');
}
$status = array_key_exists('status', $input) ? market_management_validate_status($input['status']) : 'open';
$days = $input['days'] ?? null;
if (!is_array($days) || count($days) < 1) {
throw new InvalidArgumentException('Mindestens ein Durchführungstag ist erforderlich.');
}
$definitions = [
'place' => 'string', 'address' => 'string', 'municipality_reference_id' => 'id', 'notes' => 'string',
'registration_deadline' => 'string', 'request_or_registration_date' => 'string', 'confirmation_date' => 'string',
@@ -296,13 +433,35 @@ function market_management_create_occurrence(PDO $pdo, array $input): array
$params[':status'] = $status;
$columns = array_map(static fn (string $field): string => explode(' = ', $field, 2)[0], $fields);
$values = array_map(static fn (string $field): string => explode(' = ', $field, 2)[1], $fields);
$stmt = $pdo->prepare(
'INSERT INTO market_occurrence (' . implode(', ', $columns) . ')
VALUES (' . implode(', ', $values) . ')
RETURNING *'
);
$stmt->execute($params);
return (array) $stmt->fetch();
$startedTransaction = !$pdo->inTransaction();
if ($startedTransaction) {
$pdo->beginTransaction();
}
try {
$stmt = $pdo->prepare(
'INSERT INTO market_occurrence (' . implode(', ', $columns) . ')
VALUES (' . implode(', ', $values) . ')
RETURNING *'
);
$stmt->execute($params);
$occurrence = (array) $stmt->fetch();
foreach ($days as $day) {
if (!is_array($day)) {
throw new InvalidArgumentException('Jeder Durchführungstag muss ein Objekt sein.');
}
$day['occurrence_id'] = (int) $occurrence['id'];
market_management_create_day($pdo, $day);
}
if ($startedTransaction) {
$pdo->commit();
}
} catch (Throwable $e) {
if ($startedTransaction && $pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}
return market_management_get_occurrence($pdo, (int) $occurrence['id'], true) ?? $occurrence;
}
function market_management_get_occurrence(PDO $pdo, int $occurrenceId, bool $includeArchived = false): ?array
@@ -322,6 +481,12 @@ function market_management_get_occurrence(PDO $pdo, int $occurrenceId, bool $inc
);
$days->execute([':occurrence_id' => $occurrenceId]);
$occurrence['days'] = $days->fetchAll();
$organizer = $pdo->prepare('SELECT * FROM market_occurrence_organizer WHERE occurrence_id = :occurrence_id');
$organizer->execute([':occurrence_id' => $occurrenceId]);
$occurrence['organizer'] = $organizer->fetch() ?: null;
$costs = $pdo->prepare('SELECT * FROM market_occurrence_cost_item WHERE occurrence_id = :occurrence_id ORDER BY id ASC');
$costs->execute([':occurrence_id' => $occurrenceId]);
$occurrence['cost_items'] = $costs->fetchAll();
return $occurrence;
}
@@ -465,6 +630,17 @@ function market_management_set_organizer(PDO $pdo, array $input): array
return (array) $stmt->fetch();
}
function market_management_delete_organizer(PDO $pdo, int $occurrenceId): array
{
$stmt = $pdo->prepare('DELETE FROM market_occurrence_organizer WHERE occurrence_id = :occurrence_id RETURNING id, occurrence_id');
$stmt->execute([':occurrence_id' => $occurrenceId]);
$organizer = $stmt->fetch();
if (!is_array($organizer)) {
throw new RuntimeException('Veranstalter nicht gefunden.');
}
return $organizer;
}
function market_management_create_cost_item(PDO $pdo, array $input): array
{
$occurrenceId = market_management_require_positive_id($input['occurrence_id'] ?? null, 'occurrence_id');
@@ -505,3 +681,42 @@ function market_management_delete_cost_item(PDO $pdo, int $costItemId): array
}
return $item;
}
function market_management_update_cost_item(PDO $pdo, int $costItemId, array $input): array
{
$sets = [];
$params = [':id' => $costItemId];
if (array_key_exists('cost_type', $input)) {
$costType = trim((string) $input['cost_type']);
if ($costType === '') {
throw new InvalidArgumentException('cost_type darf nicht leer sein.');
}
$sets[] = 'cost_type = :cost_type';
$params[':cost_type'] = $costType;
}
foreach (['description' => 'string', 'gross_amount' => 'number', 'tax_status' => 'string', 'tax_rate' => 'number'] as $field => $type) {
if (!array_key_exists($field, $input)) {
continue;
}
$sets[] = $field . ' = :' . $field;
$params[':' . $field] = $type === 'number'
? market_management_optional_number($input, $field)
: market_management_optional_string($input, $field);
}
if (isset($params[':tax_status']) && !in_array($params[':tax_status'], ['tax_free', 'taxable'], true)) {
throw new InvalidArgumentException('Ungültiger Steuerstatus.');
}
if ($sets === []) {
throw new InvalidArgumentException('Mindestens ein änderbares Feld ist erforderlich.');
}
$sets[] = 'updated_at = NOW()';
$stmt = $pdo->prepare(
'UPDATE market_occurrence_cost_item SET ' . implode(', ', $sets) . ' WHERE id = :id RETURNING *'
);
$stmt->execute($params);
$item = $stmt->fetch();
if (!is_array($item)) {
throw new RuntimeException('Kostenposition nicht gefunden.');
}
return $item;
}