Fix RBAC, user creation and update components
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
-- =====================================================================
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user