Files
OMT-SM/supabase/migrations/0038_service_ui_settings.sql
T

114 lines
4.4 KiB
PL/PgSQL

-- =====================================================================
-- Migration 0038 — Shop-scoped service titles and icons.
--
-- Lets owners/managers customize how transaction services appear in the
-- UI without changing canonical service codes used by accounting logic.
-- =====================================================================
set search_path = app, public;
create table if not exists app.service_ui_settings (
shop_id uuid not null references app.shops(id) on delete cascade,
service_code text not null references app.services(code) on delete cascade,
display_name text,
icon text,
updated_at timestamptz not null default now(),
updated_by uuid references auth.users(id),
primary key (shop_id, service_code),
constraint service_ui_display_name_len check (display_name is null or length(display_name) between 1 and 80),
constraint service_ui_icon_len check (icon is null or length(icon) between 1 and 16)
);
alter table app.service_ui_settings enable row level security;
alter table app.service_ui_settings force row level security;
revoke insert, update, delete on app.service_ui_settings from authenticated;
grant select on app.service_ui_settings to authenticated;
drop policy if exists service_ui_select on app.service_ui_settings;
create policy service_ui_select on app.service_ui_settings
for select using (
app.has_any_role_in_shop(shop_id, array['owner','manager','cashier','auditor']::app.business_role[])
);
create or replace view app.v_service_ui_settings as
select
s.id as shop_id,
svc.code as service_code,
svc.name as default_name,
svc.category,
svc.is_active,
ui.display_name,
ui.icon,
ui.updated_at,
ui.updated_by
from app.shops s
join app.services svc on svc.is_active = true
left join app.service_ui_settings ui
on ui.shop_id = s.id and ui.service_code = svc.code
where app.has_any_role_in_shop(s.id, array['owner','manager','cashier','auditor']::app.business_role[]);
grant select on app.v_service_ui_settings to authenticated;
create or replace function app.set_service_ui_setting(
p_shop uuid,
p_service_code text,
p_display_name text,
p_icon text
) returns void
language plpgsql
security definer
set search_path = app, public
as $$
declare
v_display text := nullif(btrim(coalesce(p_display_name, '')), '');
v_icon text := nullif(btrim(coalesce(p_icon, '')), '');
begin
if not app.has_any_role_in_shop(p_shop, array['owner','manager']::app.business_role[]) then
raise exception 'manager or owner role required';
end if;
if not exists (select 1 from app.services where code = p_service_code and is_active) then
raise exception 'unknown or inactive service %', p_service_code;
end if;
if v_display is null and v_icon is null then
delete from app.service_ui_settings
where shop_id = p_shop and service_code = p_service_code;
return;
end if;
insert into app.service_ui_settings(shop_id, service_code, display_name, icon, updated_by)
values (p_shop, p_service_code, v_display, v_icon, auth.uid())
on conflict (shop_id, service_code) do update
set display_name = excluded.display_name,
icon = excluded.icon,
updated_at = now(),
updated_by = auth.uid();
end;
$$;
revoke all on function app.set_service_ui_setting(uuid, text, text, text) from public;
grant execute on function app.set_service_ui_setting(uuid, text, text, text) to authenticated;
-- Recent transaction rows should use the shop's display title when set.
drop view if exists app.v_my_recent_transactions;
create view app.v_my_recent_transactions as
select t.id, t.reference_no, t.shop_id, t.till_id, t.shift_id,
t.service_code,
coalesce(ui.display_name, s.name) as service_name,
s.category,
t.payment_method,
t.gross_usd, t.gross_lbp,
t.fee_usd + t.commission_usd as revenue_usd,
t.fee_lbp + t.commission_lbp as revenue_lbp,
t.external_ref, t.external_ref_provider,
t.beneficiary_name, t.beneficiary_phone,
t.status, t.occurred_at, t.user_id
from app.transactions t
join app.services s on s.code = t.service_code
left join app.service_ui_settings ui
on ui.shop_id = t.shop_id and ui.service_code = t.service_code
where t.user_id = auth.uid()
or app.has_any_role_in_shop(t.shop_id,
array['owner','manager','auditor']::app.business_role[]);
grant select on app.v_my_recent_transactions to authenticated;