Veranstalter strikt von Lieferanten trennen

This commit is contained in:
gitea_admin committed 2026-09-20 11:04:13 +02:00
1 parent 0e71aca89b
commit 36fb17ac32
9 files changed
+114 -152

No files matched your search

@@ -1,44 +1,33 @@
BEGIN;
ALTER TABLE party_role
DROP CONSTRAINT chk_party_role_code;
CREATE TABLE market_organizer (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT,
phone TEXT,
status TEXT NOT NULL DEFAULT 'active',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT chk_market_organizer_status CHECK (status IN ('active', 'inactive'))
);
ALTER TABLE party_role
ADD CONSTRAINT chk_party_role_code
CHECK (role_code IN ('manufacturer', 'market_organizer'));
CREATE UNIQUE INDEX uq_market_organizer_name ON market_organizer (LOWER(TRIM(name)));
INSERT INTO market_organizer (name)
VALUES ('Beispielveranstalter 1');
ALTER TABLE market_definition
ADD COLUMN organizer_party_id BIGINT;
ADD COLUMN organizer_id BIGINT;
UPDATE market_definition m
SET organizer_party_id = source.organization_contact_id
FROM (
SELECT o.market_definition_id, MIN(oo.organization_contact_id) AS organization_contact_id
FROM market_occurrence o
INNER JOIN market_occurrence_organizer oo ON oo.occurrence_id = o.id
GROUP BY o.market_definition_id
) source
WHERE source.market_definition_id = m.id;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM market_definition WHERE organizer_party_id IS NULL) THEN
RAISE EXCEPTION 'Alle bestehenden Märkte benötigen vor der Migration einen Veranstalter.';
END IF;
END $$;
UPDATE market_definition
SET organizer_id = (SELECT id FROM market_organizer WHERE name = 'Beispielveranstalter 1');
ALTER TABLE market_definition
ALTER COLUMN organizer_party_id SET NOT NULL,
ADD CONSTRAINT fk_market_definition_organizer_party
FOREIGN KEY (organizer_party_id) REFERENCES party(id) ON DELETE RESTRICT;
ALTER COLUMN organizer_id SET NOT NULL,
ADD CONSTRAINT fk_market_definition_organizer
FOREIGN KEY (organizer_id) REFERENCES market_organizer(id) ON DELETE RESTRICT;
CREATE INDEX idx_market_definition_organizer_party
ON market_definition(organizer_party_id);
INSERT INTO party_role (party_id, role_code)
SELECT DISTINCT organizer_party_id, 'market_organizer'
FROM market_definition
ON CONFLICT (party_id, role_code) DO NOTHING;
CREATE INDEX idx_market_definition_organizer ON market_definition (organizer_id);
ALTER TABLE market_occurrence_organizer
ADD COLUMN contact_person_name TEXT,
@@ -52,6 +41,10 @@ SET contact_person_name = NULLIF(TRIM(CONCAT_WS(' ', c.first_name, c.last_name))
FROM contact c
WHERE c.id = oo.contact_person_id;
ALTER TABLE market_occurrence_organizer
DROP COLUMN organization_contact_id,
DROP COLUMN contact_person_id;
INSERT INTO market_occurrence_organizer (occurrence_id, contact_person_name, created_at, updated_at)
SELECT o.id, NULL, NOW(), NOW()
FROM market_occurrence o
@@ -59,8 +52,4 @@ WHERE NOT EXISTS (
SELECT 1 FROM market_occurrence_organizer oo WHERE oo.occurrence_id = o.id
);
ALTER TABLE market_occurrence_organizer
DROP COLUMN organization_contact_id,
DROP COLUMN contact_person_id;
COMMIT;
@@ -97,7 +97,7 @@ Das Ergebnis steht bei `tools/call` als JSON im Textinhalt des ersten
- optional: `include_archived`
- liefert Marktdurchführungen inklusive Markttagen, Kosten und berechneten Verkaufswerten
- `market_management.create_market`
- erforderlich: `name`, `category_id`, `organizer_party_id`
- erforderlich: `name`, `category_id`, `organizer_id`
- `market_management.list_market_organizers`
- liefert aktive Veranstalterorganisationen
- `market_management.create_market_organizer`
@@ -178,7 +178,7 @@ Weitere mögliche Tagesfelder sind `stand_start_time`, `stand_end_time`,
### Veranstalter und Kosten
- Veranstalter werden am Markt über `organizer_party_id` gesetzt und von den
- Veranstalter werden am Markt über `organizer_id` gesetzt und von den
Durchführungen geerbt. Marktdurchführungen können nur `contact_person_name`,
`contact_email` und `contact_phone` ändern.
- `market_management.delete_organizer`
@@ -84,7 +84,7 @@ Normativ:
- jeder Markt besitzt genau einen eindeutigen Namen
- jeder Markt besitzt genau eine Kategorie
- jeder Markt besitzt genau einen verpflichtenden Veranstalter aus dem Kontaktmodul
- jeder Markt besitzt genau einen verpflichtenden Veranstalter aus dem eigenen Markt-Veranstalterregister
- jeder Markt kann eine optionale Website-URL der Veranstaltung besitzen
- der Markt enthält keine Durchführungs-, Kontakt-, Kosten-, Zahlungs- oder
Erlösdaten
@@ -119,8 +119,8 @@ Normativ:
- die Tabelle enthält die durchführungsbezogene Kontaktperson als Freitext sowie optionale E-Mail und Telefonnummer
- jede Durchführung besitzt genau einen lokalen Kontaktdatensatz
- der Veranstalter selbst wird ausschließlich über `market_definition.organizer_party_id` vom Markt geerbt
- Veranstalterorganisationen werden im Kontaktmodul über die Rolle `market_organizer` verwaltet
- der Veranstalter selbst wird ausschließlich über `market_definition.organizer_id` vom Markt geerbt
- Veranstalter werden ausschließlich im Markt-Veranstalterregister verwaltet und nicht aus Lieferanten oder Kunden abgeleitet
### 3.5 `market_occurrence_day`
-70
View File
@@ -166,76 +166,6 @@ function get_party_options(PDO $pdo): array
);
}
function get_market_organizer_options(PDO $pdo): array
{
$stmt = $pdo->query(
"SELECT p.id, p.name, p.email, p.phone
FROM party p
INNER JOIN party_role pr ON pr.party_id = p.id AND pr.role_code = 'market_organizer'
WHERE p.status = 'active'
ORDER BY p.name ASC, p.id ASC"
);
return $stmt->fetchAll() ?: [];
}
function get_market_organizer_reference(PDO $pdo, int $partyId): ?array
{
$stmt = $pdo->prepare(
"SELECT p.id, p.name, p.email, p.phone
FROM party p
INNER JOIN party_role pr ON pr.party_id = p.id AND pr.role_code = 'market_organizer'
WHERE p.id = :id AND p.status = 'active'"
);
$stmt->execute([':id' => $partyId]);
$party = $stmt->fetch();
return is_array($party) ? $party : null;
}
function create_market_organizer(PDO $pdo, string $name, ?string $email = null, ?string $phone = null): array
{
$name = trim($name);
$email = $email !== null ? trim($email) : null;
$phone = $phone !== null ? trim($phone) : null;
if ($name === '') {
throw new InvalidArgumentException('Veranstaltername fehlt.');
}
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare(
"SELECT id, name, email, phone FROM party WHERE LOWER(TRIM(name)) = LOWER(:name) LIMIT 1 FOR UPDATE"
);
$stmt->execute([':name' => $name]);
$party = $stmt->fetch();
if (!is_array($party)) {
$stmt = $pdo->prepare(
"INSERT INTO party (type, name, email, phone, status, created_at, updated_at)
VALUES ('customer', :name, :email, :phone, 'active', NOW(), NOW())
RETURNING id, name, email, phone"
);
$stmt->execute([':name' => $name, ':email' => $email ?: null, ':phone' => $phone ?: null]);
$party = $stmt->fetch();
}
if (!is_array($party)) {
throw new RuntimeException('Veranstalter konnte nicht angelegt werden.');
}
$role = $pdo->prepare(
"INSERT INTO party_role (party_id, role_code) VALUES (:party_id, 'market_organizer')
ON CONFLICT (party_id, role_code) DO NOTHING"
);
$role->execute([':party_id' => (int) $party['id']]);
$pdo->commit();
return (array) $party;
} catch (Throwable $error) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $error;
}
}
function get_contact_options(PDO $pdo): array
{
$stmt = $pdo->query(
+4 -4
View File
@@ -73,8 +73,8 @@ function market_management_mcp_tools(): array
['name' => 'market_management.search_markets', 'description' => 'Märkte suchen', 'inputSchema' => ['type' => 'object', 'properties' => $searchProperties]],
['name' => 'market_management.search_occurrences', 'description' => 'Marktdurchführungen suchen und auflisten', 'inputSchema' => ['type' => 'object', 'properties' => $searchProperties]],
['name' => 'market_management.get_market', 'description' => 'Markt mit Marktdurchführungen lesen', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer'], 'include_archived' => ['type' => 'boolean']]]],
['name' => 'market_management.create_market', 'description' => 'Markt mit verpflichtendem Veranstalter, Kategorie und optionaler Website-URL anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name', 'category_id', 'organizer_party_id'], 'properties' => ['name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_party_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]],
['name' => 'market_management.update_market', 'description' => 'Markt inklusive Veranstalter und Website-URL ändern', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer'], 'name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_party_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]],
['name' => 'market_management.create_market', 'description' => 'Markt mit verpflichtendem Veranstalter, Kategorie und optionaler Website-URL anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name', 'category_id', 'organizer_id'], 'properties' => ['name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]],
['name' => 'market_management.update_market', 'description' => 'Markt inklusive Veranstalter und Website-URL ändern', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer'], 'name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]],
['name' => 'market_management.archive_market', 'description' => 'Markt archivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer']]]],
['name' => 'market_management.reactivate_market', 'description' => 'Markt reaktivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer']]]],
['name' => 'market_management.list_categories', 'description' => 'Bestehende Kategorien lesen', 'inputSchema' => ['type' => 'object', 'properties' => []]],
@@ -268,8 +268,8 @@ 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_market_organizers' => get_market_organizer_options($pdo),
'market_management.create_market_organizer' => create_market_organizer(
'market_management.list_market_organizers' => market_management_list_organizers($pdo),
'market_management.create_market_organizer' => market_management_create_organizer(
$pdo,
(string) ($arguments['name'] ?? ''),
$arguments['email'] ?? null,
+70 -26
View File
@@ -1,8 +1,6 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../erp/kontakte/service.php';
require_once __DIR__ . '/../shared/db.php';
const MARKET_MANAGEMENT_STATUSES = [
@@ -20,6 +18,43 @@ const MARKET_OCCURRENCE_DAY_INHERITED_FIELDS = [
'delivery_start_time', 'delivery_end_time',
];
function market_management_list_organizers(PDO $pdo): array
{
return $pdo->query(
"SELECT id, name, email, phone
FROM market_organizer
WHERE status = 'active'
ORDER BY name ASC, id ASC"
)->fetchAll() ?: [];
}
function market_management_get_organizer(PDO $pdo, int $organizerId): ?array
{
$stmt = $pdo->prepare(
"SELECT id, name, email, phone
FROM market_organizer
WHERE id = :id AND status = 'active'"
);
$stmt->execute([':id' => $organizerId]);
$organizer = $stmt->fetch();
return is_array($organizer) ? $organizer : null;
}
function market_management_create_organizer(PDO $pdo, string $name, ?string $email = null, ?string $phone = null): array
{
$name = trim($name);
if ($name === '') {
throw new InvalidArgumentException('Veranstaltername fehlt.');
}
$stmt = $pdo->prepare(
'INSERT INTO market_organizer (name, email, phone)
VALUES (:name, :email, :phone)
RETURNING id, name, email, phone'
);
$stmt->execute([':name' => $name, ':email' => $email ?: null, ':phone' => $phone ?: null]);
return (array) $stmt->fetch();
}
function market_management_require_positive_id(mixed $value, string $field): int
{
if (!is_int($value) && !ctype_digit((string) $value)) {
@@ -212,7 +247,7 @@ function market_management_create_market(PDO $pdo, array $input): array
{
$name = trim((string) ($input['name'] ?? ''));
$categoryId = market_management_require_positive_id($input['category_id'] ?? null, 'category_id');
$organizerPartyId = market_management_require_positive_id($input['organizer_party_id'] ?? null, 'organizer_party_id');
$organizerId = market_management_require_positive_id($input['organizer_id'] ?? null, 'organizer_id');
$websiteUrl = market_management_optional_url($input, 'website_url');
if ($name === '') {
@@ -221,16 +256,16 @@ function market_management_create_market(PDO $pdo, array $input): array
if (!market_management_category_exists($pdo, $categoryId)) {
throw new InvalidArgumentException('category_id verweist nicht auf eine aktive Kategorie.');
}
if (get_market_organizer_reference($pdo, $organizerPartyId) === null) {
throw new InvalidArgumentException('organizer_party_id verweist nicht auf einen aktiven Veranstalter.');
if (market_management_get_organizer($pdo, $organizerId) === null) {
throw new InvalidArgumentException('organizer_id verweist nicht auf einen aktiven Veranstalter.');
}
$stmt = $pdo->prepare(
'INSERT INTO market_definition (name, category_id, organizer_party_id, website_url)
VALUES (:name, :category_id, :organizer_party_id, :website_url)
RETURNING id, name, category_id, organizer_party_id, website_url, archived_at, created_at, updated_at'
'INSERT INTO market_definition (name, category_id, organizer_id, website_url)
VALUES (:name, :category_id, :organizer_id, :website_url)
RETURNING id, name, category_id, organizer_id, website_url, archived_at, created_at, updated_at'
);
$stmt->execute([':name' => $name, ':category_id' => $categoryId, ':organizer_party_id' => $organizerPartyId, ':website_url' => $websiteUrl]);
$stmt->execute([':name' => $name, ':category_id' => $categoryId, ':organizer_id' => $organizerId, ':website_url' => $websiteUrl]);
return (array) $stmt->fetch();
}
@@ -298,18 +333,18 @@ function market_management_search_markets(PDO $pdo, array $input): array
}
$stmt = $pdo->prepare(
'SELECT m.id, m.name, m.category_id, m.organizer_party_id, m.website_url, c.code AS category_code, c.name AS category_name,
'SELECT m.id, m.name, m.category_id, m.organizer_id, m.website_url, c.code AS category_code, c.name AS category_name,
p.name AS organizer_name,
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
INNER JOIN party p ON p.id = m.organizer_party_id
INNER JOIN market_organizer p ON p.id = m.organizer_id
LEFT JOIN market_occurrence o ON o.market_definition_id = m.id AND ' . $occurrenceArchivePredicate . '
LEFT JOIN market_occurrence_day d ON d.occurrence_id = o.id
WHERE ' . ($where === [] ? 'TRUE' : implode(' AND ', $where)) . '
GROUP BY m.id, m.category_id, m.organizer_party_id, c.code, c.name, p.name
GROUP BY m.id, m.category_id, m.organizer_id, c.code, c.name, p.name
ORDER BY ' . $sortColumn . ' ' . $sortDirection . ', m.id ASC
LIMIT :limit OFFSET :offset'
);
@@ -324,12 +359,12 @@ function market_management_get_market(PDO $pdo, int $marketId, bool $includeArch
{
$marketArchivePredicate = $includeArchived ? 'TRUE' : 'm.archived_at IS NULL';
$stmt = $pdo->prepare(
'SELECT m.id, m.name, m.category_id, m.organizer_party_id, m.website_url, c.code AS category_code, c.name AS category_name,
'SELECT m.id, m.name, m.category_id, m.organizer_id, m.website_url, c.code AS category_code, c.name AS category_name,
p.name AS organizer_name,
m.archived_at, m.created_at, m.updated_at
FROM market_definition m
INNER JOIN market_category c ON c.id = m.category_id
INNER JOIN party p ON p.id = m.organizer_party_id
INNER JOIN market_organizer p ON p.id = m.organizer_id
WHERE m.id = :id AND ' . $marketArchivePredicate
);
$stmt->execute([':id' => $marketId]);
@@ -367,7 +402,7 @@ function market_management_get_market(PDO $pdo, int $marketId, bool $includeArch
function market_management_list_active_market_definitions(PDO $pdo): array
{
$stmt = $pdo->query(
'SELECT m.id, m.name, m.category_id, m.organizer_party_id, c.name AS category_name
'SELECT m.id, m.name, m.category_id, m.organizer_id, c.name AS category_name
FROM market_definition m
INNER JOIN market_category c ON c.id = m.category_id
WHERE m.archived_at IS NULL
@@ -501,13 +536,13 @@ function market_management_update_market(PDO $pdo, int $marketId, array $input):
$sets[] = 'category_id = :category_id';
$params[':category_id'] = $categoryId;
}
if (array_key_exists('organizer_party_id', $input)) {
$organizerPartyId = market_management_require_positive_id($input['organizer_party_id'], 'organizer_party_id');
if (get_market_organizer_reference($pdo, $organizerPartyId) === null) {
throw new InvalidArgumentException('organizer_party_id verweist nicht auf einen aktiven Veranstalter.');
if (array_key_exists('organizer_id', $input)) {
$organizerId = market_management_require_positive_id($input['organizer_id'], 'organizer_id');
if (market_management_get_organizer($pdo, $organizerId) === null) {
throw new InvalidArgumentException('organizer_id verweist nicht auf einen aktiven Veranstalter.');
}
$sets[] = 'organizer_party_id = :organizer_party_id';
$params[':organizer_party_id'] = $organizerPartyId;
$sets[] = 'organizer_id = :organizer_id';
$params[':organizer_id'] = $organizerId;
}
if (array_key_exists('website_url', $input)) {
$sets[] = 'website_url = :website_url';
@@ -520,7 +555,7 @@ function market_management_update_market(PDO $pdo, int $marketId, array $input):
$sets[] = 'updated_at = NOW()';
$stmt = $pdo->prepare(
'UPDATE market_definition SET ' . implode(', ', $sets) . ' WHERE id = :id
RETURNING id, name, category_id, organizer_party_id, website_url, archived_at, created_at, updated_at'
RETURNING id, name, category_id, organizer_id, website_url, archived_at, created_at, updated_at'
);
$stmt->execute($params);
$market = $stmt->fetch();
@@ -536,7 +571,7 @@ function market_management_set_market_archive(PDO $pdo, int $marketId, bool $arc
'UPDATE market_definition
SET archived_at = ' . ($archive ? 'COALESCE(archived_at, NOW())' : 'NULL') . ', updated_at = NOW()
WHERE id = :id
RETURNING id, name, category_id, organizer_party_id, website_url, archived_at, created_at, updated_at'
RETURNING id, name, category_id, organizer_id, website_url, archived_at, created_at, updated_at'
);
$stmt->execute([':id' => $marketId]);
$market = $stmt->fetch();
@@ -748,7 +783,7 @@ function market_management_get_occurrence(PDO $pdo, int $occurrenceId, bool $inc
if (is_array($organizer)) {
$market = market_management_get_market($pdo, (int) $occurrence['market_definition_id'], true);
$organizer['organization'] = is_array($market)
? get_market_organizer_reference($pdo, (int) $market['organizer_party_id'])
? market_management_get_organizer($pdo, (int) $market['organizer_id'])
: null;
$organizer['contact_person'] = [
'first_name' => (string) ($organizer['contact_person_name'] ?? ''),
@@ -758,14 +793,23 @@ function market_management_get_occurrence(PDO $pdo, int $occurrenceId, bool $inc
];
}
$occurrence['organizer'] = is_array($organizer) ? $organizer : null;
$occurrence['organizer_party_id'] = is_array($occurrence['organizer']) && is_array($occurrence['organizer']['organization'] ?? null)
$occurrence['organizer_id'] = is_array($occurrence['organizer']) && is_array($occurrence['organizer']['organization'] ?? null)
? (int) $occurrence['organizer']['organization']['id']
: null;
$contactPersonName = (string) ($occurrence['organizer']['contact_person_name'] ?? '');
$occurrence['contact_person_id'] = $contactPersonName;
$referenceData = market_management_list_reference_data($pdo, []);
$occurrence['reference_options'] = [
'categories' => market_management_list_categories($pdo),
'municipalities' => $referenceData['municipalities'] ?? [],
'market_organizers' => get_market_organizer_options($pdo),
'market_organizers' => market_management_list_organizers($pdo),
'parties' => market_management_list_organizers($pdo),
'contacts' => $contactPersonName === '' ? [] : [[
'id' => $contactPersonName,
'first_name' => $contactPersonName,
'last_name' => '',
'party_name' => '',
]],
];
$costs = $pdo->prepare('SELECT * FROM market_occurrence_cost_item WHERE occurrence_id = :occurrence_id ORDER BY id ASC');
$costs->execute([':occurrence_id' => $occurrenceId]);
+4 -4
View File
@@ -187,7 +187,7 @@ function auth_render_market_definitions_content(array $marketTable): string
$marketName = (string) ($row['name'] ?? '');
$organizerName = (string) ($row['organizer_name'] ?? '');
$html[] = '<div class="' . $rowClass . '" role="row" data-component-part="large-table-row" data-market-definition-row data-market-definition-id="' . $marketId . '">';
$html[] = '<div class="sg-large-table__cell" role="cell"><a class="sg-hyperlink" href="#" aria-haspopup="dialog" aria-expanded="false" data-market-definition-open data-market-definition-id="' . $marketId . '" data-market-definition-name="' . auth_escape_html($marketName) . '" data-market-definition-category-id="' . (int) ($row['category_id'] ?? 0) . '" data-market-definition-category-name="' . auth_escape_html((string) ($row['category_name'] ?? $row['category_code'] ?? '')) . '" data-market-definition-organizer-id="' . (int) ($row['organizer_party_id'] ?? 0) . '" data-market-definition-organizer-name="' . auth_escape_html($organizerName) . '" data-market-definition-website="' . auth_escape_html($website) . '">' . auth_escape_html($marketName) . '</a></div>';
$html[] = '<div class="sg-large-table__cell" role="cell"><a class="sg-hyperlink" href="#" aria-haspopup="dialog" aria-expanded="false" data-market-definition-open data-market-definition-id="' . $marketId . '" data-market-definition-name="' . auth_escape_html($marketName) . '" data-market-definition-category-id="' . (int) ($row['category_id'] ?? 0) . '" data-market-definition-category-name="' . auth_escape_html((string) ($row['category_name'] ?? $row['category_code'] ?? '')) . '" data-market-definition-organizer-id="' . (int) ($row['organizer_id'] ?? 0) . '" data-market-definition-organizer-name="' . auth_escape_html($organizerName) . '" data-market-definition-website="' . auth_escape_html($website) . '">' . auth_escape_html($marketName) . '</a></div>';
$html[] = '<div class="sg-large-table__cell" role="cell">' . auth_escape_html((string) ($row['category_name'] ?? $row['category_code'] ?? '')) . '</div>';
$html[] = '<div class="sg-large-table__cell" role="cell">' . auth_escape_html($organizerName) . '</div>';
$html[] = '<div class="sg-large-table__cell" role="cell">' . auth_escape_html($websiteValue) . '</div>';
@@ -201,7 +201,7 @@ function auth_render_market_definitions_content(array $marketTable): string
foreach ($categories as $category) {
$html[] = '<option value="' . (int) ($category['id'] ?? 0) . '">' . auth_escape_html((string) ($category['name'] ?? $category['code'] ?? '')) . '</option>';
}
$html[] = '</select></label></div><div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Veranstalter<span class="market-required-field-indicator" aria-hidden="true">*</span></span><select class="sg-interaction-element sg-input-single-line sg-pulldown" name="organizer_party_id" required data-market-definition-organizer><option value="">Bitte auswählen</option>';
$html[] = '</select></label></div><div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Veranstalter<span class="market-required-field-indicator" aria-hidden="true">*</span></span><select class="sg-interaction-element sg-input-single-line sg-pulldown" name="organizer_id" required data-market-definition-organizer><option value="">Bitte auswählen</option>';
foreach ($organizers as $organizer) {
$html[] = '<option value="' . (int) ($organizer['id'] ?? 0) . '">' . auth_escape_html((string) ($organizer['name'] ?? '')) . '</option>';
}
@@ -1283,10 +1283,10 @@ JS;
echo " const openEditCard = () => { if (!activeMarket) { return; } const title = form.querySelector('[data-market-definition-form-title]'); if (title) { title.textContent = 'Markt bearbeiten'; } const name = form.querySelector('[data-market-definition-name]'); const category = form.querySelector('[data-market-definition-category]'); const organizer = form.querySelector('[data-market-definition-organizer]'); const website = form.querySelector('[data-market-definition-website]'); if (name) { name.value = activeMarket.trigger.dataset.marketDefinitionName || ''; } if (category) { category.value = activeMarket.trigger.dataset.marketDefinitionCategoryId || ''; } if (organizer) { organizer.value = activeMarket.trigger.dataset.marketDefinitionOrganizerId || ''; } if (website) { website.value = activeMarket.trigger.dataset.marketDefinitionWebsite === 'Nicht hinterlegt' ? '' : (activeMarket.trigger.dataset.marketDefinitionWebsite || ''); } syncNewOrganizerFields(); };";
echo " editButton.addEventListener('click', (event) => { event.preventDefault(); openEditCard(); });";
echo " form.querySelector('[data-market-definition-organizer]')?.addEventListener('change', syncNewOrganizerFields);";
echo " const appendMarketRow = (market) => { const table = contentRoot.querySelector('[data-market-definitions-table]'); if (!table) { return; } const row = document.createElement('div'); row.className = 'sg-card-segment sg-card-segment--body sg-large-table__row'; row.setAttribute('role', 'row'); row.dataset.componentPart = 'large-table-row'; row.dataset.marketDefinitionRow = ''; row.dataset.marketDefinitionId = String(market.id || ''); const category = form.querySelector('[data-market-definition-category]')?.selectedOptions[0]?.textContent || ''; const organizer = form.querySelector('[data-market-definition-organizer]')?.selectedOptions[0]?.textContent || ''; const website = market.website_url || 'Nicht hinterlegt'; const cells = document.createElement('div'); cells.className = 'sg-large-table__cell'; const trigger = document.createElement('a'); trigger.className = 'sg-hyperlink'; trigger.href = '#'; trigger.setAttribute('aria-haspopup', 'dialog'); trigger.setAttribute('aria-expanded', 'false'); trigger.dataset.marketDefinitionOpen = ''; trigger.dataset.marketDefinitionId = String(market.id || ''); trigger.dataset.marketDefinitionName = market.name || ''; trigger.dataset.marketDefinitionCategoryId = String(market.category_id || ''); trigger.dataset.marketDefinitionCategoryName = category; trigger.dataset.marketDefinitionOrganizerId = String(market.organizer_party_id || ''); trigger.dataset.marketDefinitionOrganizerName = organizer; trigger.dataset.marketDefinitionWebsite = website; trigger.textContent = market.name || ''; cells.appendChild(trigger); row.appendChild(cells); [category, organizer, website, '0', 'Aktiv'].forEach((value) => { const cell = document.createElement('div'); cell.className = 'sg-large-table__cell'; cell.setAttribute('role', 'cell'); cell.textContent = value; row.appendChild(cell); }); table.appendChild(row); };";
echo " const appendMarketRow = (market) => { const table = contentRoot.querySelector('[data-market-definitions-table]'); if (!table) { return; } const row = document.createElement('div'); row.className = 'sg-card-segment sg-card-segment--body sg-large-table__row'; row.setAttribute('role', 'row'); row.dataset.componentPart = 'large-table-row'; row.dataset.marketDefinitionRow = ''; row.dataset.marketDefinitionId = String(market.id || ''); const category = form.querySelector('[data-market-definition-category]')?.selectedOptions[0]?.textContent || ''; const organizer = form.querySelector('[data-market-definition-organizer]')?.selectedOptions[0]?.textContent || ''; const website = market.website_url || 'Nicht hinterlegt'; const cells = document.createElement('div'); cells.className = 'sg-large-table__cell'; const trigger = document.createElement('a'); trigger.className = 'sg-hyperlink'; trigger.href = '#'; trigger.setAttribute('aria-haspopup', 'dialog'); trigger.setAttribute('aria-expanded', 'false'); trigger.dataset.marketDefinitionOpen = ''; trigger.dataset.marketDefinitionId = String(market.id || ''); trigger.dataset.marketDefinitionName = market.name || ''; trigger.dataset.marketDefinitionCategoryId = String(market.category_id || ''); trigger.dataset.marketDefinitionCategoryName = category; trigger.dataset.marketDefinitionOrganizerId = String(market.organizer_id || ''); trigger.dataset.marketDefinitionOrganizerName = organizer; trigger.dataset.marketDefinitionWebsite = website; trigger.textContent = market.name || ''; cells.appendChild(trigger); row.appendChild(cells); [category, organizer, website, '0', 'Aktiv'].forEach((value) => { const cell = document.createElement('div'); cell.className = 'sg-large-table__cell'; cell.setAttribute('role', 'cell'); cell.textContent = value; row.appendChild(cell); }); table.appendChild(row); };";
echo " document.addEventListener('click', (event) => { if (event.target.closest('[data-market-definition-create]')) { event.preventDefault(); activeMarket = null; resetForm(); const title = form.querySelector('[data-market-definition-form-title]'); if (title) { title.textContent = 'Neuen Markt anlegen'; } if (sharedCard && window.SharedEditableActionCard) { window.SharedEditableActionCard.setEditMode(sharedCard, true); } setDrawerState(true); form.querySelector('[data-market-definition-name]')?.focus(); return; } if (event.target.closest('[data-market-definition-edit]')) { event.preventDefault(); openEditCard(); return; } if (event.target.closest('[data-market-definition-cancel]')) { event.preventDefault(); if (!activeMarket) { closeDrawer(); } return; } if (event.target.closest('[data-market-definition-drawer-backdrop]')) { event.preventDefault(); closeDrawer(); } });";
echo " document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && drawer.dataset.open === 'true') { closeDrawer(); } });";
echo " form.addEventListener('submit', async (event) => { event.preventDefault(); const status = form.querySelector('[data-market-definition-status]'); const submit = form.querySelector('[type=submit]'); const fields = Object.fromEntries(new FormData(form).entries()); const isUpdate = Boolean(activeMarket?.id); if (fields.organizer_party_id === '__new__' && !String(fields.organizer_name || '').trim()) { if (status) { status.textContent = 'Name des neuen Veranstalters ist erforderlich.'; } return; } if (status) { status.textContent = 'Speichern läuft …'; } if (submit) { submit.disabled = true; } try { const response = await fetch('/api/market-definition.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': definitionContent.dataset.marketDefinitionsCsrfToken || '' }, body: JSON.stringify({ action: isUpdate ? 'update' : 'create', id: isUpdate ? activeMarket.id : null, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } if (isUpdate) { const trigger = activeMarket.trigger; const category = form.querySelector('[data-market-definition-category]')?.selectedOptions[0]; const organizer = form.querySelector('[data-market-definition-organizer]')?.selectedOptions[0]; const website = result.market.website_url || 'Nicht hinterlegt'; trigger.textContent = result.market.name; trigger.dataset.marketDefinitionName = result.market.name; trigger.dataset.marketDefinitionCategoryId = String(result.market.category_id); trigger.dataset.marketDefinitionCategoryName = category?.textContent || ''; trigger.dataset.marketDefinitionOrganizerId = String(result.market.organizer_party_id); trigger.dataset.marketDefinitionOrganizerName = organizer?.textContent || ''; trigger.dataset.marketDefinitionWebsite = website; showReadCard(trigger); } else { appendMarketRow(result.market); closeDrawer(); } } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen.'; } } finally { if (submit) { submit.disabled = false; } } });";
echo " form.addEventListener('submit', async (event) => { event.preventDefault(); const status = form.querySelector('[data-market-definition-status]'); const submit = form.querySelector('[type=submit]'); const fields = Object.fromEntries(new FormData(form).entries()); const isUpdate = Boolean(activeMarket?.id); if (fields.organizer_id === '__new__' && !String(fields.organizer_name || '').trim()) { if (status) { status.textContent = 'Name des neuen Veranstalters ist erforderlich.'; } return; } if (status) { status.textContent = 'Speichern läuft …'; } if (submit) { submit.disabled = true; } try { const response = await fetch('/api/market-definition.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': definitionContent.dataset.marketDefinitionsCsrfToken || '' }, body: JSON.stringify({ action: isUpdate ? 'update' : 'create', id: isUpdate ? activeMarket.id : null, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } if (isUpdate) { const trigger = activeMarket.trigger; const category = form.querySelector('[data-market-definition-category]')?.selectedOptions[0]; const organizer = form.querySelector('[data-market-definition-organizer]')?.selectedOptions[0]; const website = result.market.website_url || 'Nicht hinterlegt'; trigger.textContent = result.market.name; trigger.dataset.marketDefinitionName = result.market.name; trigger.dataset.marketDefinitionCategoryId = String(result.market.category_id); trigger.dataset.marketDefinitionCategoryName = category?.textContent || ''; trigger.dataset.marketDefinitionOrganizerId = String(result.market.organizer_id); trigger.dataset.marketDefinitionOrganizerName = organizer?.textContent || ''; trigger.dataset.marketDefinitionWebsite = website; showReadCard(trigger); } else { appendMarketRow(result.market); closeDrawer(); } } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen.'; } } finally { if (submit) { submit.disabled = false; } } });";
echo "}";
echo "function initBestellungenBindings() {";
echo " const contentRoot = document.querySelector('[data-left-navigation-content-body]');";
+5 -6
View File
@@ -3,7 +3,6 @@ declare(strict_types=1);
require_once __DIR__ . '/../../modules/shared/db.php';
require_once __DIR__ . '/../../modules/shared/auth/service.php';
require_once __DIR__ . '/../../modules/erp/kontakte/service.php';
require_once __DIR__ . '/../../modules/market_management/service.php';
header('Content-Type: application/json; charset=utf-8');
@@ -41,14 +40,14 @@ if (!is_array($payload)) {
try {
$fields = is_array($payload['fields'] ?? null) ? $payload['fields'] : [];
if (($fields['organizer_party_id'] ?? '') === '__new__') {
$organizer = create_market_organizer(
if (($fields['organizer_id'] ?? '') === '__new__') {
$organizer = market_management_create_organizer(
$pdo,
(string) ($fields['organizer_name'] ?? ''),
(string) ($fields['organizer_email'] ?? ''),
(string) ($fields['organizer_phone'] ?? '')
);
$fields['organizer_party_id'] = $organizer['id'];
$fields['organizer_id'] = $organizer['id'];
}
if (($payload['action'] ?? 'create') === 'delete') {
$marketId = (int) ($payload['id'] ?? 0);
@@ -67,14 +66,14 @@ try {
$market = market_management_update_market($pdo, $marketId, [
'name' => $fields['name'] ?? null,
'category_id' => $fields['category_id'] ?? null,
'organizer_party_id' => $fields['organizer_party_id'] ?? null,
'organizer_id' => $fields['organizer_id'] ?? null,
'website_url' => $fields['website_url'] ?? null,
]);
} else {
$market = market_management_create_market($pdo, [
'name' => $fields['name'] ?? null,
'category_id' => $fields['category_id'] ?? null,
'organizer_party_id' => $fields['organizer_party_id'] ?? null,
'organizer_id' => $fields['organizer_id'] ?? null,
'website_url' => $fields['website_url'] ?? null,
]);
}
+1 -1
View File
@@ -88,7 +88,7 @@ if ($currentUser !== null) {
'offset' => 0,
]),
'categories' => market_management_list_categories($pdo),
'organizers' => get_market_organizer_options($pdo),
'organizers' => market_management_list_organizers($pdo),
];
if ((string) ($_GET['bestellungen_fragment'] ?? '') === '1') {
echo auth_render_bestellungen_content($bestellungenTable);