исправление хранения календаря

This commit is contained in:
Zuev
2026-07-31 21:24:11 +03:00
parent 87f3d98621
commit cc422255a2
31 changed files with 825 additions and 425 deletions

View File

@@ -2,6 +2,7 @@
-- Инициализация расширений
-- ==========================================
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS btree_gist;
-- ===============================
-- Справочники высшего уровня
@@ -714,22 +715,25 @@ JOIN specialty_profiles profile ON profile.specialty_id = specialty.id AND profi
WHERE ay.title = '2025-2026'
ON CONFLICT (academic_year_id, specialty_profile_id, study_form_id, title) DO NOTHING;
CREATE TABLE IF NOT EXISTS academic_calendar_days (
CREATE TABLE IF NOT EXISTS academic_calendar_periods (
id BIGSERIAL PRIMARY KEY,
calendar_id BIGINT NOT NULL REFERENCES academic_calendars(id) ON DELETE CASCADE,
course_number INT NOT NULL,
date DATE NOT NULL,
week_number INT NOT NULL,
day_of_week INT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
activity_type_id BIGINT NOT NULL REFERENCES academic_calendar_activity_types(id),
CONSTRAINT chk_calendar_days_course_positive CHECK (course_number > 0),
CONSTRAINT chk_calendar_days_week_positive CHECK (week_number > 0),
CONSTRAINT chk_calendar_days_day CHECK (day_of_week BETWEEN 1 AND 7),
CONSTRAINT uq_calendar_days UNIQUE (calendar_id, course_number, date)
CONSTRAINT chk_calendar_periods_course_positive CHECK (course_number > 0),
CONSTRAINT chk_calendar_periods_dates CHECK (end_date >= start_date),
CONSTRAINT ex_calendar_periods_no_overlap
EXCLUDE USING gist (
calendar_id WITH =,
course_number WITH =,
(daterange(start_date, end_date, '[]')) WITH &&
)
);
CREATE INDEX IF NOT EXISTS idx_calendar_days_lookup
ON academic_calendar_days(calendar_id, course_number, date);
CREATE INDEX IF NOT EXISTS idx_calendar_periods_schedule
ON academic_calendar_periods(calendar_id, course_number, start_date, end_date);
CREATE OR REPLACE FUNCTION calculate_academic_calendar_week_number(
academic_year_start DATE,
@@ -750,20 +754,23 @@ AS $$
) + 1
$$;
INSERT INTO academic_calendar_days (calendar_id, course_number, date, week_number, day_of_week, activity_type_id)
INSERT INTO academic_calendar_periods (
calendar_id,
course_number,
start_date,
end_date,
activity_type_id
)
SELECT
calendar.id,
course.course_number,
days.date::DATE,
calculate_academic_calendar_week_number(ay.start_date, days.date::DATE),
EXTRACT(ISODOW FROM days.date::DATE)::INT,
ay.start_date,
ay.end_date,
activity.id
FROM academic_calendars calendar
JOIN academic_years ay ON ay.id = calendar.academic_year_id
JOIN academic_calendar_activity_types activity ON activity.code = 'Т'
CROSS JOIN GENERATE_SERIES(1, calendar.course_count) AS course(course_number)
CROSS JOIN GENERATE_SERIES(ay.start_date, ay.end_date, INTERVAL '1 day') AS days(date)
ON CONFLICT (calendar_id, course_number, date) DO NOTHING;
CROSS JOIN GENERATE_SERIES(1, calendar.course_count) AS course(course_number);
CREATE TABLE IF NOT EXISTS academic_calendar_subjects (
id BIGSERIAL PRIMARY KEY,
@@ -1134,7 +1141,7 @@ COMMENT ON TABLE academic_years IS 'Учебные годы';
COMMENT ON TABLE semesters IS 'Семестры учебного года';
COMMENT ON TABLE academic_calendar_activity_types IS 'Коды активностей календарного учебного графика';
COMMENT ON TABLE academic_calendars IS 'Календарные учебные графики по профилю, форме обучения и учебному году';
COMMENT ON TABLE academic_calendar_days IS 'Дневные ячейки календарного учебного графика';
COMMENT ON TABLE academic_calendar_periods IS 'Непересекающиеся периоды активности календарного учебного графика';
COMMENT ON TABLE academic_calendar_subjects IS 'Привязка дисциплин к календарному учебному графику по номерам учебных семестров';
COMMENT ON TABLE student_group_calendar_assignments IS 'Назначения календарных графиков учебным группам по учебному году';
COMMENT ON TABLE schedule_rules IS 'Правила динамической генерации расписания';
@@ -1275,12 +1282,11 @@ COMMENT ON COLUMN academic_calendars.specialty_profile_id IS 'ID профиля
COMMENT ON COLUMN academic_calendars.study_form_id IS 'ID формы обучения из education_forms';
COMMENT ON COLUMN academic_calendars.course_count IS 'Количество курсов в графике';
COMMENT ON COLUMN academic_calendar_days.calendar_id IS 'ID календарного учебного графика';
COMMENT ON COLUMN academic_calendar_days.course_number IS 'Номер курса';
COMMENT ON COLUMN academic_calendar_days.date IS 'Дата';
COMMENT ON COLUMN academic_calendar_days.week_number IS 'Номер недели в учебном году';
COMMENT ON COLUMN academic_calendar_days.day_of_week IS 'День недели ISO';
COMMENT ON COLUMN academic_calendar_days.activity_type_id IS 'ID кода активности';
COMMENT ON COLUMN academic_calendar_periods.calendar_id IS 'ID календарного учебного графика';
COMMENT ON COLUMN academic_calendar_periods.course_number IS 'Номер курса';
COMMENT ON COLUMN academic_calendar_periods.start_date IS 'Первая дата периода включительно';
COMMENT ON COLUMN academic_calendar_periods.end_date IS 'Последняя дата периода включительно';
COMMENT ON COLUMN academic_calendar_periods.activity_type_id IS 'ID кода активности';
COMMENT ON FUNCTION calculate_academic_calendar_week_number(DATE, DATE)
IS 'Номер недели календарного учебного графика по периодам понедельник–воскресенье';
@@ -1579,8 +1585,6 @@ BEGIN
END IF;
END $$;
CREATE EXTENSION IF NOT EXISTS btree_gist;
ALTER TABLE time_slots
ADD CONSTRAINT chk_time_slots_duration_matches_range
CHECK (
@@ -1818,7 +1822,7 @@ CREATE TRIGGER trg_academic_years_include_semesters
DO $$
DECLARE
assignment_violations BIGINT;
calendar_day_violations BIGINT;
calendar_period_violations BIGINT;
calendar_subject_violations BIGINT;
BEGIN
SELECT count(*)
@@ -1840,13 +1844,13 @@ BEGIN
END NOT BETWEEN 1 AND calendar.course_count;
SELECT count(*)
INTO calendar_day_violations
FROM academic_calendar_days calendar_day
JOIN academic_calendars calendar ON calendar.id = calendar_day.calendar_id
INTO calendar_period_violations
FROM academic_calendar_periods calendar_period
JOIN academic_calendars calendar ON calendar.id = calendar_period.calendar_id
JOIN academic_years academic_year ON academic_year.id = calendar.academic_year_id
WHERE calendar_day.course_number > calendar.course_count
OR calendar_day.date < academic_year.start_date
OR calendar_day.date > academic_year.end_date;
WHERE calendar_period.course_number > calendar.course_count
OR calendar_period.start_date < academic_year.start_date
OR calendar_period.end_date > academic_year.end_date;
SELECT count(*)
INTO calendar_subject_violations
@@ -1855,14 +1859,14 @@ BEGIN
WHERE calendar_subject.semester_number > calendar.course_count * 2;
IF assignment_violations > 0
OR calendar_day_violations > 0
OR calendar_period_violations > 0
OR calendar_subject_violations > 0 THEN
RAISE EXCEPTION USING
ERRCODE = '23514',
MESSAGE = format(
'Невозможно создать baseline: несовместимые назначения графиков=%s, строки сетки вне измерений=%s, дисциплины вне измерений=%s. Исправьте seed-данные V1 и повторите создание схемы.',
'Невозможно создать baseline: несовместимые назначения графиков=%s, периоды активности вне измерений=%s, дисциплины вне измерений=%s. Исправьте seed-данные V1 и повторите создание схемы.',
assignment_violations,
calendar_day_violations,
calendar_period_violations,
calendar_subject_violations
);
END IF;
@@ -1990,12 +1994,12 @@ BEGIN
IF EXISTS (
SELECT 1
FROM academic_calendar_days calendar_day
WHERE calendar_day.calendar_id = OLD.id
FROM academic_calendar_periods calendar_period
WHERE calendar_period.calendar_id = OLD.id
AND (
calendar_day.course_number > NEW.course_count
OR calendar_day.date < academic_year_start
OR calendar_day.date > academic_year_end
calendar_period.course_number > NEW.course_count
OR calendar_period.start_date < academic_year_start
OR calendar_period.end_date > academic_year_end
)
) THEN
RAISE EXCEPTION USING
@@ -2059,7 +2063,7 @@ CREATE TRIGGER trg_student_groups_protect_calendar_assignments
FOR EACH ROW
EXECUTE FUNCTION prevent_incompatible_student_group_change();
CREATE OR REPLACE FUNCTION enforce_calendar_day_dimensions()
CREATE OR REPLACE FUNCTION enforce_calendar_period_dimensions()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
@@ -2078,22 +2082,22 @@ BEGIN
IF calendar_course_count IS NOT NULL
AND (
NEW.course_number > calendar_course_count
OR NEW.date < academic_year_start
OR NEW.date > academic_year_end
OR NEW.start_date < academic_year_start
OR NEW.end_date > academic_year_end
) THEN
RAISE EXCEPTION USING
ERRCODE = '23514',
MESSAGE = 'Строка сетки выходит за измерения календарного графика';
MESSAGE = 'Период активности выходит за измерения календарного графика';
END IF;
RETURN NEW;
END $$;
CREATE TRIGGER trg_calendar_days_dimensions
BEFORE INSERT OR UPDATE OF calendar_id, course_number, date
ON academic_calendar_days
CREATE TRIGGER trg_calendar_periods_dimensions
BEFORE INSERT OR UPDATE OF calendar_id, course_number, start_date, end_date
ON academic_calendar_periods
FOR EACH ROW
EXECUTE FUNCTION enforce_calendar_day_dimensions();
EXECUTE FUNCTION enforce_calendar_period_dimensions();
CREATE OR REPLACE FUNCTION enforce_calendar_subject_dimensions()
RETURNS TRIGGER
@@ -2132,11 +2136,11 @@ BEGIN
IF EXISTS (
SELECT 1
FROM academic_calendars calendar
JOIN academic_calendar_days calendar_day ON calendar_day.calendar_id = calendar.id
JOIN academic_calendar_periods calendar_period ON calendar_period.calendar_id = calendar.id
WHERE calendar.academic_year_id = OLD.id
AND (
calendar_day.date < NEW.start_date
OR calendar_day.date > NEW.end_date
calendar_period.start_date < NEW.start_date
OR calendar_period.end_date > NEW.end_date
)
) OR EXISTS (
SELECT 1