79 lines
3.0 KiB
PL/PgSQL
79 lines
3.0 KiB
PL/PgSQL
-- =====================================================================
|
|
-- Migration 0016: Shift Assignments
|
|
-- Extends open_shift so managers/owners can assign a shift to any employee.
|
|
-- =====================================================================
|
|
|
|
drop function if exists app.open_shift(uuid, numeric, numeric);
|
|
|
|
create or replace function app.open_shift(
|
|
p_till_id uuid,
|
|
p_opening_usd numeric,
|
|
p_opening_lbp numeric,
|
|
p_assigned_user_id uuid default null
|
|
) returns uuid
|
|
language plpgsql
|
|
security definer
|
|
set search_path = app, public
|
|
as $$
|
|
declare
|
|
v_shop uuid;
|
|
v_shift uuid;
|
|
v_target_user uuid;
|
|
begin
|
|
if p_opening_usd is null or p_opening_lbp is null then
|
|
raise exception 'opening counts are required';
|
|
end if;
|
|
if p_opening_usd < 0 or p_opening_lbp < 0 then
|
|
raise exception 'opening counts must be non-negative';
|
|
end if;
|
|
|
|
select shop_id into v_shop from app.tills where id = p_till_id and is_active;
|
|
if v_shop is null then
|
|
raise exception 'till % not found or inactive', p_till_id;
|
|
end if;
|
|
|
|
v_target_user := coalesce(p_assigned_user_id, auth.uid());
|
|
|
|
-- Caller must have a role to open.
|
|
if not app.has_any_role_in_shop(v_shop, array['owner','manager','cashier']::app.business_role[]) then
|
|
raise exception 'not authorized to open a shift on this till';
|
|
end if;
|
|
|
|
-- If trying to open for someone else, must be owner or manager
|
|
if v_target_user <> auth.uid() then
|
|
if not app.has_any_role_in_shop(v_shop, array['owner','manager']::app.business_role[]) then
|
|
raise exception 'only managers or owners can assign shifts to other users';
|
|
end if;
|
|
end if;
|
|
|
|
-- Target user must have a role in the shop
|
|
if not exists (
|
|
select 1 from app.user_shop_assignments
|
|
where user_id = v_target_user and shop_id = v_shop
|
|
) then
|
|
raise exception 'target user does not have a role in this shop';
|
|
end if;
|
|
|
|
-- Reject if any non-closed shift exists on this till.
|
|
if exists (select 1 from app.shifts where till_id = p_till_id and status <> 'closed') then
|
|
raise exception 'till % already has an active shift; close it first', p_till_id;
|
|
end if;
|
|
|
|
insert into app.shifts(till_id, shop_id, user_id, opened_by, opening_usd, opening_lbp)
|
|
values (p_till_id, v_shop, v_target_user, auth.uid(), p_opening_usd, p_opening_lbp)
|
|
returning id into v_shift;
|
|
|
|
-- Record the opening float as a cash movement for clean ledgers.
|
|
insert into app.cash_movements(shift_id, type, currency, amount, note)
|
|
values (v_shift, 'opening_float', 'USD', p_opening_usd, 'opening float'),
|
|
(v_shift, 'opening_float', 'LBP', p_opening_lbp, 'opening float');
|
|
|
|
perform app.log_auth_event('shift_opened', v_shop, null,
|
|
jsonb_build_object('shift_id', v_shift, 'till_id', p_till_id, 'assigned_user_id', v_target_user));
|
|
return v_shift;
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function app.open_shift(uuid, numeric, numeric, uuid) from public;
|
|
grant execute on function app.open_shift(uuid, numeric, numeric, uuid) to authenticated;
|