fiddy/packages/db/migrations/008_schedules_pivot.sql
Nico f8e426542d
Some checks failed
Build & Deploy Fiddy (Dokploy) / build (push) Has been cancelled
Build & Deploy Fiddy (Dokploy) / deploy (push) Has been cancelled
feat: implement schedules pivot, scheduler service, and dokploy deploy flow
2026-02-15 17:10:58 -08:00

144 lines
4.8 KiB
SQL

create type schedule_frequency as enum ('DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY');
create type schedule_end_condition as enum ('NEVER', 'AFTER_COUNT', 'BY_DATE');
create table schedules(
id bigserial primary key,
group_id bigint not null references groups(id) on delete cascade,
created_by bigint not null references users(id),
entry_type entry_type not null default 'SPENDING',
amount_dollars numeric(12,2) not null check (amount_dollars >= 0),
necessity spending_necessity not null,
purchase_type text not null,
notes text,
starts_on date not null,
frequency schedule_frequency not null,
interval_count integer not null default 1 check (interval_count > 0),
end_condition schedule_end_condition not null default 'NEVER',
end_count integer,
end_date date,
next_run_on date not null,
last_run_on date,
run_count integer not null default 0,
is_active boolean not null default true,
legacy_source_entry_id bigint references entries(id) on delete set null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index schedules_group_next_run_idx on schedules(group_id, next_run_on);
create index schedules_created_by_idx on schedules(created_by);
create table schedule_tags(
schedule_id bigint not null references schedules(id) on delete cascade,
tag_id bigint not null references tags(id) on delete cascade,
created_at timestamptz not null default now(),
primary key (schedule_id, tag_id)
);
create index schedule_tags_schedule_idx on schedule_tags(schedule_id);
create index schedule_tags_tag_idx on schedule_tags(tag_id);
alter table entries
add column source_schedule_id bigint references schedules(id) on delete set null;
create unique index entries_schedule_occurrence_unique
on entries(source_schedule_id, occurred_at)
where source_schedule_id is not null;
with legacy_recurring as (
select
e.id,
e.group_id,
e.created_by,
e.entry_type,
e.amount_dollars,
e.necessity,
e.purchase_type,
e.notes,
e.occurred_at,
case
when e.frequency::text = 'BIWEEKLY' then 'WEEKLY'::schedule_frequency
when e.frequency::text = 'QUARTERLY' then 'MONTHLY'::schedule_frequency
when e.frequency::text in ('DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY') then e.frequency::text::schedule_frequency
else 'MONTHLY'::schedule_frequency
end as schedule_frequency,
case
when e.frequency::text = 'BIWEEKLY' then greatest(coalesce(e.interval_count, 1), 1) * 2
when e.frequency::text = 'QUARTERLY' then greatest(coalesce(e.interval_count, 1), 1) * 3
else greatest(coalesce(e.interval_count, 1), 1)
end as safe_interval_count,
coalesce(e.end_condition::text, 'NEVER')::schedule_end_condition as schedule_end_condition,
e.end_count,
e.end_date,
coalesce(e.next_run_at, e.occurred_at) as legacy_base_date
from entries e
where e.is_recurring = true
), inserted_schedules as (
insert into schedules(
group_id,
created_by,
entry_type,
amount_dollars,
necessity,
purchase_type,
notes,
starts_on,
frequency,
interval_count,
end_condition,
end_count,
end_date,
next_run_on,
last_run_on,
run_count,
is_active,
legacy_source_entry_id
)
select
lr.group_id,
lr.created_by,
lr.entry_type,
lr.amount_dollars,
lr.necessity,
lr.purchase_type,
lr.notes,
lr.legacy_base_date as starts_on,
lr.schedule_frequency,
lr.safe_interval_count,
lr.schedule_end_condition,
lr.end_count,
lr.end_date,
case
when lr.schedule_frequency = 'DAILY' then (lr.legacy_base_date + (lr.safe_interval_count::text || ' day')::interval)::date
when lr.schedule_frequency = 'WEEKLY' then (lr.legacy_base_date + (lr.safe_interval_count::text || ' week')::interval)::date
when lr.schedule_frequency = 'MONTHLY' then (lr.legacy_base_date + (lr.safe_interval_count::text || ' month')::interval)::date
else (lr.legacy_base_date + (lr.safe_interval_count::text || ' year')::interval)::date
end as next_run_on,
lr.legacy_base_date as last_run_on,
1 as run_count,
case
when lr.schedule_end_condition = 'AFTER_COUNT' and coalesce(lr.end_count, 0) <= 1 then false
when lr.schedule_end_condition = 'BY_DATE' and lr.end_date is not null and lr.end_date < lr.legacy_base_date then false
else true
end as is_active,
lr.id as legacy_source_entry_id
from legacy_recurring lr
returning id, legacy_source_entry_id
)
insert into schedule_tags(schedule_id, tag_id)
select i.id, et.tag_id
from inserted_schedules i
join entry_tags et on et.entry_id = i.legacy_source_entry_id
on conflict do nothing;
update entries
set is_recurring = false,
frequency = null,
interval_count = 1,
end_condition = null,
end_count = null,
end_date = null,
next_run_at = null,
last_executed_at = null
where is_recurring = true;