Files
erp_naurua/db/migrations/0023_market_management.sql

216 lines
7.6 KiB
PL/PgSQL

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;