"""schema iniziale Revision ID: 0001 Revises: Create Date: 2026-04-28 12:40:08.000000 """ from typing import Sequence, Union from alembic import op revision: str = "0001" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.execute(""" DO $$ BEGIN CREATE TYPE userrole AS ENUM ('venditore','valutatore','backoffice','operatore_perizie','approvatore_perizie','admin'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE valuationstatus AS ENUM ('bozza','inviata','in_lavorazione','completata','vinta','persa'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE valuationpriority AS ENUM ('contratto','preventivo','valutazione'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE retakeregime AS ENUM ('nuovo','usato'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE repairstatus AS ENUM ('bozza','inviata','in_approvazione','approvata','rifiutata','chiusa'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE repairdestination AS ENUM ('vendita','rottamazione','altro'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE offerstatus AS ENUM ('in_attesa','accettata','rifiutata'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; CREATE TABLE IF NOT EXISTS groups ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, description VARCHAR(255), is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, full_name VARCHAR(200) NOT NULL, hashed_password VARCHAR(255) NOT NULL, role userrole NOT NULL, group_id INTEGER REFERENCES groups(id), is_active BOOLEAN NOT NULL DEFAULT TRUE, notify_email BOOLEAN NOT NULL DEFAULT TRUE, notify_push BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_users_email ON users (email); CREATE TABLE IF NOT EXISTS vehicle_registry ( plate VARCHAR(10) PRIMARY KEY, vin VARCHAR(17), vehicle_type VARCHAR(50), registration_date DATE, homologation_code VARCHAR(50), engine_code VARCHAR(20), last_revision_date DATE, foreign_plate VARCHAR(20), foreign_registration_date DATE, foreign_country VARCHAR(50), is_foreign_registered BOOLEAN, source VARCHAR(20), raw_response JSONB, fetched_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, selected_motornet_code VARCHAR(20) ); CREATE TABLE IF NOT EXISTS vehicle_versions ( motornet_code VARCHAR(20) PRIMARY KEY, brand_acronym VARCHAR(10), brand_name VARCHAR(50), model_code VARCHAR(10), model_description VARCHAR(100), gamma_code VARCHAR(10), gamma_description VARCHAR(100), series_code VARCHAR(10), series_description VARCHAR(100), historical_group_code VARCHAR(10), historical_group_desc VARCHAR(100), cod_desc_model_code VARCHAR(10), cod_desc_model_desc VARCHAR(100), version_label VARCHAR(200), body_type VARCHAR(10), doors SMALLINT, wheelbase SMALLINT, list_price NUMERIC(10,2), production_start DATE, production_end DATE, commercial_start DATE, commercial_end DATE, first_seen_at TIMESTAMPTZ, last_seen_at TIMESTAMPTZ, seen_count INTEGER NOT NULL DEFAULT 1, source VARCHAR(20) ); CREATE TABLE IF NOT EXISTS plate_version_candidates ( plate VARCHAR(10) REFERENCES vehicle_registry(plate), motornet_code VARCHAR(20) REFERENCES vehicle_versions(motornet_code), is_selected BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (plate, motornet_code) ); CREATE TABLE IF NOT EXISTS api_usage_log ( id SERIAL PRIMARY KEY, source VARCHAR(20), plate VARCHAR(10), hit_cache BOOLEAN NOT NULL DEFAULT FALSE, remaining_queries INTEGER, called_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS valuations ( id SERIAL PRIMARY KEY, plate VARCHAR(10) NOT NULL REFERENCES vehicle_registry(plate), user_id INTEGER NOT NULL REFERENCES users(id), group_id INTEGER REFERENCES groups(id), motornet_code VARCHAR(20) REFERENCES vehicle_versions(motornet_code), mileage INTEGER, retake_regime retakeregime, expected_return_date DATE, interest_fuel VARCHAR(50), priority valuationpriority NOT NULL DEFAULT 'valutazione', notes TEXT, status valuationstatus NOT NULL DEFAULT 'bozza', final_value NUMERIC(10,2), evaluator_id INTEGER REFERENCES users(id), evaluator_notes TEXT, is_frozen BOOLEAN NOT NULL DEFAULT FALSE, priority_rank INTEGER NOT NULL DEFAULT 3, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_valuations_plate ON valuations (plate); CREATE INDEX IF NOT EXISTS ix_valuations_status ON valuations (status); CREATE TABLE IF NOT EXISTS valuation_photos ( id SERIAL PRIMARY KEY, valuation_id INTEGER NOT NULL REFERENCES valuations(id), filename VARCHAR(255) NOT NULL, storage_path VARCHAR(500) NOT NULL, uploaded_by INTEGER NOT NULL REFERENCES users(id), uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS valuation_comments ( id SERIAL PRIMARY KEY, valuation_id INTEGER NOT NULL REFERENCES valuations(id), user_id INTEGER NOT NULL REFERENCES users(id), content TEXT NOT NULL, visible_to_all BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS valuation_history ( id SERIAL PRIMARY KEY, valuation_id INTEGER NOT NULL REFERENCES valuations(id), changed_by INTEGER NOT NULL REFERENCES users(id), field_name VARCHAR(100) NOT NULL, old_value TEXT, new_value TEXT, changed_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS contracts ( id SERIAL PRIMARY KEY, contract_number VARCHAR(50) NOT NULL, year INTEGER NOT NULL, company VARCHAR(100) NOT NULL, area VARCHAR(100), plate VARCHAR(10), customer_name VARCHAR(200), import_batch VARCHAR(100), imported_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS valuation_contracts ( id SERIAL PRIMARY KEY, valuation_id INTEGER NOT NULL REFERENCES valuations(id), contract_id INTEGER NOT NULL REFERENCES contracts(id), linked_by INTEGER NOT NULL REFERENCES users(id), linked_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS repairs ( id SERIAL PRIMARY KEY, plate VARCHAR(10) NOT NULL REFERENCES vehicle_registry(plate), company VARCHAR(100), infinity_repair_number VARCHAR(50), intervention_type VARCHAR(100), delivery_location VARCHAR(100), group_id INTEGER REFERENCES groups(id), created_by INTEGER NOT NULL REFERENCES users(id), status repairstatus NOT NULL DEFAULT 'bozza', destination repairdestination, offer_status offerstatus, estimated_value NUMERIC(10,2), actual_value NUMERIC(10,2), subtotal NUMERIC(10,2), notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_repairs_plate ON repairs (plate); CREATE INDEX IF NOT EXISTS ix_repairs_status ON repairs (status); CREATE TABLE IF NOT EXISTS repair_documents ( id SERIAL PRIMARY KEY, repair_id INTEGER NOT NULL REFERENCES repairs(id), filename VARCHAR(255) NOT NULL, storage_path VARCHAR(500) NOT NULL, uploaded_by INTEGER NOT NULL REFERENCES users(id), uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS repair_comments ( id SERIAL PRIMARY KEY, repair_id INTEGER NOT NULL REFERENCES repairs(id), user_id INTEGER NOT NULL REFERENCES users(id), content TEXT NOT NULL, mentions TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS repair_history ( id SERIAL PRIMARY KEY, repair_id INTEGER NOT NULL REFERENCES repairs(id), changed_by INTEGER NOT NULL REFERENCES users(id), field_name VARCHAR(100) NOT NULL, old_value TEXT, new_value TEXT, changed_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS notifications ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), title VARCHAR(200) NOT NULL, body TEXT, link VARCHAR(500), is_read BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS alert_config ( id SERIAL PRIMARY KEY, key VARCHAR(100) NOT NULL UNIQUE, value VARCHAR(255) NOT NULL, description VARCHAR(500), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS vehicle_stock ( id SERIAL PRIMARY KEY, plate VARCHAR(10) NOT NULL, brand VARCHAR(50), model VARCHAR(100), version VARCHAR(200), registration_date VARCHAR(20), mileage INTEGER, location VARCHAR(100), import_batch VARCHAR(100), imported_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_vehicle_stock_plate ON vehicle_stock (plate); CREATE TABLE IF NOT EXISTS repair_costs_avg ( id SERIAL PRIMARY KEY, model VARCHAR(100) NOT NULL, avg_cost NUMERIC(10,2), import_batch VARCHAR(100), imported_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_repair_costs_avg_model ON repair_costs_avg (model); CREATE TABLE IF NOT EXISTS vehicle_sales_avg ( id SERIAL PRIMARY KEY, model VARCHAR(100) NOT NULL, avg_stock_days NUMERIC(6,1), avg_repair_cost NUMERIC(10,2), avg_margin_eur NUMERIC(10,2), avg_margin_pct NUMERIC(5,2), avg_sale_price NUMERIC(10,2), import_batch VARCHAR(100), imported_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_vehicle_sales_avg_model ON vehicle_sales_avg (model); INSERT INTO alert_config (key, value, description) VALUES ('cache_ttl_days', '90', 'TTL cache targhe in giorni'), ('alert_days_threshold', '30', 'Giorni ritardo rientro per alert'), ('repair_alert_pct_consuntiva', '20', 'Soglia % preventivo vs consuntiva'), ('repair_alert_pct_preventiva', '20', 'Soglia % preventivo vs preventiva') ON CONFLICT (key) DO NOTHING; """) def downgrade() -> None: op.execute(""" DROP TABLE IF EXISTS vehicle_sales_avg CASCADE; DROP TABLE IF EXISTS repair_costs_avg CASCADE; DROP TABLE IF EXISTS vehicle_stock CASCADE; DROP TABLE IF EXISTS alert_config CASCADE; DROP TABLE IF EXISTS notifications CASCADE; DROP TABLE IF EXISTS repair_history CASCADE; DROP TABLE IF EXISTS repair_comments CASCADE; DROP TABLE IF EXISTS repair_documents CASCADE; DROP TABLE IF EXISTS repairs CASCADE; DROP TABLE IF EXISTS valuation_contracts CASCADE; DROP TABLE IF EXISTS contracts CASCADE; DROP TABLE IF EXISTS valuation_history CASCADE; DROP TABLE IF EXISTS valuation_comments CASCADE; DROP TABLE IF EXISTS valuation_photos CASCADE; DROP TABLE IF EXISTS valuations CASCADE; DROP TABLE IF EXISTS api_usage_log CASCADE; DROP TABLE IF EXISTS plate_version_candidates CASCADE; DROP TABLE IF EXISTS vehicle_versions CASCADE; DROP TABLE IF EXISTS vehicle_registry CASCADE; DROP TABLE IF EXISTS users CASCADE; DROP TABLE IF EXISTS groups CASCADE; DROP TYPE IF EXISTS offerstatus; DROP TYPE IF EXISTS repairdestination; DROP TYPE IF EXISTS repairstatus; DROP TYPE IF EXISTS retakeregime; DROP TYPE IF EXISTS valuationpriority; DROP TYPE IF EXISTS valuationstatus; DROP TYPE IF EXISTS userrole; """)