Fix occurrence MCP schema and expose product sales

This commit is contained in:
gitea_admin committed 2026-10-12 21:47:12 +02:00
1 parent d871b94c52
commit b8ba2db7a5
4 files changed
+51 -4

No files matched your search

@@ -173,10 +173,11 @@ Kategorien anlegen oder ändern.
- erforderlich: `market_definition_id`, `days` mit mindestens einem Markttag
- initialer Status: `open`
- weitere Marktdurchführungsfelder sind optional und werden typisiert validiert
- legt automatisch eine Standard-Checkliste mit den vier festen Kapiteln an
- `market_management.get_occurrence`
- erforderlich: `occurrence_id`
- liefert zusätzlich den Markt, Markttage, Kosten und
berechnete Tages- und Gesamterlöse
berechnete Tages- und Gesamterlöse sowie `sales_items` je Markttag
- `market_management.get_occurrence_checklist`
- erforderlich: `occurrence_id`
- liefert die vier festen Kapitel, die Punkte mit `chapter_key` und den
@@ -323,12 +324,14 @@ Weitere mögliche Tagesfelder sind `stand_start_time`, `stand_end_time`,
- Hermes darf keine Kategorien anlegen.
- Umsatz und Artikelmengen werden nicht über MCP geschrieben; sie werden aus
den OTC-Verkäufen abgeleitet.
- `get_occurrence` liefert `sales_items` mit `day_id`, `date`, `product_id`,
`sku`, `product_name`, `quantity`, `gross_revenue` und `unit_price`.
- Markt und Marktdurchführung werden unabhängig archiviert.
- Alle Schreibvorgänge werden serverseitig validiert.
- Jeder schreibende MCP-Aufruf benötigt `idempotency_key`; optional kann
`client_id` mitgegeben werden. Wiederholungen liefern dasselbe Ergebnis.
- Umsatz- und Artikelmengen aus OTC-Verkäufen sind erst verfügbar, sobald die
separate OTC-Schnittstelle die Zuordnung zum Markttag bereitstellt.
- Die gelieferten Verkaufspositionen stammen ausschließlich aus OTC-Verkäufen,
die einem konkreten Markttag zugeordnet sind.
## Fehler
+1 -1
View File
@@ -85,7 +85,7 @@ function market_management_mcp_tools(): array
['name' => 'market_management.list_reference_data', 'description' => 'Kategorien, Kantone und Gemeinden lesen', 'inputSchema' => ['type' => 'object', 'properties' => (object) []]],
['name' => 'market_management.create_municipality', 'description' => 'Lokale Gemeinde im Markt-Gemeinderegister anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name'], 'properties' => ['name' => ['type' => 'string']]]],
['name' => 'market_management.get_overview', 'description' => 'Marktübersicht mit serverseitigen Filtern lesen', 'inputSchema' => ['type' => 'object', 'properties' => $searchProperties]],
['name' => 'market_management.create_occurrence', 'description' => 'Marktdurchführung mit mindestens einem Markttag anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['market_definition_id', 'days'], 'properties' => ['market_definition_id' => ['type' => 'integer'], 'status' => ['type' => 'string'], 'days' => ['type' => 'array', 'minItems' => 1, 'items' => ['type' => 'object', 'required' => ['date'], 'properties' => $dayProperties]] + $occurrenceProperties]]],
['name' => 'market_management.create_occurrence', 'description' => 'Marktdurchführung mit mindestens einem Markttag anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['market_definition_id', 'days'], 'properties' => ['market_definition_id' => ['type' => 'integer'], 'status' => ['type' => 'string']] + $occurrenceProperties + ['days' => ['type' => 'array', 'minItems' => 1, 'items' => ['type' => 'object', 'required' => ['date'], 'properties' => $dayProperties]]]]],
['name' => 'market_management.get_occurrence', 'description' => 'Marktdurchführung mit Markt, Markttagen und berechneten Verkaufsdaten lesen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id'], 'properties' => ['occurrence_id' => ['type' => 'integer'], 'include_archived' => ['type' => 'boolean']]]],
['name' => 'market_management.get_occurrence_checklist', 'description' => 'Checkliste einer Marktdurchführung lesen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id'], 'properties' => ['occurrence_id' => ['type' => 'integer'], 'include_archived' => ['type' => 'boolean']]]],
['name' => 'market_management.update_occurrence_checklist', 'description' => 'Checkliste einer Marktdurchführung vollständig ersetzen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id', 'items'], 'properties' => ['occurrence_id' => ['type' => 'integer'], 'comment' => ['type' => ['string', 'null']], 'items' => ['type' => 'array', 'items' => ['type' => 'object', 'required' => ['chapter_key', 'label', 'is_completed'], 'properties' => ['chapter_key' => ['type' => 'string', 'enum' => ['products_sales', 'decoration', 'stand_equipment', 'technology']], 'label' => ['type' => 'string'], 'is_completed' => ['type' => 'boolean']]]]]]],
+39
View File
@@ -1040,6 +1040,45 @@ function market_management_get_occurrence(PDO $pdo, int $occurrenceId, bool $inc
$occurrenceSalesCount += (int) $salesRow['sales_count'];
}
unset($day);
$salesItemsStmt = $pdo->prepare(
'SELECT so.market_occurrence_day_id AS day_id,
d.date,
p.id AS product_id,
p.sku,
p.name AS product_name,
SUM(GREATEST(sol.qty - sol.qty_cancelled, 0) * sic.qty_per_item) AS quantity,
SUM(CASE WHEN sol.line_status = \'cancelled\' OR sol.line_total IS NULL THEN 0
ELSE sol.line_total * GREATEST(sol.qty - sol.qty_cancelled, 0) / NULLIF(sol.qty, 0) END) AS gross_revenue
FROM sales_order so
INNER JOIN sales_order_line sol ON sol.sales_order_id = so.id
INNER JOIN sellable_item_component sic ON sic.sellable_item_id = sol.sellable_item_id
INNER JOIN product p ON p.id = sic.product_id
INNER JOIN market_occurrence_day d ON d.id = so.market_occurrence_day_id
WHERE so.market_occurrence_id = :occurrence_id
AND so.market_occurrence_day_id IS NOT NULL
AND so.order_source = \'direct\'
AND so.order_status <> \'cancelled\'
GROUP BY so.market_occurrence_day_id, d.date, p.id, p.sku, p.name
ORDER BY d.date ASC, p.sku ASC, p.id ASC'
);
$salesItemsStmt->execute([':occurrence_id' => $occurrenceId]);
$occurrence['sales_items'] = array_map(
static function (array $item): array {
$quantity = (float) $item['quantity'];
$grossRevenue = (float) $item['gross_revenue'];
return [
'day_id' => (int) $item['day_id'],
'date' => (string) $item['date'],
'product_id' => (int) $item['product_id'],
'sku' => (string) $item['sku'],
'product_name' => (string) $item['product_name'],
'quantity' => $quantity,
'gross_revenue' => $grossRevenue,
'unit_price' => $quantity > 0 ? $grossRevenue / $quantity : null,
];
},
$salesItemsStmt->fetchAll()
);
$occurrence['sales_summary'] = $occurrenceSalesCount === 0
? [
'sales_count' => 0,
@@ -74,6 +74,11 @@ assert_test(count($tools) === 23, 'Expected 23 MCP tools, got ' . count($tools))
$occurrenceTool = array_values(array_filter($tools, static fn (array $tool): bool => $tool['name'] === 'market_management.update_occurrence'))[0] ?? null;
assert_test(is_array($occurrenceTool), 'Update occurrence MCP tool must exist');
assert_test(isset($occurrenceTool['inputSchema']['properties']['registration_note']), 'MCP occurrence schema must expose registration_note');
$createOccurrenceTool = array_values(array_filter($tools, static fn (array $tool): bool => $tool['name'] === 'market_management.create_occurrence'))[0] ?? null;
assert_test(is_array($createOccurrenceTool), 'Create occurrence MCP tool must exist');
$createProperties = $createOccurrenceTool['inputSchema']['properties'];
assert_test(isset($createProperties['place'], $createProperties['days']['items']['properties']['date']), 'Create occurrence schema must expose both levels');
assert_test(!isset($createProperties['days']['items']['properties']['place']), 'Occurrence fields must not be nested under days');
$actual = [];
foreach ($tools as $tool) {