BEGIN; -- Market Management base schema. -- Fachliche Ownership: market_management. CREATE TABLE market_category ( id BIGSERIAL PRIMARY KEY, code TEXT NOT NULL, name TEXT NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX uq_market_category_code ON market_category(code); CREATE UNIQUE INDEX uq_market_category_name ON market_category(name); INSERT INTO market_category (code, name) VALUES ('herbstmarkt', 'Herbstmärkte'), ('weihnachtsmarkt', 'Weihnachtsmärkte'), ('fruehlingsmarkt', 'Frühlingsmärkte'), ('ostermarkt', 'Ostermärkte'), ('kunsthandwerksmarkt', 'Kunsthandwerksmärkte'), ('regionalmarkt', 'Regionalmärkte'), ('weitere_geeignete_maerkte', 'Weitere geeignete Märkte'); CREATE TABLE market_municipality_reference ( id BIGSERIAL PRIMARY KEY, bfs_municipality_number INTEGER NOT NULL, name TEXT NOT NULL, canton_code TEXT NOT NULL, data_version DATE NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX uq_market_municipality_reference_bfs_number_version ON market_municipality_reference(bfs_municipality_number, data_version); CREATE INDEX idx_market_municipality_reference_canton ON market_municipality_reference(canton_code, name); CREATE TABLE market_definition ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, category_id BIGINT NOT NULL REFERENCES market_category(id) ON DELETE RESTRICT, archived_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX uq_market_definition_name ON market_definition(name); CREATE INDEX idx_market_definition_category ON market_definition(category_id); CREATE TABLE market_occurrence ( id BIGSERIAL PRIMARY KEY, market_definition_id BIGINT NOT NULL REFERENCES market_definition(id) ON DELETE RESTRICT, status TEXT NOT NULL DEFAULT 'open', place TEXT, address TEXT, municipality_reference_id BIGINT REFERENCES market_municipality_reference(id) ON DELETE RESTRICT, notes TEXT, registration_deadline DATE, request_or_registration_date DATE, confirmation_date DATE, payment_deadline DATE, stand_size TEXT, table_available BOOLEAN, table_rentable BOOLEAN, own_table_required BOOLEAN, shelter_available BOOLEAN, power_available BOOLEAN, water_available BOOLEAN, lighting_available BOOLEAN, other_infrastructure TEXT, own_stand_requirements TEXT, travel_address TEXT, travel_distance_km NUMERIC(10, 2), travel_duration_minutes INTEGER, parking_options TEXT, delivery_access TEXT, direct_unload_possible BOOLEAN, storage_options TEXT, travel_notes TEXT, payment_status TEXT NOT NULL DEFAULT 'unpaid', payment_date DATE, paid_amount NUMERIC(14, 2), payment_method_code TEXT, cancellation_terms TEXT, revenue_share_terms TEXT, other_participation_conditions TEXT, contract_folder_url TEXT, archived_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), CONSTRAINT chk_market_occurrence_status CHECK ( status IN ('open', 'waitlist', 'registered', 'confirmation_pending', 'confirmed', 'cancelled', 'completed') ), CONSTRAINT chk_market_occurrence_payment_status CHECK ( payment_status IN ('paid', 'unpaid') ), CONSTRAINT chk_market_occurrence_travel_distance CHECK ( travel_distance_km IS NULL OR travel_distance_km >= 0 ), CONSTRAINT chk_market_occurrence_travel_duration CHECK ( travel_duration_minutes IS NULL OR travel_duration_minutes >= 0 ), CONSTRAINT chk_market_occurrence_paid_amount CHECK ( paid_amount IS NULL OR paid_amount >= 0 ) ); CREATE INDEX idx_market_occurrence_market ON market_occurrence(market_definition_id); CREATE INDEX idx_market_occurrence_status ON market_occurrence(status); CREATE INDEX idx_market_occurrence_municipality ON market_occurrence(municipality_reference_id); CREATE INDEX idx_market_occurrence_archived_at ON market_occurrence(archived_at); CREATE TABLE market_occurrence_organizer ( id BIGSERIAL PRIMARY KEY, occurrence_id BIGINT NOT NULL UNIQUE REFERENCES market_occurrence(id) ON DELETE RESTRICT, organization_contact_id BIGINT NOT NULL, contact_person_id BIGINT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_market_occurrence_organizer_organization ON market_occurrence_organizer(organization_contact_id); CREATE INDEX idx_market_occurrence_organizer_contact_person ON market_occurrence_organizer(contact_person_id); CREATE TABLE market_occurrence_day ( id BIGSERIAL PRIMARY KEY, occurrence_id BIGINT NOT NULL REFERENCES market_occurrence(id) ON DELETE RESTRICT, date DATE NOT NULL, stand_start_time TIME, stand_end_time TIME, setup_start_time TIME, setup_end_time TIME, delivery_start_time TIME, delivery_end_time TIME, observations TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), CONSTRAINT chk_market_occurrence_day_stand_time CHECK ( stand_start_time IS NULL OR stand_end_time IS NULL OR stand_end_time >= stand_start_time ), CONSTRAINT chk_market_occurrence_day_setup_time CHECK ( setup_start_time IS NULL OR setup_end_time IS NULL OR setup_end_time >= setup_start_time ), CONSTRAINT chk_market_occurrence_day_delivery_time CHECK ( delivery_start_time IS NULL OR delivery_end_time IS NULL OR delivery_end_time >= delivery_start_time ) ); CREATE UNIQUE INDEX uq_market_occurrence_day_date ON market_occurrence_day(occurrence_id, date); CREATE INDEX idx_market_occurrence_day_date ON market_occurrence_day(date); CREATE TABLE market_occurrence_cost_item ( id BIGSERIAL PRIMARY KEY, occurrence_id BIGINT NOT NULL REFERENCES market_occurrence(id) ON DELETE RESTRICT, cost_type TEXT NOT NULL, description TEXT, gross_amount NUMERIC(14, 2) NOT NULL, tax_status TEXT NOT NULL, tax_rate NUMERIC(5, 2) NOT NULL DEFAULT 0, tax_amount NUMERIC(14, 2) GENERATED ALWAYS AS ( CASE WHEN tax_status = 'tax_free' THEN 0 ELSE ROUND(gross_amount - gross_amount / (1 + tax_rate / 100), 2) END ) STORED, net_amount NUMERIC(14, 2) GENERATED ALWAYS AS ( gross_amount - CASE WHEN tax_status = 'tax_free' THEN 0 ELSE ROUND(gross_amount - gross_amount / (1 + tax_rate / 100), 2) END ) STORED, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), CONSTRAINT chk_market_occurrence_cost_gross_nonnegative CHECK (gross_amount >= 0), CONSTRAINT chk_market_occurrence_cost_tax_status CHECK (tax_status IN ('tax_free', 'taxable')), CONSTRAINT chk_market_occurrence_cost_tax_rate CHECK ( (tax_status = 'tax_free' AND tax_rate = 0) OR (tax_status = 'taxable' AND tax_rate > 0) ) ); CREATE INDEX idx_market_occurrence_cost_item_occurrence ON market_occurrence_cost_item(occurrence_id); CREATE TABLE market_mcp_idempotency ( id BIGSERIAL PRIMARY KEY, user_subject TEXT NOT NULL, client_id TEXT NOT NULL, idempotency_key TEXT NOT NULL, tool_name TEXT NOT NULL, request_hash TEXT NOT NULL, result_status TEXT NOT NULL, result_entity_type TEXT, result_entity_id TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW(), expires_at TIMESTAMP NOT NULL, CONSTRAINT chk_market_mcp_idempotency_result_status CHECK ( result_status IN ('processing', 'succeeded', 'failed') ) ); CREATE UNIQUE INDEX uq_market_mcp_idempotency_client_key ON market_mcp_idempotency(client_id, idempotency_key); CREATE INDEX idx_market_mcp_idempotency_expiry ON market_mcp_idempotency(expires_at); COMMIT;