Vervollstaendige Marktmanagement-MCP-Abdeckung
This commit is contained in:
1 parent
3267a121de
commit
9bde337506
4 files changed
+348
-18
No files matched your search
@@ -0,0 +1,6 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE market_mcp_idempotency
|
||||
ADD COLUMN result_payload JSONB;
|
||||
|
||||
COMMIT;
|
||||
@@ -32,13 +32,16 @@ function market_management_mcp_tools(): array
|
||||
{
|
||||
return [
|
||||
['name' => 'market_management.search_markets', 'description' => 'Märkte suchen', 'inputSchema' => ['type' => 'object']],
|
||||
['name' => 'market_management.search_occurrences', 'description' => 'Durchführungen suchen und auflisten', 'inputSchema' => ['type' => 'object']],
|
||||
['name' => 'market_management.get_market', 'description' => 'Markt mit Durchführungen lesen', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.create_market', 'description' => 'Markt mit bestehender Kategorie anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name', 'category_id']]],
|
||||
['name' => 'market_management.update_market', 'description' => 'Markt ändern', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.archive_market', 'description' => 'Markt archivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.reactivate_market', 'description' => 'Markt reaktivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.list_categories', 'description' => 'Bestehende Kategorien lesen', 'inputSchema' => ['type' => 'object']],
|
||||
['name' => 'market_management.create_occurrence', 'description' => 'Durchführung anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['market_definition_id']]],
|
||||
['name' => 'market_management.list_reference_data', 'description' => 'Kategorien, Kantone und Gemeinden lesen', 'inputSchema' => ['type' => 'object']],
|
||||
['name' => 'market_management.get_overview', 'description' => 'Marktübersicht mit serverseitigen Filtern lesen', 'inputSchema' => ['type' => 'object']],
|
||||
['name' => 'market_management.create_occurrence', 'description' => 'Durchführung mit mindestens einem Tag anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['market_definition_id', 'days']]],
|
||||
['name' => 'market_management.get_occurrence', 'description' => 'Durchführung mit Tagen lesen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
['name' => 'market_management.update_occurrence', 'description' => 'Durchführung ändern', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
['name' => 'market_management.archive_occurrence', 'description' => 'Durchführung archivieren', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
@@ -47,15 +50,107 @@ function market_management_mcp_tools(): array
|
||||
['name' => 'market_management.update_day', 'description' => 'Durchführungstag ändern', 'inputSchema' => ['type' => 'object', 'required' => ['day_id']]],
|
||||
['name' => 'market_management.delete_day', 'description' => 'Durchführungstag entfernen', 'inputSchema' => ['type' => 'object', 'required' => ['day_id']]],
|
||||
['name' => 'market_management.set_organizer', 'description' => 'Veranstalter einer Durchführung setzen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id', 'organization_contact_id', 'contact_person_id']]],
|
||||
['name' => 'market_management.delete_organizer', 'description' => 'Veranstalter einer Durchführung entfernen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
['name' => 'market_management.create_cost_item', 'description' => 'Kostenposition anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id', 'cost_type', 'gross_amount', 'tax_status']]],
|
||||
['name' => 'market_management.update_cost_item', 'description' => 'Kostenposition ändern', 'inputSchema' => ['type' => 'object', 'required' => ['cost_item_id']]],
|
||||
['name' => 'market_management.delete_cost_item', 'description' => 'Kostenposition entfernen', 'inputSchema' => ['type' => 'object', 'required' => ['cost_item_id']]],
|
||||
];
|
||||
}
|
||||
|
||||
function market_management_mcp_is_write_tool(string $tool): bool
|
||||
{
|
||||
return in_array($tool, [
|
||||
'market_management.create_market', 'market_management.update_market',
|
||||
'market_management.archive_market', 'market_management.reactivate_market',
|
||||
'market_management.create_occurrence', 'market_management.update_occurrence',
|
||||
'market_management.archive_occurrence', 'market_management.reactivate_occurrence',
|
||||
'market_management.create_day', 'market_management.update_day', 'market_management.delete_day',
|
||||
'market_management.set_organizer', 'market_management.delete_organizer',
|
||||
'market_management.create_cost_item', 'market_management.update_cost_item',
|
||||
'market_management.delete_cost_item',
|
||||
], true);
|
||||
}
|
||||
|
||||
function market_management_mcp_call_idempotent(PDO $pdo, string $userSubject, string $tool, array $arguments): array
|
||||
{
|
||||
if (!market_management_mcp_is_write_tool($tool)) {
|
||||
return market_management_mcp_call($pdo, $tool, $arguments);
|
||||
}
|
||||
|
||||
$idempotencyKey = trim((string) ($arguments['idempotency_key'] ?? ''));
|
||||
if ($idempotencyKey === '') {
|
||||
throw new InvalidArgumentException('Schreibende MCP-Aufrufe benötigen idempotency_key.');
|
||||
}
|
||||
$clientId = trim((string) ($arguments['client_id'] ?? 'hermes'));
|
||||
if ($clientId === '') {
|
||||
throw new InvalidArgumentException('client_id darf nicht leer sein.');
|
||||
}
|
||||
$businessArguments = $arguments;
|
||||
unset($businessArguments['idempotency_key'], $businessArguments['client_id']);
|
||||
$requestHash = hash('sha256', json_encode([
|
||||
'tool' => $tool,
|
||||
'arguments' => $businessArguments,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$existingStmt = $pdo->prepare(
|
||||
'SELECT user_subject, request_hash, result_status, result_payload
|
||||
FROM market_mcp_idempotency
|
||||
WHERE client_id = :client_id AND idempotency_key = :idempotency_key
|
||||
FOR UPDATE'
|
||||
);
|
||||
$existingStmt->execute([':client_id' => $clientId, ':idempotency_key' => $idempotencyKey]);
|
||||
$existing = $existingStmt->fetch();
|
||||
if (is_array($existing)) {
|
||||
if ((string) $existing['user_subject'] !== $userSubject || (string) $existing['request_hash'] !== $requestHash) {
|
||||
throw new InvalidArgumentException('Idempotency-Schlüssel wurde bereits für eine andere Anfrage verwendet.');
|
||||
}
|
||||
if ((string) $existing['result_status'] === 'succeeded' && $existing['result_payload'] !== null) {
|
||||
$pdo->commit();
|
||||
return (array) json_decode((string) $existing['result_payload'], true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
throw new RuntimeException('Idempotency-Anfrage wird bereits verarbeitet oder ist fehlgeschlagen.');
|
||||
}
|
||||
|
||||
$insert = $pdo->prepare(
|
||||
'INSERT INTO market_mcp_idempotency
|
||||
(user_subject, client_id, idempotency_key, tool_name, request_hash, result_status, expires_at)
|
||||
VALUES (:user_subject, :client_id, :idempotency_key, :tool_name, :request_hash, \'processing\', NOW() + INTERVAL \'24 hours\')'
|
||||
);
|
||||
$insert->execute([
|
||||
':user_subject' => $userSubject,
|
||||
':client_id' => $clientId,
|
||||
':idempotency_key' => $idempotencyKey,
|
||||
':tool_name' => $tool,
|
||||
':request_hash' => $requestHash,
|
||||
]);
|
||||
$result = market_management_mcp_call($pdo, $tool, $businessArguments);
|
||||
$update = $pdo->prepare(
|
||||
'UPDATE market_mcp_idempotency
|
||||
SET result_status = \'succeeded\', result_payload = CAST(:result_payload AS JSONB)
|
||||
WHERE client_id = :client_id AND idempotency_key = :idempotency_key'
|
||||
);
|
||||
$update->execute([
|
||||
':result_payload' => json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
|
||||
':client_id' => $clientId,
|
||||
':idempotency_key' => $idempotencyKey,
|
||||
]);
|
||||
$pdo->commit();
|
||||
return $result;
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function market_management_mcp_call(PDO $pdo, string $tool, array $arguments): array
|
||||
{
|
||||
return match ($tool) {
|
||||
'market_management.search_markets' => market_management_search_markets($pdo, $arguments),
|
||||
'market_management.search_occurrences' => market_management_search_occurrences($pdo, $arguments),
|
||||
'market_management.get_market' => market_management_get_market(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['market_id'] ?? null, 'market_id'),
|
||||
@@ -78,6 +173,11 @@ function market_management_mcp_call(PDO $pdo, string $tool, array $arguments): a
|
||||
false
|
||||
),
|
||||
'market_management.list_categories' => market_management_list_categories($pdo),
|
||||
'market_management.list_reference_data' => market_management_list_reference_data($pdo, $arguments),
|
||||
'market_management.get_overview' => [
|
||||
'markets' => market_management_search_markets($pdo, $arguments),
|
||||
'occurrences' => market_management_search_occurrences($pdo, $arguments),
|
||||
],
|
||||
'market_management.create_occurrence' => market_management_create_occurrence($pdo, $arguments),
|
||||
'market_management.get_occurrence' => market_management_get_occurrence(
|
||||
$pdo,
|
||||
@@ -110,7 +210,16 @@ function market_management_mcp_call(PDO $pdo, string $tool, array $arguments): a
|
||||
market_management_require_positive_id($arguments['day_id'] ?? null, 'day_id')
|
||||
),
|
||||
'market_management.set_organizer' => market_management_set_organizer($pdo, $arguments),
|
||||
'market_management.delete_organizer' => market_management_delete_organizer(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['occurrence_id'] ?? null, 'occurrence_id')
|
||||
),
|
||||
'market_management.create_cost_item' => market_management_create_cost_item($pdo, $arguments),
|
||||
'market_management.update_cost_item' => market_management_update_cost_item(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['cost_item_id'] ?? null, 'cost_item_id'),
|
||||
$arguments
|
||||
),
|
||||
'market_management.delete_cost_item' => market_management_delete_cost_item(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['cost_item_id'] ?? null, 'cost_item_id')
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ try {
|
||||
$result = [
|
||||
'content' => [[
|
||||
'type' => 'text',
|
||||
'text' => json_encode(market_management_mcp_call($pdo, $tool, $arguments), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
|
||||
'text' => json_encode(market_management_mcp_call_idempotent($pdo, $userSubject, $tool, $arguments), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
|
||||
]],
|
||||
'isError' => false,
|
||||
];
|
||||
|
||||
Reference in new issue
Block a user