feat(i18n): add VAT labels and email sent messages in German and English
style(admin): increase max-width of admin page and adjust table styles fix(n8n): enhance workflow import and publishing process fix(workflows): update SQL queries for fetching and updating sales orders feat(migrations): normalize rental types and enhance email guard for individuell rentals feat(migrations): add RPC for updating deposit in sales orders fix(migrations): ensure individuell orders persist net/vat components and backfill existing records test: update last run status to failed
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
-- 13-rental-type-daily-and-email-guard.sql
|
||||
-- Introduce explicit 'single_day' rental_type, normalize legacy values,
|
||||
-- and harden auto-email guard for individuell rentals.
|
||||
|
||||
-- =============================================================================
|
||||
-- A. Normalize and expand rental_type checks
|
||||
-- =============================================================================
|
||||
|
||||
alter table public.leads drop constraint if exists leads_rental_type_check;
|
||||
alter table public.sales_orders drop constraint if exists sales_orders_rental_type_check;
|
||||
|
||||
update public.leads
|
||||
set rental_type = lower(trim(coalesce(rental_type, '')));
|
||||
|
||||
update public.sales_orders
|
||||
set rental_type = lower(trim(coalesce(rental_type, '')));
|
||||
|
||||
update public.leads
|
||||
set rental_type = 'individuell'
|
||||
where rental_type in ('individual', 'custom');
|
||||
|
||||
update public.sales_orders
|
||||
set rental_type = 'individuell'
|
||||
where rental_type in ('individual', 'custom');
|
||||
|
||||
update public.leads
|
||||
set rental_type = 'single_day'
|
||||
where rental_type in ('day', 'daily', '1 tag', '1_tag', 'single_day');
|
||||
|
||||
update public.sales_orders
|
||||
set rental_type = 'single_day'
|
||||
where rental_type in ('day', 'daily', '1 tag', '1_tag', 'single_day');
|
||||
|
||||
-- Existing one-day bookings should be single_day.
|
||||
update public.leads
|
||||
set rental_type = 'single_day'
|
||||
where rental_type = 'weekend'
|
||||
and total_days = 1;
|
||||
|
||||
update public.sales_orders
|
||||
set rental_type = 'single_day'
|
||||
where rental_type = 'weekend'
|
||||
and total_days = 1;
|
||||
|
||||
-- Two-day non-Saturday starts are effectively single_day rentals, not weekend packages.
|
||||
update public.leads
|
||||
set rental_type = 'single_day'
|
||||
where rental_type = 'weekend'
|
||||
and total_days = 2
|
||||
and date_from is not null
|
||||
and extract(isodow from date_from) <> 6;
|
||||
|
||||
update public.sales_orders
|
||||
set rental_type = 'single_day'
|
||||
where rental_type = 'weekend'
|
||||
and total_days = 2
|
||||
and date_from is not null
|
||||
and extract(isodow from date_from) <> 6;
|
||||
|
||||
-- Fallback for any unexpected value.
|
||||
update public.leads
|
||||
set rental_type = 'weekend'
|
||||
where rental_type not in ('single_day', 'weekend', 'individuell');
|
||||
|
||||
update public.sales_orders
|
||||
set rental_type = 'weekend'
|
||||
where rental_type not in ('single_day', 'weekend', 'individuell');
|
||||
|
||||
alter table public.leads
|
||||
alter column rental_type set default 'weekend';
|
||||
|
||||
alter table public.sales_orders
|
||||
alter column rental_type set default 'weekend';
|
||||
|
||||
alter table public.leads
|
||||
add constraint leads_rental_type_check
|
||||
check (rental_type in ('single_day', 'weekend', 'individuell'));
|
||||
|
||||
alter table public.sales_orders
|
||||
add constraint sales_orders_rental_type_check
|
||||
check (rental_type in ('single_day', 'weekend', 'individuell'));
|
||||
|
||||
-- =============================================================================
|
||||
-- B. Harden notify_lead_qualified() against malformed rental_type values
|
||||
-- =============================================================================
|
||||
|
||||
create or replace function public.notify_lead_qualified()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
v_rental_type text := coalesce(lower(trim(NEW.rental_type)), 'weekend');
|
||||
begin
|
||||
-- Never auto-email individuell orders (including legacy synonyms).
|
||||
if v_rental_type in ('individuell', 'individual', 'custom') then
|
||||
return NEW;
|
||||
end if;
|
||||
|
||||
perform pg_notify('lead_qualified', json_build_object(
|
||||
'sales_order_id', NEW.id,
|
||||
'customer_id', NEW.customer_id,
|
||||
'lead_id', NEW.lead_id,
|
||||
'order_number', NEW.order_number,
|
||||
'total_eur', NEW.total_eur,
|
||||
'deposit_eur', NEW.deposit_eur,
|
||||
'date_from', NEW.date_from,
|
||||
'date_to', NEW.date_to,
|
||||
'vehicle_label', NEW.vehicle_label,
|
||||
'rental_type', v_rental_type,
|
||||
'email_sent', NEW.email_sent
|
||||
)::text);
|
||||
|
||||
return NEW;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- =============================================================================
|
||||
-- C. Update create_lead() classification logic to include daily
|
||||
-- =============================================================================
|
||||
|
||||
create or replace function public.create_lead(
|
||||
p_name text,
|
||||
p_email text,
|
||||
p_phone text default '',
|
||||
p_vehicle_id uuid default null,
|
||||
p_vehicle_label text default '',
|
||||
p_date_from date default null,
|
||||
p_date_to date default null,
|
||||
p_message text default '',
|
||||
p_source text default 'website',
|
||||
p_ip_address text default '',
|
||||
p_ip_country text default ''
|
||||
)
|
||||
returns uuid
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
v_lead_id uuid;
|
||||
v_vehicle record;
|
||||
v_total_days integer := 0;
|
||||
v_weekend_days integer := 0;
|
||||
v_weekdays integer := 0;
|
||||
v_daily_subtotal integer := 0;
|
||||
v_weekend_subtotal integer := 0;
|
||||
v_subtotal_eur integer := 0;
|
||||
v_vat_eur integer := 0;
|
||||
v_total_eur integer := 0;
|
||||
v_deposit_eur integer := 0;
|
||||
v_rental_type text := 'weekend';
|
||||
v_cur date;
|
||||
v_dow integer;
|
||||
begin
|
||||
if p_vehicle_id is not null and p_date_from is not null and p_date_to is not null and p_date_to > p_date_from then
|
||||
select daily_price_eur, weekend_price_eur, kaution_eur
|
||||
into v_vehicle
|
||||
from public.vehicles
|
||||
where id = p_vehicle_id;
|
||||
|
||||
if found then
|
||||
v_total_days := (p_date_to - p_date_from);
|
||||
|
||||
-- Classification:
|
||||
-- 1 day => single_day
|
||||
-- 2 days starting Saturday => weekend package
|
||||
-- 2 days otherwise => single_day
|
||||
-- > 2 days => individuell (manual processing)
|
||||
if v_total_days > 2 then
|
||||
v_rental_type := 'individuell';
|
||||
elsif v_total_days = 1 then
|
||||
v_rental_type := 'single_day';
|
||||
elsif v_total_days = 2 and extract(isodow from p_date_from) = 6 then
|
||||
v_rental_type := 'weekend';
|
||||
elsif v_total_days = 2 then
|
||||
v_rental_type := 'single_day';
|
||||
else
|
||||
v_rental_type := 'weekend';
|
||||
end if;
|
||||
|
||||
if v_rental_type = 'individuell' then
|
||||
v_daily_subtotal := 0;
|
||||
v_weekend_subtotal := 0;
|
||||
v_subtotal_eur := 0;
|
||||
v_vat_eur := 0;
|
||||
v_total_eur := 0;
|
||||
v_deposit_eur := 0;
|
||||
else
|
||||
v_cur := p_date_from;
|
||||
while v_cur < p_date_to loop
|
||||
v_dow := extract(isodow from v_cur);
|
||||
if v_dow in (6, 7) then
|
||||
v_weekend_days := v_weekend_days + 1;
|
||||
end if;
|
||||
v_cur := v_cur + 1;
|
||||
end loop;
|
||||
|
||||
v_weekdays := v_total_days - v_weekend_days;
|
||||
v_daily_subtotal := v_weekdays * v_vehicle.daily_price_eur;
|
||||
v_weekend_subtotal := v_weekend_days * (case when v_vehicle.weekend_price_eur > 0 then v_vehicle.weekend_price_eur else v_vehicle.daily_price_eur end);
|
||||
v_subtotal_eur := v_daily_subtotal + v_weekend_subtotal;
|
||||
v_vat_eur := round(v_subtotal_eur * 0.20);
|
||||
v_total_eur := v_subtotal_eur + v_vat_eur;
|
||||
v_deposit_eur := coalesce(nullif(v_vehicle.kaution_eur, 0), 5000);
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
|
||||
insert into public.leads (
|
||||
name, email, phone, vehicle_id, vehicle_label, date_from, date_to,
|
||||
message, source,
|
||||
daily_subtotal, weekend_subtotal, subtotal_eur, vat_eur, total_eur, deposit_eur,
|
||||
total_days, weekday_count, weekend_day_count, ip_address, ip_country,
|
||||
rental_type
|
||||
) values (
|
||||
p_name, p_email, p_phone, p_vehicle_id, p_vehicle_label, p_date_from, p_date_to,
|
||||
p_message, p_source,
|
||||
v_daily_subtotal, v_weekend_subtotal, v_subtotal_eur, v_vat_eur, v_total_eur, v_deposit_eur,
|
||||
v_total_days, v_weekdays, v_weekend_days, p_ip_address, p_ip_country,
|
||||
v_rental_type
|
||||
)
|
||||
returning id into v_lead_id;
|
||||
|
||||
return v_lead_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.create_lead(
|
||||
text, text, text, uuid, text, date, date, text, text, text, text
|
||||
) to anon, authenticated, service_role;
|
||||
|
||||
notify pgrst, 'reload schema';
|
||||
@@ -0,0 +1,29 @@
|
||||
-- 14-sales-order-set-deposit.sql
|
||||
-- Adds sales_order_set_deposit RPC for updating deposit from admin pricing tab.
|
||||
|
||||
-- =============================================================================
|
||||
-- A. RPC: sales_order_set_deposit
|
||||
-- =============================================================================
|
||||
|
||||
create or replace function public.sales_order_set_deposit(p_so_id uuid, p_deposit_eur integer)
|
||||
returns void
|
||||
language plpgsql
|
||||
security invoker
|
||||
as $$
|
||||
begin
|
||||
update public.sales_orders
|
||||
set deposit_eur = p_deposit_eur, updated_at = now()
|
||||
where id = p_so_id;
|
||||
if not found then
|
||||
raise exception 'sales order % not found', p_so_id;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.sales_order_set_deposit(uuid, integer) to authenticated;
|
||||
|
||||
-- =============================================================================
|
||||
-- B. Schema reload
|
||||
-- =============================================================================
|
||||
|
||||
notify pgrst, 'reload schema';
|
||||
@@ -0,0 +1,61 @@
|
||||
-- Ensure individuell orders persist net/vat components when total is manually set
|
||||
-- and backfill existing records where these fields are still zero.
|
||||
|
||||
create or replace function public.sales_order_set_total(p_so_id uuid, p_total_eur integer)
|
||||
returns void
|
||||
language plpgsql
|
||||
security invoker
|
||||
as $$
|
||||
declare
|
||||
v_so public.sales_orders;
|
||||
v_subtotal_eur integer := 0;
|
||||
v_vat_eur integer := 0;
|
||||
begin
|
||||
select * into v_so from public.sales_orders where id = p_so_id for update;
|
||||
if not found then
|
||||
raise exception 'sales order % not found', p_so_id;
|
||||
end if;
|
||||
|
||||
if v_so.rental_type != 'individuell' then
|
||||
raise exception 'can only set total for individuell orders';
|
||||
end if;
|
||||
|
||||
if coalesce(p_total_eur, 0) < 0 then
|
||||
raise exception 'total must be >= 0';
|
||||
end if;
|
||||
|
||||
if p_total_eur > 0 then
|
||||
v_subtotal_eur := round(p_total_eur / 1.2);
|
||||
v_vat_eur := p_total_eur - v_subtotal_eur;
|
||||
end if;
|
||||
|
||||
update public.sales_orders
|
||||
set total_eur = p_total_eur,
|
||||
subtotal_eur = v_subtotal_eur,
|
||||
vat_eur = v_vat_eur,
|
||||
daily_subtotal = v_subtotal_eur,
|
||||
weekend_subtotal = 0,
|
||||
weekday_count = coalesce(total_days, 0),
|
||||
weekend_day_count = 0,
|
||||
updated_at = now()
|
||||
where id = p_so_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.sales_order_set_total(uuid, integer) to authenticated;
|
||||
|
||||
-- Backfill already existing individuell orders with missing net/vat split.
|
||||
update public.sales_orders
|
||||
set subtotal_eur = round(total_eur / 1.2),
|
||||
vat_eur = total_eur - round(total_eur / 1.2),
|
||||
daily_subtotal = round(total_eur / 1.2),
|
||||
weekend_subtotal = 0,
|
||||
weekday_count = coalesce(total_days, 0),
|
||||
weekend_day_count = 0,
|
||||
updated_at = now()
|
||||
where rental_type = 'individuell'
|
||||
and coalesce(total_eur, 0) > 0
|
||||
and coalesce(subtotal_eur, 0) = 0
|
||||
and coalesce(vat_eur, 0) = 0;
|
||||
|
||||
notify pgrst, 'reload schema';
|
||||
Reference in New Issue
Block a user