144 lines
4.8 KiB
SQL
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;
|