Migración 0004: maestros, perfil (tutor_id, foto_path), bucket avatares, fallback tutor en crear_solicitud
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
-- 0004_perfil_maestros_avatares.sql
|
||||
-- 3 bloques:
|
||||
-- A) tabla prestamos.maestros (catalogo de tutores) + RLS
|
||||
-- B) profiles.tutor_id + profiles.foto_path
|
||||
-- C) bucket 'avatares' publico con RLS por carpeta = uid
|
||||
-- + update de crear_solicitud: fallback a nombre del tutor si el
|
||||
-- alumno deja p_maestro_responsable vacio.
|
||||
|
||||
begin;
|
||||
|
||||
-- ============================================================
|
||||
-- A) Tabla de maestros (catalogo admin-managed)
|
||||
-- ============================================================
|
||||
create table if not exists prestamos.maestros (
|
||||
id serial primary key,
|
||||
nombre text not null unique,
|
||||
activo boolean not null default true,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table prestamos.maestros enable row level security;
|
||||
|
||||
drop policy if exists maestros_read_all on prestamos.maestros;
|
||||
drop policy if exists maestros_admin_write on prestamos.maestros;
|
||||
|
||||
create policy maestros_read_all on prestamos.maestros
|
||||
for select to authenticated using (true);
|
||||
|
||||
create policy maestros_admin_write on prestamos.maestros
|
||||
for all to authenticated
|
||||
using (prestamos.is_admin())
|
||||
with check (prestamos.is_admin());
|
||||
|
||||
grant select, insert, update, delete on prestamos.maestros to authenticated, service_role;
|
||||
grant usage, select on prestamos.maestros_id_seq to authenticated, service_role;
|
||||
|
||||
insert into prestamos.maestros (nombre) values
|
||||
('Sin especificar')
|
||||
on conflict (nombre) do nothing;
|
||||
|
||||
-- ============================================================
|
||||
-- B) profiles: tutor_id + foto_path
|
||||
-- ============================================================
|
||||
alter table prestamos.profiles
|
||||
add column if not exists tutor_id int references prestamos.maestros(id) on delete set null,
|
||||
add column if not exists foto_path text;
|
||||
|
||||
-- policy profiles_update_self (definida en 0001) usa:
|
||||
-- with check (id = auth.uid() and rol = (select rol from ...))
|
||||
-- solo bloquea cambios al rol, permite editar matricula/semestre/tutor_id/foto_path.
|
||||
-- No requiere cambios.
|
||||
|
||||
-- ============================================================
|
||||
-- C) Bucket 'avatares' publico + policies por carpeta = uid
|
||||
-- Path pattern esperado: '<uid>/<timestamp>.<ext>'
|
||||
-- ============================================================
|
||||
insert into storage.buckets (id, name, public)
|
||||
values ('avatares', 'avatares', true)
|
||||
on conflict (id) do update set public = true;
|
||||
|
||||
drop policy if exists "avatares_read_all" on storage.objects;
|
||||
drop policy if exists "avatares_owner_write" on storage.objects;
|
||||
|
||||
create policy "avatares_read_all" on storage.objects
|
||||
for select to anon, authenticated
|
||||
using (bucket_id = 'avatares');
|
||||
|
||||
create policy "avatares_owner_write" on storage.objects
|
||||
for all to authenticated
|
||||
using (
|
||||
bucket_id = 'avatares'
|
||||
and (storage.foldername(name))[1] = auth.uid()::text
|
||||
)
|
||||
with check (
|
||||
bucket_id = 'avatares'
|
||||
and (storage.foldername(name))[1] = auth.uid()::text
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- D) crear_solicitud: si p_maestro_responsable llega vacio,
|
||||
-- autocompletar con el nombre del tutor guardado en profiles.
|
||||
-- Si el usuario tampoco tiene tutor, sigue el raise
|
||||
-- 'maestro_responsable_requerido' original -> el checkout ya
|
||||
-- guarda contra ese caso (perfil incompleto).
|
||||
-- ============================================================
|
||||
create or replace function prestamos.crear_solicitud(
|
||||
p_maestro_responsable text,
|
||||
p_notas text,
|
||||
p_items jsonb
|
||||
) returns bigint
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = prestamos, pg_temp
|
||||
as $$
|
||||
declare
|
||||
v_alumno uuid := auth.uid();
|
||||
v_solicitud_id bigint;
|
||||
v_item jsonb;
|
||||
v_material_id int;
|
||||
v_cantidad int;
|
||||
v_descripcion text;
|
||||
v_disponible int;
|
||||
v_estado_mat text;
|
||||
v_trackeado boolean;
|
||||
v_unidad_id bigint;
|
||||
v_asignadas int;
|
||||
v_maestro text;
|
||||
begin
|
||||
if v_alumno is null then
|
||||
raise exception 'no_autenticado';
|
||||
end if;
|
||||
|
||||
v_maestro := nullif(trim(coalesce(p_maestro_responsable, '')), '');
|
||||
|
||||
-- fallback al tutor del perfil si el alumno no escribio nada
|
||||
if v_maestro is null then
|
||||
select m.nombre into v_maestro
|
||||
from prestamos.profiles p
|
||||
join prestamos.maestros m on m.id = p.tutor_id
|
||||
where p.id = v_alumno;
|
||||
end if;
|
||||
|
||||
if v_maestro is null then
|
||||
raise exception 'maestro_responsable_requerido';
|
||||
end if;
|
||||
|
||||
if p_items is null or jsonb_typeof(p_items) <> 'array' or jsonb_array_length(p_items) = 0 then
|
||||
raise exception 'items_requeridos';
|
||||
end if;
|
||||
|
||||
for v_material_id in
|
||||
select distinct (elem->>'material_id')::int
|
||||
from jsonb_array_elements(p_items) elem
|
||||
order by 1
|
||||
loop
|
||||
perform 1 from prestamos.materiales where id = v_material_id for update;
|
||||
end loop;
|
||||
|
||||
insert into prestamos.solicitudes (alumno_id, estado, maestro_responsable, notas)
|
||||
values (v_alumno, 'pendiente', v_maestro, nullif(trim(coalesce(p_notas, '')), ''))
|
||||
returning id into v_solicitud_id;
|
||||
|
||||
for v_item in select * from jsonb_array_elements(p_items)
|
||||
loop
|
||||
v_material_id := (v_item->>'material_id')::int;
|
||||
v_cantidad := (v_item->>'cantidad')::int;
|
||||
v_descripcion := nullif(trim(coalesce(v_item->>'descripcion', '')), '');
|
||||
|
||||
if v_material_id is null or v_cantidad is null or v_cantidad <= 0 then
|
||||
raise exception 'item_invalido';
|
||||
end if;
|
||||
|
||||
select cantidad_disponible, estado, trackeado_por_unidad
|
||||
into v_disponible, v_estado_mat, v_trackeado
|
||||
from prestamos.materiales where id = v_material_id;
|
||||
|
||||
if v_estado_mat is null then
|
||||
raise exception 'material_no_existe: %', v_material_id;
|
||||
end if;
|
||||
if v_estado_mat <> 'disponible' then
|
||||
raise exception 'material_no_disponible: %', v_material_id;
|
||||
end if;
|
||||
if v_disponible < v_cantidad then
|
||||
raise exception 'stock_insuficiente: %', v_material_id;
|
||||
end if;
|
||||
|
||||
if v_trackeado then
|
||||
v_asignadas := 0;
|
||||
while v_asignadas < v_cantidad loop
|
||||
select id into v_unidad_id
|
||||
from prestamos.material_unidades
|
||||
where material_id = v_material_id and estado = 'disponible'
|
||||
order by id
|
||||
for update skip locked
|
||||
limit 1;
|
||||
|
||||
if v_unidad_id is null then
|
||||
raise exception 'sin_unidad_disponible: %', v_material_id;
|
||||
end if;
|
||||
|
||||
insert into prestamos.solicitud_items
|
||||
(solicitud_id, material_id, cantidad, descripcion, material_unidad_id)
|
||||
values (v_solicitud_id, v_material_id, 1, v_descripcion, v_unidad_id);
|
||||
|
||||
update prestamos.material_unidades
|
||||
set estado = 'prestado'
|
||||
where id = v_unidad_id;
|
||||
|
||||
v_asignadas := v_asignadas + 1;
|
||||
end loop;
|
||||
else
|
||||
insert into prestamos.solicitud_items (solicitud_id, material_id, cantidad, descripcion)
|
||||
values (v_solicitud_id, v_material_id, v_cantidad, v_descripcion);
|
||||
end if;
|
||||
end loop;
|
||||
|
||||
return v_solicitud_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function prestamos.crear_solicitud(text, text, jsonb) to authenticated;
|
||||
|
||||
commit;
|
||||
Reference in New Issue
Block a user