From 1ede1d14ab584abc1e145d147e1492cf33c76cdd Mon Sep 17 00:00:00 2001 From: Matteo Giustini Date: Tue, 28 Apr 2026 12:42:25 +0200 Subject: [PATCH] Primo schema DB --- .gitignore | 3 + .../alembic/versions/0001_initial_schema.py | 350 ++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 backend/alembic/versions/0001_initial_schema.py diff --git a/.gitignore b/.gitignore index 1c78fd5..eb74a08 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ __pycache__/ *.py[cod] *$py.class +__pycache__/ +*.py[cod] +.env # C extensions *.so diff --git a/backend/alembic/versions/0001_initial_schema.py b/backend/alembic/versions/0001_initial_schema.py new file mode 100644 index 0000000..71104b1 --- /dev/null +++ b/backend/alembic/versions/0001_initial_schema.py @@ -0,0 +1,350 @@ +"""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; + """)