feat(back-end): new db for custom quote requests
This commit is contained in:
558
db.sql
558
db.sql
@@ -16,11 +16,10 @@ create table printer_machine
|
||||
);
|
||||
|
||||
create view printer_fleet_current as
|
||||
select 1 as fleet_id,
|
||||
case
|
||||
select case
|
||||
when sum(fleet_weight) = 0 then null
|
||||
else round(sum(power_watts * fleet_weight) / sum(fleet_weight))::integer
|
||||
end as weighted_average_power_watts,
|
||||
end as weighted_average_power_watts,
|
||||
max(build_volume_x_mm) as fleet_max_build_x_mm,
|
||||
max(build_volume_y_mm) as fleet_max_build_y_mm,
|
||||
max(build_volume_z_mm) as fleet_max_build_z_mm
|
||||
@@ -156,54 +155,63 @@ begin;
|
||||
|
||||
set timezone = 'Europe/Zurich';
|
||||
|
||||
is_active = excluded.is_active;
|
||||
-- =========================================================
|
||||
-- 0) (Solo se non esiste) tabella infill_pattern + seed
|
||||
-- =========================================================
|
||||
-- Se la tabella esiste già, commenta questo blocco.
|
||||
create table if not exists infill_pattern
|
||||
(
|
||||
infill_pattern_id bigserial primary key,
|
||||
pattern_code text not null unique, -- es: grid, gyroid
|
||||
display_name text not null,
|
||||
is_active boolean not null default true
|
||||
);
|
||||
|
||||
insert into infill_pattern (pattern_code, display_name, is_active)
|
||||
values ('grid', 'Grid', true),
|
||||
('gyroid', 'Gyroid', true)
|
||||
on conflict (pattern_code) do update
|
||||
set display_name = excluded.display_name,
|
||||
is_active = excluded.is_active;
|
||||
|
||||
|
||||
-- =========================================================
|
||||
-- 1) Pricing policy (valori ESATTI da Excel)
|
||||
-- Valid from: 2026-01-01, valid_to: NULL
|
||||
-- =========================================================
|
||||
insert into pricing_policy (
|
||||
policy_name,
|
||||
valid_from,
|
||||
valid_to,
|
||||
electricity_cost_chf_per_kwh,
|
||||
markup_percent,
|
||||
fixed_job_fee_chf,
|
||||
nozzle_change_base_fee_chf,
|
||||
cad_cost_chf_per_hour,
|
||||
is_active
|
||||
) values (
|
||||
'Excel Tariffe 2026-01-01',
|
||||
'2026-01-01 00:00:00+01'::timestamptz,
|
||||
null,
|
||||
0.156, -- Costo elettricità CHF/kWh (Excel)
|
||||
0.000, -- Markup non specificato -> 0 (puoi cambiarlo dopo)
|
||||
1.00, -- Costo fisso macchina CHF (Excel)
|
||||
0.00, -- Base cambio ugello: non specificato -> 0
|
||||
25.00, -- Tariffa CAD CHF/h (Excel)
|
||||
true
|
||||
)
|
||||
insert into pricing_policy (policy_name,
|
||||
valid_from,
|
||||
valid_to,
|
||||
electricity_cost_chf_per_kwh,
|
||||
markup_percent,
|
||||
fixed_job_fee_chf,
|
||||
nozzle_change_base_fee_chf,
|
||||
cad_cost_chf_per_hour,
|
||||
is_active)
|
||||
values ('Excel Tariffe 2026-01-01',
|
||||
'2026-01-01 00:00:00+01'::timestamptz,
|
||||
null,
|
||||
0.156, -- Costo elettricità CHF/kWh (Excel)
|
||||
0.000, -- Markup non specificato -> 0 (puoi cambiarlo dopo)
|
||||
1.00, -- Costo fisso macchina CHF (Excel)
|
||||
0.00, -- Base cambio ugello: non specificato -> 0
|
||||
25.00, -- Tariffa CAD CHF/h (Excel)
|
||||
true)
|
||||
on conflict do nothing;
|
||||
|
||||
-- scaglioni tariffa stampa (Excel)
|
||||
insert into pricing_policy_machine_hour_tier (
|
||||
pricing_policy_id,
|
||||
tier_start_hours,
|
||||
tier_end_hours,
|
||||
machine_cost_chf_per_hour
|
||||
)
|
||||
select
|
||||
p.pricing_policy_id,
|
||||
tiers.tier_start_hours,
|
||||
tiers.tier_end_hours,
|
||||
tiers.machine_cost_chf_per_hour
|
||||
insert into pricing_policy_machine_hour_tier (pricing_policy_id,
|
||||
tier_start_hours,
|
||||
tier_end_hours,
|
||||
machine_cost_chf_per_hour)
|
||||
select p.pricing_policy_id,
|
||||
tiers.tier_start_hours,
|
||||
tiers.tier_end_hours,
|
||||
tiers.machine_cost_chf_per_hour
|
||||
from pricing_policy p
|
||||
cross join (
|
||||
values
|
||||
(0.00::numeric, 10.00::numeric, 2.00::numeric), -- 0–10 h
|
||||
(10.00::numeric, 20.00::numeric, 1.40::numeric), -- 10–20 h
|
||||
(20.00::numeric, null::numeric, 0.50::numeric) -- >20 h
|
||||
cross join (values (0.00::numeric, 10.00::numeric, 2.00::numeric), -- 0–10 h
|
||||
(10.00::numeric, 20.00::numeric, 1.40::numeric), -- 10–20 h
|
||||
(20.00::numeric, null::numeric, 0.50::numeric) -- >20 h
|
||||
) as tiers(tier_start_hours, tier_end_hours, machine_cost_chf_per_hour)
|
||||
where p.policy_name = 'Excel Tariffe 2026-01-01'
|
||||
on conflict do nothing;
|
||||
@@ -212,52 +220,45 @@ on conflict do nothing;
|
||||
-- =========================================================
|
||||
-- 2) Stampante: BambuLab A1
|
||||
-- =========================================================
|
||||
insert into printer_machine (
|
||||
printer_display_name,
|
||||
build_volume_x_mm,
|
||||
build_volume_y_mm,
|
||||
build_volume_z_mm,
|
||||
power_watts,
|
||||
fleet_weight,
|
||||
is_active
|
||||
) values (
|
||||
'BambuLab A1',
|
||||
256,
|
||||
256,
|
||||
256,
|
||||
150, -- hai detto "150, 140": qui ho messo 150
|
||||
1.000,
|
||||
true
|
||||
)
|
||||
insert into printer_machine (printer_display_name,
|
||||
build_volume_x_mm,
|
||||
build_volume_y_mm,
|
||||
build_volume_z_mm,
|
||||
power_watts,
|
||||
fleet_weight,
|
||||
is_active)
|
||||
values ('BambuLab A1',
|
||||
256,
|
||||
256,
|
||||
256,
|
||||
150, -- hai detto "150, 140": qui ho messo 150
|
||||
1.000,
|
||||
true)
|
||||
on conflict (printer_display_name) do update
|
||||
set
|
||||
build_volume_x_mm = excluded.build_volume_x_mm,
|
||||
set build_volume_x_mm = excluded.build_volume_x_mm,
|
||||
build_volume_y_mm = excluded.build_volume_y_mm,
|
||||
build_volume_z_mm = excluded.build_volume_z_mm,
|
||||
power_watts = excluded.power_watts,
|
||||
fleet_weight = excluded.fleet_weight,
|
||||
is_active = excluded.is_active;
|
||||
power_watts = excluded.power_watts,
|
||||
fleet_weight = excluded.fleet_weight,
|
||||
is_active = excluded.is_active;
|
||||
|
||||
|
||||
-- =========================================================
|
||||
-- 3) Material types (da Excel) - per ora niente technical
|
||||
-- =========================================================
|
||||
insert into filament_material_type (
|
||||
material_code,
|
||||
is_flexible,
|
||||
is_technical,
|
||||
technical_type_label
|
||||
) values
|
||||
('PLA', false, false, null),
|
||||
('PETG', false, false, null),
|
||||
('TPU', true, false, null),
|
||||
('ABS', false, false, null),
|
||||
('Nylon', false, false, null),
|
||||
('Carbon PLA', false, false, null)
|
||||
insert into filament_material_type (material_code,
|
||||
is_flexible,
|
||||
is_technical,
|
||||
technical_type_label)
|
||||
values ('PLA', false, false, null),
|
||||
('PETG', false, false, null),
|
||||
('TPU', true, false, null),
|
||||
('ABS', false, false, null),
|
||||
('Nylon', false, false, null),
|
||||
('Carbon PLA', false, false, null)
|
||||
on conflict (material_code) do update
|
||||
set
|
||||
is_flexible = excluded.is_flexible,
|
||||
is_technical = excluded.is_technical,
|
||||
set is_flexible = excluded.is_flexible,
|
||||
is_technical = excluded.is_technical,
|
||||
technical_type_label = excluded.technical_type_label;
|
||||
|
||||
|
||||
@@ -268,99 +269,358 @@ on conflict (material_code) do update
|
||||
-- =========================================================
|
||||
|
||||
-- helper: ID PLA
|
||||
with pla as (
|
||||
select filament_material_type_id
|
||||
from filament_material_type
|
||||
where material_code = 'PLA'
|
||||
)
|
||||
insert into filament_variant (
|
||||
filament_material_type_id,
|
||||
variant_display_name,
|
||||
color_name,
|
||||
is_matte,
|
||||
is_special,
|
||||
cost_chf_per_kg,
|
||||
stock_spools,
|
||||
spool_net_kg,
|
||||
is_active
|
||||
)
|
||||
select
|
||||
pla.filament_material_type_id,
|
||||
v.variant_display_name,
|
||||
v.color_name,
|
||||
v.is_matte,
|
||||
v.is_special,
|
||||
18.00, -- PLA da Excel
|
||||
v.stock_spools,
|
||||
1.000,
|
||||
true
|
||||
with pla as (select filament_material_type_id
|
||||
from filament_material_type
|
||||
where material_code = 'PLA')
|
||||
insert
|
||||
into filament_variant (filament_material_type_id,
|
||||
variant_display_name,
|
||||
color_name,
|
||||
is_matte,
|
||||
is_special,
|
||||
cost_chf_per_kg,
|
||||
stock_spools,
|
||||
spool_net_kg,
|
||||
is_active)
|
||||
select pla.filament_material_type_id,
|
||||
v.variant_display_name,
|
||||
v.color_name,
|
||||
v.is_matte,
|
||||
v.is_special,
|
||||
18.00, -- PLA da Excel
|
||||
v.stock_spools,
|
||||
1.000,
|
||||
true
|
||||
from pla
|
||||
cross join (
|
||||
values
|
||||
('PLA Bianco', 'Bianco', false, false, 3.000::numeric),
|
||||
('PLA Nero', 'Nero', false, false, 3.000::numeric),
|
||||
('PLA Blu', 'Blu', false, false, 1.000::numeric),
|
||||
('PLA Arancione', 'Arancione', false, false, 1.000::numeric),
|
||||
('PLA Grigio', 'Grigio', false, false, 1.000::numeric),
|
||||
('PLA Grigio Scuro', 'Grigio scuro', false, false, 1.000::numeric),
|
||||
('PLA Grigio Chiaro', 'Grigio chiaro', false, false, 1.000::numeric),
|
||||
('PLA Viola', 'Viola', false, false, 1.000::numeric)
|
||||
) as v(variant_display_name, color_name, is_matte, is_special, stock_spools)
|
||||
cross join (values ('PLA Bianco', 'Bianco', false, false, 3.000::numeric),
|
||||
('PLA Nero', 'Nero', false, false, 3.000::numeric),
|
||||
('PLA Blu', 'Blu', false, false, 1.000::numeric),
|
||||
('PLA Arancione', 'Arancione', false, false, 1.000::numeric),
|
||||
('PLA Grigio', 'Grigio', false, false, 1.000::numeric),
|
||||
('PLA Grigio Scuro', 'Grigio scuro', false, false, 1.000::numeric),
|
||||
('PLA Grigio Chiaro', 'Grigio chiaro', false, false, 1.000::numeric),
|
||||
('PLA Viola', 'Viola', false, false,
|
||||
1.000::numeric)) as v(variant_display_name, color_name, is_matte, is_special, stock_spools)
|
||||
on conflict (filament_material_type_id, variant_display_name) do update
|
||||
set
|
||||
color_name = excluded.color_name,
|
||||
is_matte = excluded.is_matte,
|
||||
is_special = excluded.is_special,
|
||||
set color_name = excluded.color_name,
|
||||
is_matte = excluded.is_matte,
|
||||
is_special = excluded.is_special,
|
||||
cost_chf_per_kg = excluded.cost_chf_per_kg,
|
||||
stock_spools = excluded.stock_spools,
|
||||
spool_net_kg = excluded.spool_net_kg,
|
||||
is_active = excluded.is_active;
|
||||
stock_spools = excluded.stock_spools,
|
||||
spool_net_kg = excluded.spool_net_kg,
|
||||
is_active = excluded.is_active;
|
||||
|
||||
|
||||
-- =========================================================
|
||||
-- 5) Ugelli
|
||||
-- 0.4 standard (0 extra), 0.6 con attivazione 50 CHF
|
||||
-- =========================================================
|
||||
insert into nozzle_option (
|
||||
nozzle_diameter_mm,
|
||||
owned_quantity,
|
||||
extra_nozzle_change_fee_chf,
|
||||
is_active
|
||||
) values
|
||||
(0.40, 1, 0.00, true),
|
||||
(0.60, 1, 50.00, true)
|
||||
insert into nozzle_option (nozzle_diameter_mm,
|
||||
owned_quantity,
|
||||
extra_nozzle_change_fee_chf,
|
||||
is_active)
|
||||
values (0.40, 1, 0.00, true),
|
||||
(0.60, 1, 50.00, true)
|
||||
on conflict (nozzle_diameter_mm) do update
|
||||
set
|
||||
owned_quantity = excluded.owned_quantity,
|
||||
set owned_quantity = excluded.owned_quantity,
|
||||
extra_nozzle_change_fee_chf = excluded.extra_nozzle_change_fee_chf,
|
||||
is_active = excluded.is_active;
|
||||
is_active = excluded.is_active;
|
||||
|
||||
|
||||
-- =========================================================
|
||||
-- 6) Layer heights (opzioni)
|
||||
-- =========================================================
|
||||
insert into layer_height_option (
|
||||
layer_height_mm,
|
||||
time_multiplier,
|
||||
is_active
|
||||
) values
|
||||
(0.080, 1.000, true),
|
||||
(0.120, 1.000, true),
|
||||
(0.160, 1.000, true),
|
||||
(0.200, 1.000, true),
|
||||
(0.240, 1.000, true),
|
||||
(0.280, 1.000, true)
|
||||
insert into layer_height_option (layer_height_mm,
|
||||
time_multiplier,
|
||||
is_active)
|
||||
values (0.080, 1.000, true),
|
||||
(0.120, 1.000, true),
|
||||
(0.160, 1.000, true),
|
||||
(0.200, 1.000, true),
|
||||
(0.240, 1.000, true),
|
||||
(0.280, 1.000, true)
|
||||
on conflict (layer_height_mm) do update
|
||||
set
|
||||
time_multiplier = excluded.time_multiplier,
|
||||
is_active = excluded.is_active;
|
||||
set time_multiplier = excluded.time_multiplier,
|
||||
is_active = excluded.is_active;
|
||||
|
||||
commit;
|
||||
|
||||
|
||||
|
||||
|
||||
-- Sostituisci __MULTIPLIER__ con il tuo valore (es. 1.10)
|
||||
update layer_height_option
|
||||
set time_multiplier = 0.1
|
||||
where layer_height_mm = 0.080;
|
||||
|
||||
|
||||
-- =========================
|
||||
-- CUSTOMERS (minimo indispensabile)
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS customers
|
||||
(
|
||||
customer_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
customer_type text NOT NULL CHECK (customer_type IN ('PRIVATE', 'COMPANY')),
|
||||
email text NOT NULL,
|
||||
phone text,
|
||||
|
||||
-- per PRIVATE
|
||||
first_name text,
|
||||
last_name text,
|
||||
|
||||
-- per COMPANY
|
||||
company_name text,
|
||||
contact_person text,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_customers_email
|
||||
ON customers (lower(email));
|
||||
|
||||
-- =========================
|
||||
-- QUOTE SESSIONS (carrello preventivo)
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS quote_sessions
|
||||
(
|
||||
quote_session_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
status text NOT NULL CHECK (status IN ('ACTIVE', 'EXPIRED', 'CONVERTED')),
|
||||
pricing_version text NOT NULL,
|
||||
|
||||
-- Parametri "globali" (dalla tua UI avanzata)
|
||||
material_code text NOT NULL, -- es: PLA, PETG...
|
||||
nozzle_diameter_mm numeric(5, 2), -- es: 0.40
|
||||
layer_height_mm numeric(6, 3), -- es: 0.20
|
||||
infill_pattern text, -- es: grid
|
||||
infill_percent integer CHECK (infill_percent BETWEEN 0 AND 100),
|
||||
supports_enabled boolean NOT NULL DEFAULT false,
|
||||
notes text,
|
||||
|
||||
setup_cost_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
expires_at timestamptz NOT NULL,
|
||||
converted_order_id uuid
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_quote_sessions_status
|
||||
ON quote_sessions (status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_quote_sessions_expires_at
|
||||
ON quote_sessions (expires_at);
|
||||
|
||||
-- =========================
|
||||
-- QUOTE LINE ITEMS (1 file = 1 riga)
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS quote_line_items
|
||||
(
|
||||
quote_line_item_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
quote_session_id uuid NOT NULL REFERENCES quote_sessions (quote_session_id) ON DELETE CASCADE,
|
||||
|
||||
status text NOT NULL CHECK (status IN ('CALCULATING', 'READY', 'FAILED')),
|
||||
|
||||
original_filename text NOT NULL,
|
||||
quantity integer NOT NULL DEFAULT 1 CHECK (quantity >= 1),
|
||||
color_code text, -- es: white/black o codice interno
|
||||
|
||||
-- Output slicing / calcolo
|
||||
bounding_box_x_mm numeric(10, 3),
|
||||
bounding_box_y_mm numeric(10, 3),
|
||||
bounding_box_z_mm numeric(10, 3),
|
||||
print_time_seconds integer CHECK (print_time_seconds >= 0),
|
||||
material_grams numeric(12, 2) CHECK (material_grams >= 0),
|
||||
|
||||
unit_price_chf numeric(12, 2) CHECK (unit_price_chf >= 0),
|
||||
pricing_breakdown jsonb, -- opzionale: costi dettagliati senza creare tabelle
|
||||
|
||||
error_message text,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_quote_line_items_session
|
||||
ON quote_line_items (quote_session_id);
|
||||
|
||||
-- Vista utile per totale quote
|
||||
CREATE OR REPLACE VIEW quote_session_totals AS
|
||||
SELECT qs.quote_session_id,
|
||||
qs.setup_cost_chf +
|
||||
COALESCE(SUM(qli.unit_price_chf * qli.quantity), 0.00) AS total_chf
|
||||
FROM quote_sessions qs
|
||||
LEFT JOIN quote_line_items qli
|
||||
ON qli.quote_session_id = qs.quote_session_id
|
||||
AND qli.status = 'READY'
|
||||
GROUP BY qs.quote_session_id;
|
||||
|
||||
-- =========================
|
||||
-- ORDERS
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS orders
|
||||
(
|
||||
order_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
source_quote_session_id uuid REFERENCES quote_sessions (quote_session_id),
|
||||
|
||||
status text NOT NULL CHECK (status IN (
|
||||
'PENDING_PAYMENT', 'PAID', 'IN_PRODUCTION',
|
||||
'SHIPPED', 'COMPLETED', 'CANCELLED'
|
||||
)),
|
||||
|
||||
customer_id uuid REFERENCES customers (customer_id),
|
||||
customer_email text NOT NULL,
|
||||
customer_phone text,
|
||||
|
||||
-- Snapshot indirizzo/fatturazione (evita tabella addresses e mantiene storico)
|
||||
billing_customer_type text NOT NULL CHECK (billing_customer_type IN ('PRIVATE', 'COMPANY')),
|
||||
billing_first_name text,
|
||||
billing_last_name text,
|
||||
billing_company_name text,
|
||||
billing_contact_person text,
|
||||
|
||||
billing_address_line1 text NOT NULL,
|
||||
billing_address_line2 text,
|
||||
billing_zip text NOT NULL,
|
||||
billing_city text NOT NULL,
|
||||
billing_country_code char(2) NOT NULL DEFAULT 'CH',
|
||||
|
||||
shipping_same_as_billing boolean NOT NULL DEFAULT true,
|
||||
shipping_first_name text,
|
||||
shipping_last_name text,
|
||||
shipping_company_name text,
|
||||
shipping_contact_person text,
|
||||
shipping_address_line1 text,
|
||||
shipping_address_line2 text,
|
||||
shipping_zip text,
|
||||
shipping_city text,
|
||||
shipping_country_code char(2),
|
||||
|
||||
currency char(3) NOT NULL DEFAULT 'CHF',
|
||||
setup_cost_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||
shipping_cost_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||
discount_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||
|
||||
subtotal_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||
total_chf numeric(12, 2) NOT NULL DEFAULT 0.00,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
paid_at timestamptz
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_status
|
||||
ON orders (status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_customer_email
|
||||
ON orders (lower(customer_email));
|
||||
|
||||
-- =========================
|
||||
-- ORDER ITEMS (1 file 3D = 1 riga, file salvato su disco)
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS order_items
|
||||
(
|
||||
order_item_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
order_id uuid NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE,
|
||||
|
||||
original_filename text NOT NULL,
|
||||
stored_relative_path text NOT NULL, -- es: orders/<orderId>/3d-files/<orderItemId>/<uuid>.stl
|
||||
stored_filename text NOT NULL, -- es: <uuid>.stl
|
||||
|
||||
file_size_bytes bigint CHECK (file_size_bytes >= 0),
|
||||
mime_type text,
|
||||
sha256_hex text, -- opzionale, utile anche per dedup interno
|
||||
|
||||
material_code text NOT NULL,
|
||||
color_code text,
|
||||
quantity integer NOT NULL DEFAULT 1 CHECK (quantity >= 1),
|
||||
|
||||
-- Snapshot output
|
||||
print_time_seconds integer CHECK (print_time_seconds >= 0),
|
||||
material_grams numeric(12, 2) CHECK (material_grams >= 0),
|
||||
unit_price_chf numeric(12, 2) NOT NULL CHECK (unit_price_chf >= 0),
|
||||
line_total_chf numeric(12, 2) NOT NULL CHECK (line_total_chf >= 0),
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_order_items_order
|
||||
ON order_items (order_id);
|
||||
|
||||
-- =========================
|
||||
-- PAYMENTS (supporta più tentativi / metodi)
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS payments
|
||||
(
|
||||
payment_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
order_id uuid NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE,
|
||||
|
||||
method text NOT NULL CHECK (method IN ('QR_BILL', 'BANK_TRANSFER', 'TWINT', 'CARD', 'OTHER')),
|
||||
status text NOT NULL CHECK (status IN ('PENDING', 'RECEIVED', 'FAILED', 'CANCELLED', 'REFUNDED')),
|
||||
|
||||
currency char(3) NOT NULL DEFAULT 'CHF',
|
||||
amount_chf numeric(12, 2) NOT NULL CHECK (amount_chf >= 0),
|
||||
|
||||
-- riferimento pagamento (molto utile per QR bill / riconciliazione)
|
||||
payment_reference text,
|
||||
provider_transaction_id text,
|
||||
|
||||
qr_payload text, -- se vuoi salvare contenuto QR/Swiss QR bill
|
||||
initiated_at timestamptz NOT NULL DEFAULT now(),
|
||||
received_at timestamptz
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_payments_order
|
||||
ON payments (order_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_payments_reference
|
||||
ON payments (payment_reference);
|
||||
|
||||
-- =========================
|
||||
-- CUSTOM QUOTE REQUESTS (preventivo personalizzato, form che hai mostrato)
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS custom_quote_requests
|
||||
(
|
||||
request_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
request_type text NOT NULL, -- es: "PREVENTIVO_PERSONALIZZATO" o come preferisci
|
||||
|
||||
customer_type text NOT NULL CHECK (customer_type IN ('PRIVATE', 'COMPANY')),
|
||||
email text NOT NULL,
|
||||
phone text,
|
||||
|
||||
-- PRIVATE
|
||||
name text,
|
||||
|
||||
-- COMPANY
|
||||
company_name text,
|
||||
contact_person text,
|
||||
|
||||
message text NOT NULL,
|
||||
status text NOT NULL CHECK (status IN ('NEW', 'PENDING', 'IN_PROGRESS', 'DONE', 'CLOSED')),
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_custom_quote_requests_status
|
||||
ON custom_quote_requests (status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_custom_quote_requests_email
|
||||
ON custom_quote_requests (lower(email));
|
||||
|
||||
-- Allegati della richiesta (max 15 come UI)
|
||||
CREATE TABLE IF NOT EXISTS custom_quote_request_attachments
|
||||
(
|
||||
attachment_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
request_id uuid NOT NULL REFERENCES custom_quote_requests (request_id) ON DELETE CASCADE,
|
||||
|
||||
original_filename text NOT NULL,
|
||||
stored_relative_path text NOT NULL, -- es: quote-requests/<requestId>/attachments/<attachmentId>/<uuid>.stl
|
||||
stored_filename text NOT NULL,
|
||||
|
||||
file_size_bytes bigint CHECK (file_size_bytes >= 0),
|
||||
mime_type text,
|
||||
sha256_hex text,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_custom_quote_attachments_request
|
||||
ON custom_quote_request_attachments (request_id);
|
||||
|
||||
Reference in New Issue
Block a user