310 lines
9.1 KiB
PL/PgSQL
310 lines
9.1 KiB
PL/PgSQL
set search_path = app, public;
|
|
|
|
create table if not exists app.end_of_day_reports (
|
|
id uuid primary key default gen_random_uuid(),
|
|
shop_id uuid not null references app.shops(id) on delete restrict,
|
|
business_date date not null,
|
|
submitted_at timestamptz not null default now(),
|
|
submitted_by uuid not null references auth.users(id) on delete restrict,
|
|
submitted_by_name text not null,
|
|
note text,
|
|
period_started_at timestamptz not null,
|
|
period_ended_at timestamptz not null,
|
|
completed_txn_count bigint not null default 0,
|
|
voided_txn_count bigint not null default 0,
|
|
gross_usd numeric(14,2) not null default 0,
|
|
gross_lbp numeric(18,0) not null default 0,
|
|
safe_drop_usd numeric(14,2) not null default 0,
|
|
safe_drop_lbp numeric(18,0) not null default 0,
|
|
closed_shift_count bigint not null default 0,
|
|
total_variance_usd numeric(14,2) not null default 0,
|
|
total_variance_lbp numeric(18,0) not null default 0,
|
|
activity_count bigint not null default 0,
|
|
activity_log jsonb not null default '[]'::jsonb,
|
|
unique (shop_id, business_date)
|
|
);
|
|
|
|
alter table app.end_of_day_reports enable row level security;
|
|
alter table app.end_of_day_reports force row level security;
|
|
revoke insert, update, delete on app.end_of_day_reports from authenticated;
|
|
|
|
drop policy if exists end_of_day_reports_select on app.end_of_day_reports;
|
|
create policy end_of_day_reports_select on app.end_of_day_reports
|
|
for select to authenticated
|
|
using (
|
|
app.has_any_role_in_shop(shop_id,
|
|
array['owner','auditor']::app.business_role[])
|
|
);
|
|
|
|
grant select on app.end_of_day_reports to authenticated;
|
|
|
|
create or replace view app.v_end_of_day_reports as
|
|
select
|
|
id,
|
|
shop_id,
|
|
business_date,
|
|
submitted_at,
|
|
submitted_by,
|
|
submitted_by_name,
|
|
note,
|
|
period_started_at,
|
|
period_ended_at,
|
|
completed_txn_count,
|
|
voided_txn_count,
|
|
gross_usd,
|
|
gross_lbp,
|
|
safe_drop_usd,
|
|
safe_drop_lbp,
|
|
closed_shift_count,
|
|
total_variance_usd,
|
|
total_variance_lbp,
|
|
activity_count,
|
|
activity_log
|
|
from app.end_of_day_reports;
|
|
|
|
grant select on app.v_end_of_day_reports to authenticated;
|
|
|
|
create or replace function app.submit_end_of_day(
|
|
p_shop uuid,
|
|
p_note text default null
|
|
)
|
|
returns table (
|
|
report_id uuid,
|
|
business_date date,
|
|
submitted_at timestamptz,
|
|
completed_txn_count bigint,
|
|
gross_usd numeric,
|
|
gross_lbp numeric,
|
|
activity_count bigint
|
|
)
|
|
language plpgsql
|
|
security definer
|
|
set search_path = app, public
|
|
as $$
|
|
declare
|
|
v_period_started_at timestamptz;
|
|
v_period_ended_at timestamptz := now();
|
|
v_business_date date := (now() at time zone 'Asia/Beirut')::date;
|
|
v_open_shift_count integer;
|
|
v_report_id uuid;
|
|
v_submitted_by_name text;
|
|
v_completed_txn_count bigint;
|
|
v_voided_txn_count bigint;
|
|
v_gross_usd numeric(14,2);
|
|
v_gross_lbp numeric(18,0);
|
|
v_safe_drop_usd numeric(14,2);
|
|
v_safe_drop_lbp numeric(18,0);
|
|
v_closed_shift_count bigint;
|
|
v_total_variance_usd numeric(14,2);
|
|
v_total_variance_lbp numeric(18,0);
|
|
v_activity_count bigint;
|
|
v_activity_log jsonb;
|
|
begin
|
|
if not app.has_role_in_shop(p_shop, 'owner') then
|
|
raise exception 'owner role required';
|
|
end if;
|
|
|
|
select count(*)
|
|
into v_open_shift_count
|
|
from app.shifts
|
|
where shop_id = p_shop
|
|
and status <> 'closed';
|
|
|
|
if v_open_shift_count > 0 then
|
|
raise exception 'close or finalize all shifts before submitting end of day';
|
|
end if;
|
|
|
|
if exists (
|
|
select 1
|
|
from app.end_of_day_reports eod
|
|
where eod.shop_id = p_shop
|
|
and eod.business_date = v_business_date
|
|
) then
|
|
raise exception 'end of day already submitted for this shop today';
|
|
end if;
|
|
|
|
select coalesce(
|
|
max(period_ended_at),
|
|
date_trunc('day', now() at time zone 'Asia/Beirut') at time zone 'Asia/Beirut'
|
|
)
|
|
into v_period_started_at
|
|
from app.end_of_day_reports
|
|
where shop_id = p_shop;
|
|
|
|
select coalesce(full_name, email, auth.uid()::text)
|
|
into v_submitted_by_name
|
|
from auth.users
|
|
where id = auth.uid();
|
|
|
|
select
|
|
count(*) filter (where t.status = 'completed'),
|
|
count(*) filter (where t.status = 'voided'),
|
|
coalesce(sum(t.gross_usd) filter (where t.status = 'completed'), 0),
|
|
coalesce(sum(t.gross_lbp) filter (where t.status = 'completed'), 0)
|
|
into v_completed_txn_count, v_voided_txn_count, v_gross_usd, v_gross_lbp
|
|
from app.transactions t
|
|
where t.shop_id = p_shop
|
|
and t.occurred_at >= v_period_started_at
|
|
and t.occurred_at <= v_period_ended_at;
|
|
|
|
select
|
|
coalesce(sum(case when cm.currency = 'USD' then abs(cm.amount) else 0 end), 0),
|
|
coalesce(sum(case when cm.currency = 'LBP' then abs(cm.amount) else 0 end), 0)
|
|
into v_safe_drop_usd, v_safe_drop_lbp
|
|
from app.cash_movements cm
|
|
join app.shifts sh on sh.id = cm.shift_id
|
|
where sh.shop_id = p_shop
|
|
and cm.type = 'drop_to_safe'
|
|
and cm.occurred_at >= v_period_started_at
|
|
and cm.occurred_at <= v_period_ended_at;
|
|
|
|
select
|
|
count(*),
|
|
coalesce(sum(sh.variance_usd), 0),
|
|
coalesce(sum(sh.variance_lbp), 0)
|
|
into v_closed_shift_count, v_total_variance_usd, v_total_variance_lbp
|
|
from app.shifts sh
|
|
where sh.shop_id = p_shop
|
|
and sh.status = 'closed'
|
|
and sh.closed_at is not null
|
|
and sh.closed_at >= v_period_started_at
|
|
and sh.closed_at <= v_period_ended_at;
|
|
|
|
select
|
|
count(*),
|
|
coalesce(
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'occurred_at', ae.occurred_at,
|
|
'event_type', ae.event_type,
|
|
'metadata', ae.metadata
|
|
)
|
|
order by ae.occurred_at desc
|
|
),
|
|
'[]'::jsonb
|
|
)
|
|
into v_activity_count, v_activity_log
|
|
from app.auth_events ae
|
|
where ae.shop_id = p_shop
|
|
and ae.occurred_at >= v_period_started_at
|
|
and ae.occurred_at <= v_period_ended_at
|
|
and ae.event_type <> 'end_of_day_submitted';
|
|
|
|
insert into app.end_of_day_reports(
|
|
shop_id,
|
|
business_date,
|
|
submitted_by,
|
|
submitted_by_name,
|
|
note,
|
|
period_started_at,
|
|
period_ended_at,
|
|
completed_txn_count,
|
|
voided_txn_count,
|
|
gross_usd,
|
|
gross_lbp,
|
|
safe_drop_usd,
|
|
safe_drop_lbp,
|
|
closed_shift_count,
|
|
total_variance_usd,
|
|
total_variance_lbp,
|
|
activity_count,
|
|
activity_log
|
|
)
|
|
values (
|
|
p_shop,
|
|
v_business_date,
|
|
auth.uid(),
|
|
coalesce(v_submitted_by_name, auth.uid()::text),
|
|
nullif(btrim(coalesce(p_note, '')), ''),
|
|
v_period_started_at,
|
|
v_period_ended_at,
|
|
coalesce(v_completed_txn_count, 0),
|
|
coalesce(v_voided_txn_count, 0),
|
|
coalesce(v_gross_usd, 0),
|
|
coalesce(v_gross_lbp, 0),
|
|
coalesce(v_safe_drop_usd, 0),
|
|
coalesce(v_safe_drop_lbp, 0),
|
|
coalesce(v_closed_shift_count, 0),
|
|
coalesce(v_total_variance_usd, 0),
|
|
coalesce(v_total_variance_lbp, 0),
|
|
coalesce(v_activity_count, 0),
|
|
coalesce(v_activity_log, '[]'::jsonb)
|
|
)
|
|
returning id into v_report_id;
|
|
|
|
perform app.log_auth_event(
|
|
'end_of_day_submitted',
|
|
p_shop,
|
|
null,
|
|
jsonb_build_object(
|
|
'report_id', v_report_id,
|
|
'business_date', v_business_date,
|
|
'completed_txn_count', coalesce(v_completed_txn_count, 0),
|
|
'gross_usd', coalesce(v_gross_usd, 0),
|
|
'gross_lbp', coalesce(v_gross_lbp, 0),
|
|
'activity_count', coalesce(v_activity_count, 0)
|
|
)
|
|
);
|
|
|
|
return query
|
|
select
|
|
r.id,
|
|
r.business_date,
|
|
r.submitted_at,
|
|
r.completed_txn_count,
|
|
r.gross_usd,
|
|
r.gross_lbp,
|
|
r.activity_count
|
|
from app.end_of_day_reports r
|
|
where r.id = v_report_id;
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function app.submit_end_of_day(uuid, text) from public;
|
|
grant execute on function app.submit_end_of_day(uuid, text) to authenticated;
|
|
|
|
create or replace view app.v_owner_dashboard as
|
|
with latest_eod as (
|
|
select distinct on (shop_id)
|
|
shop_id,
|
|
submitted_at
|
|
from app.end_of_day_reports
|
|
order by shop_id, submitted_at desc
|
|
),
|
|
periods as (
|
|
select
|
|
s.id as shop_id,
|
|
coalesce(
|
|
le.submitted_at,
|
|
date_trunc('day', now() at time zone 'Asia/Beirut') at time zone 'Asia/Beirut'
|
|
) as period_started_at,
|
|
le.submitted_at as last_end_of_day_at
|
|
from app.shops s
|
|
left join latest_eod le on le.shop_id = s.id
|
|
)
|
|
select
|
|
s.id as shop_id,
|
|
s.name as shop_name,
|
|
(select count(*) from app.shifts where shop_id = s.id and status = 'open') as open_shifts,
|
|
(select count(*) from app.alerts where shop_id = s.id and acknowledged_at is null) as open_alerts,
|
|
(select count(*) from app.alerts where shop_id = s.id and acknowledged_at is null and severity = 'critical') as critical_alerts,
|
|
(select count(*)
|
|
from app.reconciliation_exceptions e
|
|
join app.settlements st on st.id = e.settlement_id
|
|
where st.shop_id = s.id
|
|
and e.resolved_at is null) as open_recon_exceptions,
|
|
(select coalesce(sum(t.gross_usd), 0)
|
|
from app.transactions t
|
|
where t.shop_id = s.id
|
|
and t.status = 'completed'
|
|
and t.occurred_at >= p.period_started_at) as today_gross_usd,
|
|
(select coalesce(sum(t.gross_lbp), 0)
|
|
from app.transactions t
|
|
where t.shop_id = s.id
|
|
and t.status = 'completed'
|
|
and t.occurred_at >= p.period_started_at) as today_gross_lbp,
|
|
p.period_started_at,
|
|
p.last_end_of_day_at
|
|
from app.shops s
|
|
join periods p on p.shop_id = s.id;
|