diff --git a/app/assets/images/p_for_plano.png b/app/assets/images/p_for_plano.png new file mode 100644 index 000000000..099c95d6d Binary files /dev/null and b/app/assets/images/p_for_plano.png differ diff --git a/app/controllers/conflicts/session_conflicts_controller.rb b/app/controllers/conflicts/session_conflicts_controller.rb index f34c2d93d..d7b8bbd27 100644 --- a/app/controllers/conflicts/session_conflicts_controller.rb +++ b/app/controllers/conflicts/session_conflicts_controller.rb @@ -1,36 +1,17 @@ class Conflicts::SessionConflictsController < ApplicationController respond_to :json - def index + # Add the requested conflict id to the ignored conflicts + def ignore authorize Conflicts::SessionConflict, policy_class: Conflicts::SessionConflictPolicy - per_page = params[:perPage]&.to_i || Conflicts::SessionConflict.default_per_page - current_page = params[:current_page]&.to_i + IgnoredConflict.transaction do + permitted = params.permit(:conflict_id, :conflict_type) - collection = Conflicts::SessionConflict - .includes([:session,:person,:session_assignment,:room]) - .where("session_assignment_name is null or session_assignment_name in ('Moderator', 'Participant', 'Invisible')") - .where("conflict_session_assignment_name is null or conflict_session_assignment_name in ('Moderator', 'Participant', 'Invisible')") - .distinct.page(current_page).per(per_page) - - collection_total = collection.total_count - full_collection_total = Conflicts::SessionConflict.distinct.count + ignored = IgnoredConflict.create(permitted) + end - meta = {} - meta[:total] = collection_total - meta[:full_total] = full_collection_total ? full_collection_total : collection_total - meta[:current_page] = current_page if current_page.present? - meta[:perPage] = per_page if per_page.present? - - options = { - meta: meta, - params: { - domain: "#{request.base_url}", - current_person: current_person - } - } - render json: Conflicts::SessionConflictSerializer.new(collection,options).serializable_hash(), - content_type: 'application/json' + render status: :ok, json: {}.to_json, content_type: 'application/json' end def conflicts_with @@ -42,6 +23,7 @@ def conflicts_with .where("conflict_session_id = ?", session_id) .where("session_assignment_name is null or session_assignment_name in ('Moderator', 'Participant', 'Invisible')") .where("conflict_session_assignment_name is null or conflict_session_assignment_name in ('Moderator', 'Participant', 'Invisible')") + .where("session_conflicts.conflict_id not in (select conflict_id from ignored_conflicts)") .distinct meta = {} @@ -50,7 +32,7 @@ def conflicts_with options = { meta: meta, include: [ - # :session + :session ], params: { domain: "#{request.base_url}", @@ -70,6 +52,7 @@ def conflicts_for .where("session_id = ?", session_id) .where("session_assignment_name is null or session_assignment_name in ('Moderator', 'Participant', 'Invisible')") .where("conflict_session_assignment_name is null or conflict_session_assignment_name in ('Moderator', 'Participant', 'Invisible')") + .where("session_conflicts.conflict_id not in (select conflict_id from ignored_conflicts)") .distinct meta = {} @@ -78,7 +61,7 @@ def conflicts_for options = { meta: meta, include: [ - # :conflict_session + :conflict_session ], params: { domain: "#{request.base_url}", diff --git a/app/controllers/reports/conflict_reports_controller.rb b/app/controllers/reports/conflict_reports_controller.rb index 14c11f895..ef7a5246a 100644 --- a/app/controllers/reports/conflict_reports_controller.rb +++ b/app/controllers/reports/conflict_reports_controller.rb @@ -8,6 +8,7 @@ def multiple_sessions_in_room room_conflicts = Conflicts::RoomConflict .includes(:room) .references(:room) + .where("room_conflicts.back_to_back = false") .order('rooms.name, start_time') workbook = FastExcel.open(constant_memory: true) @@ -68,6 +69,7 @@ def person_exclusion_conflicts :person, :session, :exclusion, :excluded_session ) .where("session_assignment_name in ('Moderator', 'Participant', 'Invisible')") + .order('people.published_name asc') workbook = FastExcel.open(constant_memory: true) worksheet = workbook.add_worksheet("Person vs Exclusion") @@ -232,7 +234,7 @@ def back_to_back_to_back def back_to_back authorize SessionAssignment, policy_class: Reports::ConflictReportPolicy - conflicts_table = ::Conflicts::PersonScheduleConflict.arel_table + conflicts_table = ::Conflicts::PersonBackToBack.arel_table subquery = Session.area_list.as('areas_list') conflict_subquery = Session.area_list.as('conflict_areas_list') joins = [ @@ -252,8 +254,8 @@ def back_to_back ) ] - conflicts = Conflicts::PersonScheduleConflict.select( - Conflicts::PersonScheduleConflict.arel_table[Arel.star], + conflicts = Conflicts::PersonBackToBack.select( + Conflicts::PersonBackToBack.arel_table[Arel.star], 'areas_list.area_list as area_list', 'conflict_areas_list.area_list as conflict_area_list' ) @@ -262,7 +264,6 @@ def back_to_back .joins(joins) .where("session_assignment_name is null or session_assignment_name in ('Moderator', 'Participant', 'Invisible')") .where("conflict_session_assignment_name is null or conflict_session_assignment_name in ('Moderator', 'Participant', 'Invisible')") - .where(back_to_back: true) .order('published_name asc, conflict_start_time asc') workbook = FastExcel.open(constant_memory: true) @@ -347,8 +348,7 @@ def people_double_booked .joins(joins) .where("session_assignment_name is null or session_assignment_name in ('Moderator', 'Participant', 'Invisible')") .where("conflict_session_assignment_name is null or conflict_session_assignment_name in ('Moderator', 'Participant', 'Invisible')") - .where(back_to_back: false) - .order('people.published_name asc, conflict_start_time asc') + .order('people.published_name asc, start_time asc') workbook = FastExcel.open(constant_memory: true) worksheet = workbook.add_worksheet("People Double Booked") diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 2364eabdf..5b797e343 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -277,6 +277,9 @@ def allowed_params status environment minors_participation + require_signup + age_restriction_id + room_notes ] # Tags # format diff --git a/app/javascript/components/icon_button.vue b/app/javascript/components/icon_button.vue index b7c5897ad..d909f1ea7 100644 --- a/app/javascript/components/icon_button.vue +++ b/app/javascript/components/icon_button.vue @@ -12,7 +12,9 @@ v-b-tooltip.bottom v-bind="$attrs" > - + + + {{disabledTooltip}} @@ -77,7 +79,7 @@ export default { styles.pointerEvents = 'none'; } return styles; - } + }, }, } diff --git a/app/javascript/components/table_vue.vue b/app/javascript/components/table_vue.vue index 9cd5c72e2..30eaea3bb 100644 --- a/app/javascript/components/table_vue.vue +++ b/app/javascript/components/table_vue.vue @@ -188,7 +188,9 @@ export default { data () { return { selected_items: [], - editable_ids: [] + editable_ids: [], + keep: false, // semaphore to catch "false" unselect + selecedRowNbr: -1 // keep track of selected row to keep display } }, computed: { @@ -266,9 +268,21 @@ export default { onRowSelected(items) { this.selected_items = items if (items[0] && (items.length == 1)) { + // keep the index of the row that was selected + this.selecedRowNbr = this.sortedCollection.indexOf(items[0]) this.select(items[0]); } else { - this.select(null); + if (this.keep) { // semaphore to keep selected from getting unselected ... + // This is ugly but it works !!!! + this.keep = false + if (this.selecedRowNbr > -1) { + this.$refs.table.selectRow(this.selecedRowNbr) + } + } else { + // standard unselect + this.select(null); + this.selecedRowNbr = -1; + } } }, onSortChanged(ctx) { @@ -288,21 +302,30 @@ export default { } }, watch: { + sortedCollection(nv, ov) { + if (ov.length == 0 && nv.length > 0) { + this.keep = false + } else { + // If the length has not changed ... + this.keep = true + } + }, selected(val) { if (!val && this.selected_items.length == 1) { this.$refs.table.clearSelected() } }, - currentPage(ov,nv) { + currentPage(nv,ov) { if (ov != nv) { // page was changed so we clear our selected this.selected_items = [] this.editable_ids = [] + this.keep = false + this.selecedRowNbr = -1 } } }, mounted() { - // ensure that there is no model selected when the table is loaded this.editable_ids = [] this.unselect(); } diff --git a/app/javascript/conflicts/ignore_button.vue b/app/javascript/conflicts/ignore_button.vue new file mode 100644 index 000000000..4442e2ae8 --- /dev/null +++ b/app/javascript/conflicts/ignore_button.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/app/javascript/conflicts/session_conflicts.vue b/app/javascript/conflicts/session_conflicts.vue index e2fb55131..47517506b 100644 --- a/app/javascript/conflicts/session_conflicts.vue +++ b/app/javascript/conflicts/session_conflicts.vue @@ -11,12 +11,14 @@ v-for="conflict in conflicts" :key="conflict.id" >
+
+
@@ -32,6 +34,7 @@ import modelMixin from '../store/model.mixin'; import modelUtilsMixin from "@/store/model_utils.mixin" import { sessionModel } from '@/store/session.store' import dateTimeMixin from '../components/date_time.mixin' +import IgnoreButton from './ignore_button.vue'; export default { name: "SessionConflicts", @@ -52,6 +55,9 @@ export default { conflicts: [], conflicts_with: [] }), + components: { + IgnoreButton + }, computed: { selectedSession() { return this.get_model(sessionModel, this.sessionId) @@ -80,6 +86,13 @@ export default { } }, methods: { + ignore(conflict) { + this.ignore_conflict({conflict_id: conflict.id, conflict_type: conflict.conflict_type}).then( + () => { + this.getConflicts(this.sessionId) + } + ) + }, getConflicts(sessionId) { this.conflicts = [] this.conflicts_with = [] diff --git a/app/javascript/constants/strings.js b/app/javascript/constants/strings.js index 6654f9a05..e0d47ce23 100644 --- a/app/javascript/constants/strings.js +++ b/app/javascript/constants/strings.js @@ -197,6 +197,9 @@ module.exports = { ERROR_GENERIC_UNRECOVERABLE: (email) => twoLines("The server has encountered an internal error and was unable to complete your request.", `Please contact the server administrator at ${email} and let them know the time and date the error occurred.`), + ADD_CONFLICT_IGNORE_SUCCESS: "Ignore Conflict Added", + ADD_CONFLICT_IGNORE_ERROR: "Ignore Conflict Failed", + // Social Links Errors TWITTER_ID_INVALID_MSG: "Twitter ID is not in a valid format", FACEBOOK_ID_INVALID_MSG: "Facebook ID is not in a valid format", @@ -241,4 +244,25 @@ module.exports = { SURVEY_REDIRECT: "Unfortunately due to the browser refreshing we have lost any answers you filled in. Please fill the survey out again.", SURVEY_PUBLIC_NO_EDIT: "You cannot edit a published survey. Close the survey to enable editing.", SURVEY_PUBLIC_NO_DELETE: "You cannot delete a published survey. Close the survey to enable deletion.", + SESSION_ENVIRONMENT: { + unknown: "Unknown", + in_person: "In Person", + hybrid: "Hybrid", + virtual: "Virtual" + }, + SESSION_STATUS: { + draft: "Draft", + reviewed: "Reviewed", + revised: "Revised", + dropped: "Dropped", + }, + SESSION_MUST_UNSCHEDULE: "You must unschedule a session before dropping it", + SESSION_MUST_UNDROP: "You must un-drop the session to be able to schedule it.", + SESSION_MINORS_PARTICIPATION: { + kids_observe: "Kids welcome to observe", + kids_supervision: "Kids welcome to participate with supervision", + kids_participate: "Kids welcome to participate", + geared_families: "Geared towards families", + geared_kids: "Geared towards kids", + }, } diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 54fa32ad3..5427d3743 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -47,7 +47,9 @@ Vue.config.errorHandler = (err, vm, info) => { } } -Vue.use(BootstrapVue); +Vue.use(BootstrapVue, { + BSkeleton: { animation: 'fade' } +}); Vue.use(BootstrapVueIcons); Vue.use(CustomIconsPlugin); Vue.use(AsyncComputed); diff --git a/app/javascript/reports/reports_screen.vue b/app/javascript/reports/reports_screen.vue index 38b609997..71c24882c 100644 --- a/app/javascript/reports/reports_screen.vue +++ b/app/javascript/reports/reports_screen.vue @@ -28,9 +28,9 @@
  • Participants over Con Limit
  • - +
  • Non-Accepted Participants on Scheduled Sessions
  • @@ -51,9 +51,9 @@
  • Sessions with no Moderator
  • - +
  • Sessions with Participants not Scheduled
  • diff --git a/app/javascript/schedule/schedule_screen.vue b/app/javascript/schedule/schedule_screen.vue index 9feb24e2a..ec4a597ff 100644 --- a/app/javascript/schedule/schedule_screen.vue +++ b/app/javascript/schedule/schedule_screen.vue @@ -142,7 +142,8 @@ export default { ["room_id", "is null"] ] }, - ["duration", ">", "0"] + ["duration", ">", "0"], + ["status", "!=", "dropped"] ] } diff --git a/app/javascript/sessions/age_restriction.mixin.js b/app/javascript/sessions/age_restriction.mixin.js new file mode 100644 index 000000000..de84baebb --- /dev/null +++ b/app/javascript/sessions/age_restriction.mixin.js @@ -0,0 +1,19 @@ +import { settingsMixin } from "@/mixins" + +export const ageRestrictionMixin = { + mixins: [ settingsMixin ], + computed: { + ageRestrictionOptions() { + const opts = this.currentSettings.age_restrictions?.map(ar => ({text: ar.name, value: ar.id})) || [] + opts.push({text: 'None', value: null}) + return opts; + } + }, + methods: { + ageRestrictionName(id) { + return this.currentSettings.age_restrictions?.find(ar => ar.id === id)?.name || id; + } + } +} + +export default ageRestrictionMixin diff --git a/app/javascript/sessions/datetime_picker.vue b/app/javascript/sessions/datetime_picker.vue index 7fa6a219d..91417ef3e 100644 --- a/app/javascript/sessions/datetime_picker.vue +++ b/app/javascript/sessions/datetime_picker.vue @@ -3,8 +3,8 @@
    Time
    - - + +
    @@ -16,7 +16,13 @@ import { settingsMixin } from '@/mixins'; export default { name: "DatetimePicker", - props: ['value'], + props: { + value: null, + disabled: { + type: Boolean, + default: false + } + }, mixins: [settingsMixin], data: () => ({ tempDate: null diff --git a/app/javascript/sessions/minors_participation.mixin.js b/app/javascript/sessions/minors_participation.mixin.js new file mode 100644 index 000000000..cdaafa22f --- /dev/null +++ b/app/javascript/sessions/minors_participation.mixin.js @@ -0,0 +1,30 @@ +import { SESSION_MINORS_PARTICIPATION } from "@/constants/strings"; + +export const minorsParticipationMixin = { + data: () => ({ + SESSION_MINORS_PARTICIPATION + }), + computed: { + minorsParticipationOptions() { + return Object.entries(SESSION_MINORS_PARTICIPATION).map(([value, text]) => ({value, text})) + }, + minors_participation: { + get() { + const session = this.session || this.selected; + try { + const existing = JSON.parse(session.minors_participation) + return Array.isArray(existing) ? existing : existing ? [existing] : [] + } catch { + return [] + } + }, + set(val) { + if(this.session) { + this.session.minors_participation = JSON.stringify(val); + } + } + } + } +} + +export default minorsParticipationMixin; diff --git a/app/javascript/sessions/session.js b/app/javascript/sessions/session.js index e64b75c2f..844da4041 100644 --- a/app/javascript/sessions/session.js +++ b/app/javascript/sessions/session.js @@ -1,6 +1,10 @@ +import { SESSION_STATUS } from '@/constants/strings'; import AreaSelectForSearch from './area_select_for_search' export const session_columns = [ + // { + // key: 'id' + // }, { key: 'title', sortKey: 'sessions.title', @@ -53,20 +57,29 @@ export const session_columns = [ { key: 'status', label: 'Status', + formatter: (value) => SESSION_STATUS[value] || value, + sortable: true, + sortKey: 'status', + choices: ['draft', 'reviewed', 'revised', 'dropped'].map(value => ({label: SESSION_STATUS[value], value})), + type: "select" }, { key: 'open_for_interest', label: 'Open for Interest', type: "radio", choices: [{label: "Yes", value: "true"}, {label: "No", value: "false"}], - formatter: (value) => value ? "Yes" : "No" + formatter: (value) => value ? "Yes" : "No", + sortable: true, + sortKey: 'open_for_interest' }, { key: 'require_signup', label: 'Requires Signup', type: "radio", choices: [{label: "Yes", value: "true"}, {label: "No", value: "false"}], - formatter: (value) => value ? "Yes" : "No" + formatter: (value) => value ? "Yes" : "No", + sortable: true, + sortKey: 'require_signup' }, { key: 'publish', diff --git a/app/javascript/sessions/session_edit.vue b/app/javascript/sessions/session_edit.vue index b56787cad..d6c79e5ef 100644 --- a/app/javascript/sessions/session_edit.vue +++ b/app/javascript/sessions/session_edit.vue @@ -60,15 +60,65 @@ - + +
    +
    + + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + + +
    +
    ({ editable: false, - saving: false + saving: false, + SESSION_ENVIRONMENT, }), computed: { session() { @@ -158,7 +222,7 @@ export default { this.session.session_areas_attributes = areasForSaving } - } + }, }, methods: { edit() { @@ -172,7 +236,15 @@ export default { }, saveSession() { this.save_model(sessionModel, this.session) - } + }, + saveValidatedSession({dirty, valid=null}) { + if(dirty && valid) { + this.save_model(sessionModel, this.session) + } + }, + getValidationState({ dirty, validated, valid = null }) { + return dirty || validated ? valid : null; + }, } } diff --git a/app/javascript/sessions/session_schedule.vue b/app/javascript/sessions/session_schedule.vue index 67fe2fac0..fd593059a 100644 --- a/app/javascript/sessions/session_schedule.vue +++ b/app/javascript/sessions/session_schedule.vue @@ -2,14 +2,27 @@
    + + + {{SESSION_MUST_UNDROP}} +
    Space
    - - + + -
    - + + minutes -
    + {{ validationCtx.errors[0] }} +
    @@ -20,6 +33,14 @@ import { modelMixinNoProp } from "@/mixins"; import RoomPicker from './room_picker'; import DatetimePicker from './datetime_picker'; +import { SESSION_MUST_UNDROP } from "@/constants/strings"; +import { ValidationProvider, extend } from 'vee-validate'; +import { min_value } from 'vee-validate/dist/rules' + +extend('min_value', { + ...min_value, + message: "Sessions can't be less than 10 minutes long" + }) export default { name: "SessionSchedule", @@ -28,12 +49,37 @@ export default { ], components: { RoomPicker, - DatetimePicker + DatetimePicker, + ValidationProvider }, data: () => ({ tempDuration: null, - model: 'session' + model: 'session', + SESSION_MUST_UNDROP }), + computed: { + scheduleDisabled() { + return this.selected.status === 'dropped' + }, + duration: { + get() { + return this.tempDuration || this.selected.duration; + }, + set(val) { + this.tempDuration = val; + } + } + }, + methods: { + validatedPatchSelected(data, {dirty, valid=null}) { + if(dirty && valid) { + this.patchSelected(data); + } + }, + getValidationState({ dirty, validated, valid = null }) { + return dirty || validated ? valid : null; + }, + } } diff --git a/app/javascript/sessions/session_sidebar.vue b/app/javascript/sessions/session_sidebar.vue index 15d588c7e..e81358d71 100644 --- a/app/javascript/sessions/session_sidebar.vue +++ b/app/javascript/sessions/session_sidebar.vue @@ -3,7 +3,7 @@ @@ -11,10 +11,14 @@
    +
    Status:
    +
    + {{SESSION_STATUS[selected.status]}} +
    Description:
    -
    +
    @@ -55,14 +59,25 @@
    None Selected
    Format
    {{selected.format.name}}
    - +
    None Set
    +
    Attendee Age Restrictions
    +
    {{ ageRestrictionName(selected.age_restriction_id)}}
    +
    None
    +
    Minors Participation
    +
    {{ SESSION_MINORS_PARTICIPATION[mp]}}
    +
    No Selection
    Interest Instructions
    No Entry
    +
    Required Room Features/Services
    +
    {{selected.room_notes}}
    +
    No Entry
    Scheduled Participant Notes
    No Entry
    @@ -114,6 +129,9 @@ import { areaMixin, scheduledMixin, startTimeMixin } from './session_fields.mixi import { sessionConflictModel } from '@/store/session_conflict.store'; import SessionConflicts from '@/conflicts/session_conflicts.vue'; // import SessionAdminTab from './session_admin_tab'; +import { SESSION_ENVIRONMENT, SESSION_STATUS} from '@/constants/strings'; +import { minorsParticipationMixin } from './minors_participation.mixin'; +import { ageRestrictionMixin } from './age_restriction.mixin'; export default { name: 'SessionSidebar', @@ -129,10 +147,14 @@ export default { personSessionMixin, areaMixin, scheduledMixin, - startTimeMixin + startTimeMixin, + minorsParticipationMixin, + ageRestrictionMixin, ], data: () => ({ - sessionConflictModel + sessionConflictModel, + SESSION_ENVIRONMENT, + SESSION_STATUS }), computed: { editLink() { diff --git a/app/javascript/sessions/session_summary.vue b/app/javascript/sessions/session_summary.vue index 54ae497ac..9ef7508c0 100644 --- a/app/javascript/sessions/session_summary.vue +++ b/app/javascript/sessions/session_summary.vue @@ -66,6 +66,14 @@ :checked="scheduled" >Scheduled + + + {{SESSION_STATUS.draft}} + {{SESSION_STATUS.reviewed}} + {{SESSION_STATUS.revised}} + {{SESSION_STATUS.dropped}} + +
    @@ -75,6 +83,7 @@ import { sessionModel } from '@/store/session.store' import modelUtilsMixin from '@/store/model_utils.mixin'; import { scheduledMixin } from './session_fields.mixin'; +import { SESSION_STATUS, SESSION_MUST_UNSCHEDULE } from '@/constants/strings'; import PlanoEditor from '../components/plano_editor'; @@ -87,6 +96,10 @@ export default { modelUtilsMixin, scheduledMixin ], + data: () => ({ + SESSION_STATUS, + SESSION_MUST_UNSCHEDULE + }), computed: { session() { return this.selected_model(sessionModel); diff --git a/app/javascript/sessions/session_table.vue b/app/javascript/sessions/session_table.vue index b7e222afe..b20a92936 100644 --- a/app/javascript/sessions/session_table.vue +++ b/app/javascript/sessions/session_table.vue @@ -36,9 +36,6 @@ - diff --git a/app/javascript/sessions/view_participants.vue b/app/javascript/sessions/view_participants.vue index 978bbf251..364247ee7 100644 --- a/app/javascript/sessions/view_participants.vue +++ b/app/javascript/sessions/view_participants.vue @@ -1,9 +1,21 @@ @@ -26,21 +38,29 @@ export default { modelMixinNoProp, ], data: () => ({ - model + model, + loading: false }), + methods: { + load() { + this.clear(); + this.loading = true; + this.fetch({session_id: this.session.id}).then(() => { + this.loading = false; + }) + } + }, watch: { session(newSession, oldSession) { if (newSession) { if ((oldSession && oldSession.id !== newSession.id) || !oldSession) { - this.clear(); - this.fetch({session_id: this.session.id}) + this.load(); } } } }, mounted() { - this.clear(); - this.fetch({session_id: this.session.id}) + this.load(); } } diff --git a/app/javascript/store/model.mixin.js b/app/javascript/store/model.mixin.js index 971a98618..f48030a85 100644 --- a/app/javascript/store/model.mixin.js +++ b/app/javascript/store/model.mixin.js @@ -11,7 +11,7 @@ export const modelMixinNoProp = { selected() { return this.$store.getters[SELECTED]({model: this.model}) }, - collection() { + collection() { return Object.values(this.$store.getters['jv/get']({_jv: { type: this.model }})) } }, diff --git a/app/javascript/store/session_conflict.mixin.js b/app/javascript/store/session_conflict.mixin.js index 4fda258eb..7f5db09a1 100644 --- a/app/javascript/store/session_conflict.mixin.js +++ b/app/javascript/store/session_conflict.mixin.js @@ -2,21 +2,36 @@ import {mapActions } from 'vuex'; import { sessionConflictModel as model} from '@/store/session_conflict.store'; import {GET_CONFLICTS_FOR_SESSION} from '@/store/session_conflict.store'; import {GET_CONFLICTS_WITH_SESSION} from '@/store/session_conflict.store'; +import {IGNORE_CONFLICT} from '@/store/session_conflict.store'; -import modelMixin from "./model.mixin"; +import { toastMixin, modelMixin } from "@/mixins"; + +import { + ADD_CONFLICT_IGNORE_SUCCESS, + ADD_CONFLICT_IGNORE_ERROR +} from '../constants/strings' export const sessionConflictMixin = { - mixins: [modelMixin], + mixins: [modelMixin, toastMixin], methods: { ...mapActions({ get_conflicts_for_session: GET_CONFLICTS_FOR_SESSION, - get_conflicts_with_session: GET_CONFLICTS_WITH_SESSION + get_conflicts_with_session: GET_CONFLICTS_WITH_SESSION, + do_ignore_conflict: IGNORE_CONFLICT }), get_conflicts({session_id}) { return this.get_conflicts_for_session({session_id: session_id}); }, get_conflicts_with({session_id}) { return this.get_conflicts_with_session({session_id: session_id}); + }, + ignore_conflict({conflict_id, conflict_type}, success_text = ADD_CONFLICT_IGNORE_SUCCESS, error_text = ADD_CONFLICT_IGNORE_ERROR) { + return this.toastPromise( + this.do_ignore_conflict({conflict_id: conflict_id, conflict_type: conflict_type}), + success_text, + error_text + ); + } } } diff --git a/app/javascript/store/session_conflict.store.js b/app/javascript/store/session_conflict.store.js index 3e3818f71..eaddae892 100644 --- a/app/javascript/store/session_conflict.store.js +++ b/app/javascript/store/session_conflict.store.js @@ -1,5 +1,6 @@ export const GET_CONFLICTS_FOR_SESSION = 'GET CONFLICTS FOR SESSION'; export const GET_CONFLICTS_WITH_SESSION = 'GET CONFLICTS WITH SESSION'; +export const IGNORE_CONFLICT = 'IGNORE CONFLICT' export const sessionConflictModel = 'session_conflict'; export const sessionConflictEndpoints = { @@ -29,6 +30,9 @@ export const sessionConflictStore = { res({}); } }) + }, + [IGNORE_CONFLICT] ({commit, dispatch, state}, {conflict_id, conflict_type}) { + return dispatch('jv/get',`/session_conflict/ignore/${conflict_type}/${conflict_id}`); } }, selected: { diff --git a/app/lib/migration_helpers/plano_views.rb b/app/lib/migration_helpers/plano_views.rb index 02a4570ab..4764ebb9d 100644 --- a/app/lib/migration_helpers/plano_views.rb +++ b/app/lib/migration_helpers/plano_views.rb @@ -2,9 +2,11 @@ module MigrationHelpers module PlanoViews def self.create_views self.create_person_schedules + self.create_person_and_exclusions self.create_room_allocations self.create_room_conflicts self.create_person_schedule_conflicts + self.create_person_back_to_back self.create_person_exclusion_conflicts self.create_person_back_to_back_to_back self.create_availability_conflicts @@ -109,6 +111,7 @@ def self.create_room_conflicts end def self.create_person_schedule_conflicts + # change for back to backs query = <<-SQL.squish CREATE OR REPLACE VIEW person_schedule_conflicts AS select @@ -135,15 +138,7 @@ def self.create_person_schedule_conflicts ps2.session_assignment_role_type_id as conflict_session_assignment_role_type_id, ps2.session_assignment_role_type as conflict_session_assignment_role_type, ps2.session_assignment_name as conflict_session_assignment_name, - ps2.room_id as conflict_room_id, - case - when - ((ps2.start_time >= ps1.end_time) and (ps2.start_time <= (ps1.end_time + (40 || ' minute')::interval))) - or - ((ps1.start_time >= ps2.end_time) and (ps1.start_time <= (ps2.end_time + (40 || ' minute')::interval))) - then true - else FALSE - end as back_to_back + ps2.room_id as conflict_room_id from person_schedules ps1 join @@ -151,14 +146,78 @@ def self.create_person_schedule_conflicts and ps2.session_id != ps1.session_id and ps2.start_time >= ps1.start_time and ( - ps2.start_time <= ps1.end_time + (40 || ' minute')::interval + ps2.start_time < ps1.end_time or ( - ps2.end_time >= ps1.start_time - (40 || ' minute')::interval and ps2.end_time <= ps1.end_time + ps2.end_time > ps1.start_time and ps2.end_time <= ps1.end_time ) - ) + ); + SQL + ActiveRecord::Base.connection.execute(query) + end + + def self.create_person_back_to_back + query = <<-SQL.squish + CREATE OR REPLACE VIEW person_back_to_back AS + select + CONCAT(ps1.person_id, ':', ps1.session_id, ':', ps2.session_id) as id, + ps1.person_id, + ps1.name, + ps1.published_name, + ps1.con_state, + ps2.start_time AS conflict_start_time, + ps1.session_id, + ps1.title, + ps1.start_time, + ps1.end_time, + ps1.duration, + ps1.session_assignment_id, + ps1.session_assignment_role_type_id, + ps1.session_assignment_name as session_assignment_name, + ps1.session_assignment_role_type, + ps1.room_id, + ps2.session_id as conflict_session_id, + ps2.title as conflict_session_title, + ps2.end_time as conflict_end_time, + ps2.duration as conflict_duration, + ps2.session_assignment_role_type_id as conflict_session_assignment_role_type_id, + ps2.session_assignment_role_type as conflict_session_assignment_role_type, + ps2.session_assignment_name as conflict_session_assignment_name, + ps2.room_id as conflict_room_id + from + person_schedules ps1 + join + person_schedules ps2 on ps2.person_id = ps1.person_id + and ps2.session_id != ps1.session_id + where + (ps2.start_time >= ps1.end_time) and (ps2.start_time <= (ps1.end_time + (40 || ' minute')::interval)); + SQL + # or + # ((ps1.start_time >= ps2.end_time) and (ps1.start_time <= (ps2.end_time + (40 || ' minute')::interval))) + + ActiveRecord::Base.connection.execute(query) + end + + def self.create_person_and_exclusions + query = <<-SQL.squish + CREATE OR REPLACE VIEW person_and_exclusions AS + select + pe.exclusion_id, + pe.person_id, + s.id as session_id, + s.start_time, + (s.start_time + (s.duration || ' minute')::interval) as end_time, + s.title + from + person_exclusions pe + left join exclusions_sessions es on + es.exclusion_id = pe.exclusion_id + left join sessions s on + s.id = es.session_id + where + session_id is not null order by - ps1.person_id + pe.person_id, session_id SQL ActiveRecord::Base.connection.execute(query) end @@ -166,35 +225,53 @@ def self.create_person_schedule_conflicts def self.create_person_exclusion_conflicts query = <<-SQL.squish CREATE OR REPLACE VIEW person_exclusion_conflicts AS - SELECT - concat(person_schedules.person_id, ':', es.exclusion_id, ':', person_schedules.session_id) AS id, - person_schedules.person_id, - person_schedules.name, - person_schedules.published_name, - person_schedules.con_state, - es.exclusion_id, - es.session_id AS excluded_session_id, - s.title as excluded_session_title, - person_schedules.session_id, - person_schedules.title, - person_schedules.start_time, - person_schedules.end_time, - person_schedules.duration, - person_schedules.session_assignment_role_type_id, - person_schedules.session_assignment_id, - person_schedules.session_assignment_name, - person_schedules.session_assignment_role_type - FROM (((public.person_schedules - LEFT JOIN public.person_exclusions pe ON ((pe.person_id = person_schedules.person_id))) - JOIN public.exclusions_sessions es ON ((es.exclusion_id = pe.exclusion_id))) - LEFT JOIN public.sessions s ON ((s.id = es.session_id))) - WHERE ((person_schedules.session_id <> s.id) AND (person_schedules.start_time >= s.start_time) AND ((person_schedules.start_time <= (s.start_time + ((s.duration || ' minute'::text))::interval)) OR ((person_schedules.end_time >= s.start_time) AND (person_schedules.end_time <= (s.start_time + ((s.duration || ' minute'::text))::interval))))); + select + concat(person_schedules.person_id, ':', es.exclusion_id, ':', person_schedules.session_id) as id, + person_schedules.person_id, + person_schedules.name, + person_schedules.published_name, + person_schedules.con_state, + es.exclusion_id, + es.session_id as excluded_session_id, + s.title as excluded_session_title, + person_schedules.session_id, + person_schedules.title, + person_schedules.start_time, + person_schedules.end_time, + person_schedules.duration, + person_schedules.session_assignment_role_type_id, + person_schedules.session_assignment_id, + person_schedules.session_assignment_name, + person_schedules.session_assignment_role_type + from + person_schedules + left join person_exclusions pe on + pe.person_id = person_schedules.person_id + join exclusions_sessions es on + es.exclusion_id = pe.exclusion_id + left join sessions s on + s.id = es.session_id + where + person_schedules.session_id <> s.id + and ( + ( + person_schedules.start_time >= s.start_time + and + person_schedules.start_time < (s.start_time + (s.duration || ' minute'::text)::interval) + ) + or + ( + person_schedules.end_time > s.start_time + and + person_schedules.end_time <= (s.start_time + (s.duration || ' minute'::text)::interval) + ) + ); SQL ActiveRecord::Base.connection.execute(query) end def self.create_person_back_to_back_to_back - # check + # TODO: change new view query = <<-SQL.squish CREATE OR REPLACE VIEW person_back_to_back_to_back AS select @@ -233,11 +310,9 @@ def self.create_person_back_to_back_to_back psc2.conflict_session_assignment_name, psc2.conflict_room_id from - person_schedule_conflicts psc1 - inner join person_schedule_conflicts psc2 on + person_back_to_back psc1 + inner join person_back_to_back psc2 on psc2.session_id = psc1.conflict_session_id - and psc2.back_to_back = true - where psc1.back_to_back = true SQL ActiveRecord::Base.connection.execute(query) end @@ -382,7 +457,6 @@ def self.create_session_conflicts id as conflict_id, 'person_schedule_conflict' as conflict_type from person_schedule_conflicts - where person_schedule_conflicts.back_to_back = false UNION select session_id, @@ -401,8 +475,7 @@ def self.create_session_conflicts conflict_session_assignment_name, id as conflict_id, 'person_back_to_back' as conflict_type - from person_schedule_conflicts - where person_schedule_conflicts.back_to_back = true + from person_back_to_back SQL ActiveRecord::Base.connection.execute(query) end @@ -423,12 +496,18 @@ def self.drop_views ActiveRecord::Base.connection.execute <<-SQL DROP VIEW IF EXISTS person_schedule_conflicts; SQL + ActiveRecord::Base.connection.execute <<-SQL + DROP VIEW IF EXISTS person_back_to_back; + SQL ActiveRecord::Base.connection.execute <<-SQL DROP VIEW IF EXISTS room_conflicts; SQL ActiveRecord::Base.connection.execute <<-SQL DROP VIEW IF EXISTS room_allocations; SQL + ActiveRecord::Base.connection.execute <<-SQL + DROP VIEW IF EXISTS person_and_exclusions; + SQL ActiveRecord::Base.connection.execute <<-SQL DROP VIEW IF EXISTS person_schedules; SQL diff --git a/app/models/conflicts/person_back_to_back.rb b/app/models/conflicts/person_back_to_back.rb new file mode 100644 index 000000000..a1d97e5cf --- /dev/null +++ b/app/models/conflicts/person_back_to_back.rb @@ -0,0 +1,19 @@ +class Conflicts::PersonBackToBack < ApplicationRecord + self.table_name = :person_back_to_back + self.primary_key = :id + + belongs_to :session_assignment + belongs_to :person + belongs_to :session + belongs_to :session_assignment_role_type + belongs_to :room + + belongs_to :conflict_session_assignment, class_name: 'SessionAssignment' + belongs_to :conflict_session, class_name: 'Session' + belongs_to :conflict_session_assignment_role_type, class_name: 'SessionAssignmentRoleType' + belongs_to :conflict_room, class_name: 'Room' + + def readonly? + true + end +end diff --git a/app/models/ignored_conflict.rb b/app/models/ignored_conflict.rb new file mode 100644 index 000000000..6d081a0e1 --- /dev/null +++ b/app/models/ignored_conflict.rb @@ -0,0 +1,2 @@ +class IgnoredConflict < ApplicationRecord +end diff --git a/app/models/session.rb b/app/models/session.rb index 48625c530..d02bd50f4 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -18,9 +18,22 @@ class Session < ApplicationRecord before_save :keep_who_did_it, :keep_interest_trail, :schedule_consistency - has_many :session_conflicts, class_name: 'Conflicts::SessionConflict' + has_many :session_conflicts, + -> { + where("session_conflicts.conflict_id not in (select conflict_id from ignored_conflicts)") + .where("session_assignment_name is null or session_assignment_name in (?)", ['Moderator', 'Participant', 'Invisible']) + .where("conflict_session_assignment_name is null or conflict_session_assignment_name in (?)", ['Moderator', 'Participant', 'Invisible']) + }, + class_name: 'Conflicts::SessionConflict' + # Get where this session is on the other side of the conflict relationship - has_many :conflict_sessions, foreign_key: :conflict_session_id, class_name: 'Conflicts::SessionConflict' + has_many :conflict_sessions, + -> { + where("session_conflicts.conflict_id not in (select conflict_id from ignored_conflicts)") + .where("session_assignment_name is null or session_assignment_name in (?)", ['Moderator', 'Participant', 'Invisible']) + .where("conflict_session_assignment_name is null or conflict_session_assignment_name in (?)", ['Moderator', 'Participant', 'Invisible']) + }, + foreign_key: :conflict_session_id, class_name: 'Conflicts::SessionConflict' has_and_belongs_to_many :room_services @@ -115,6 +128,7 @@ def self.area_list def self.conflict_counts sessions = Session.arel_table conflicts = Conflicts::SessionConflict.arel_table + ignored_conflicts = ::IgnoredConflict.arel_table sessions.project( sessions[:id].as('session_id'), @@ -127,6 +141,10 @@ def self.conflict_counts .and( conflicts[:session_assignment_name].eq(nil).or(conflicts[:session_assignment_name].in(['Moderator', 'Participant', 'Invisible'])).and( conflicts[:conflict_session_assignment_name].eq(nil).or(conflicts[:conflict_session_assignment_name].in(['Moderator', 'Participant', 'Invisible'])) + ).and( + conflicts[:conflict_id].not_in( + ignored_conflicts.project(ignored_conflicts[:conflict_id]) + ) ) ) ) diff --git a/app/policies/conflicts/session_conflict_policy.rb b/app/policies/conflicts/session_conflict_policy.rb index 743849db6..e097d741c 100644 --- a/app/policies/conflicts/session_conflict_policy.rb +++ b/app/policies/conflicts/session_conflict_policy.rb @@ -1,6 +1,6 @@ class Conflicts::SessionConflictPolicy < BasePolicy - def index? - allowed?(action: :index) + def ignore? + allowed?(action: :ignore) end def conflicts_with? diff --git a/app/serializers/conflicts/session_conflict_serializer.rb b/app/serializers/conflicts/session_conflict_serializer.rb index 20d387d39..2195da9f8 100644 --- a/app/serializers/conflicts/session_conflict_serializer.rb +++ b/app/serializers/conflicts/session_conflict_serializer.rb @@ -1,7 +1,7 @@ class Conflicts::SessionConflictSerializer include JSONAPI::Serializer - attribute :person_id, :person_name, :person_published_name, + attribute :id, :person_id, :person_name, :person_published_name, :session_assignment_id, :session_id, :session_title, :session_start_time, :conflict_session_id, :conflict_session_title, diff --git a/app/serializers/session_serializer.rb b/app/serializers/session_serializer.rb index 991226397..7fe0f34d0 100644 --- a/app/serializers/session_serializer.rb +++ b/app/serializers/session_serializer.rb @@ -12,8 +12,8 @@ class SessionSerializer :updated_by, :interest_opened_by, :interest_opened_at, :room_id, :proofed, :format_id, :room_set_id, :status, :environment, - :tech_notes, - :minors_participation + :tech_notes, :room_notes, + :minors_participation, :age_restriction_id # tag_list attribute :tag_list do |session| diff --git a/app/services/reports_service.rb b/app/services/reports_service.rb index 6cef1b244..6c01f905a 100644 --- a/app/services/reports_service.rb +++ b/app/services/reports_service.rb @@ -18,16 +18,28 @@ def self.assigned_sessions_not_scheduled def self.scheduled_session_no_people active_roles = SessionAssignmentRoleType.where("role_type = 'participant' and (name != 'Invisible' and name != 'Reserve')") + # Get all sessions that are scheduled with people in role + session_with_people = PersonSchedule.where("session_assignment_role_type_id in (?)", active_roles.collect{|a| a.id}) + # Then get all scheduled sessions not in the above Session.select( ::Session.arel_table[Arel.star], 'areas_list.area_list' ) .joins(self.area_subquery) - .joins(:session_assignments) - .eager_load(:areas, :room) - .where("session_assignments.session_assignment_role_type_id not in (?)", active_roles.collect{|a| a.id}) .where("start_time is not null and room_id is not null") + .where("sessions.id not in (?)", session_with_people.collect{|a| a.session_id}) .order(:start_time) + + + # Session.select( + # ::Session.arel_table[Arel.star], + # 'areas_list.area_list' + # ) + # .joins(:session_assignments) + # .eager_load(:areas, :room) + # .where("session_assignments.session_assignment_role_type_id not in (?)", active_roles.collect{|a| a.id}) + # .where("start_time is not null and room_id is not null") + # .order(:start_time) end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 894f9d470..47416aa12 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,6 +14,7 @@ <%= javascript_pack_tag 'application' %> <%= stylesheet_pack_tag 'application' %> + <%= favicon_link_tag asset_path('p_for_plano.png') %> Planorama <%= csp_meta_tag %> diff --git a/config/routes.rb b/config/routes.rb index c923c490c..125acfe35 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -85,7 +85,7 @@ get 'agreement/latest', to: 'agreements#latest' resources :agreements, path: 'agreement' - get 'session_conflict', to: 'conflicts/session_conflicts#index' + get 'session_conflict/ignore/:conflict_type/:conflict_id', to: 'conflicts/session_conflicts#ignore' get 'session_conflict/conflicts_for/:session_id', to: 'conflicts/session_conflicts#conflicts_for' get 'session_conflict/conflicts_with/:session_id', to: 'conflicts/session_conflicts#conflicts_with' diff --git a/db/migrate/20220628121934_create_ignored_conflicts.rb b/db/migrate/20220628121934_create_ignored_conflicts.rb new file mode 100644 index 000000000..c7ddb112d --- /dev/null +++ b/db/migrate/20220628121934_create_ignored_conflicts.rb @@ -0,0 +1,12 @@ +class CreateIgnoredConflicts < ActiveRecord::Migration[6.1] + def change + create_table :ignored_conflicts, id: :uuid do |t| + t.string :conflict_id, limit: 2048 + t.string :conflict_type + + t.timestamps + end + + add_index :ignored_conflicts, [:conflict_id, :conflict_type], unique: true + end +end diff --git a/db/migrate/20220630032544_add_room_notes_to_session.rb b/db/migrate/20220630032544_add_room_notes_to_session.rb new file mode 100644 index 000000000..84357057f --- /dev/null +++ b/db/migrate/20220630032544_add_room_notes_to_session.rb @@ -0,0 +1,5 @@ +class AddRoomNotesToSession < ActiveRecord::Migration[6.1] + def change + add_column :sessions, :room_notes, :text + end +end diff --git a/db/structure.sql b/db/structure.sql index 0380bc9d1..7b5a8cdc1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -660,7 +660,8 @@ CREATE TABLE public.sessions ( tech_notes text, age_restriction_id uuid, minors_participation jsonb, - room_set_id uuid + room_set_id uuid, + room_notes text ); @@ -872,6 +873,19 @@ CREATE TABLE public.formats ( ); +-- +-- Name: ignored_conflicts; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.ignored_conflicts ( + id uuid DEFAULT public.gen_random_uuid() NOT NULL, + conflict_id character varying(2048), + conflict_type character varying, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + -- -- Name: label_dimensions; Type: TABLE; Schema: public; Owner: - -- @@ -1011,6 +1025,38 @@ CREATE TABLE public.person_agreements ( ); +-- +-- Name: person_exclusions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.person_exclusions ( + id uuid DEFAULT public.gen_random_uuid() NOT NULL, + person_id uuid, + exclusion_id uuid, + lock_version integer, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: person_and_exclusions; Type: VIEW; Schema: public; Owner: - +-- + +CREATE VIEW public.person_and_exclusions AS + SELECT pe.exclusion_id, + pe.person_id, + s.id AS session_id, + s.start_time, + (s.start_time + ((s.duration || ' minute'::text))::interval) AS end_time, + s.title + FROM ((public.person_exclusions pe + LEFT JOIN public.exclusions_sessions es ON ((es.exclusion_id = pe.exclusion_id))) + LEFT JOIN public.sessions s ON ((s.id = es.session_id))) + WHERE (es.session_id IS NOT NULL) + ORDER BY pe.person_id, s.id; + + -- -- Name: person_schedules; Type: VIEW; Schema: public; Owner: - -- @@ -1041,16 +1087,16 @@ CREATE VIEW public.person_schedules AS -- --- Name: person_schedule_conflicts; Type: VIEW; Schema: public; Owner: - +-- Name: person_back_to_back; Type: VIEW; Schema: public; Owner: - -- -CREATE VIEW public.person_schedule_conflicts AS +CREATE VIEW public.person_back_to_back AS SELECT concat(ps1.person_id, ':', ps1.session_id, ':', ps2.session_id) AS id, ps1.person_id, ps1.name, ps1.published_name, ps1.con_state, - GREATEST(ps1.start_time, ps2.start_time) AS conflict_start_time, + ps2.start_time AS conflict_start_time, ps1.session_id, ps1.title, ps1.start_time, @@ -1068,14 +1114,10 @@ CREATE VIEW public.person_schedule_conflicts AS ps2.session_assignment_role_type_id AS conflict_session_assignment_role_type_id, ps2.session_assignment_role_type AS conflict_session_assignment_role_type, ps2.session_assignment_name AS conflict_session_assignment_name, - ps2.room_id AS conflict_room_id, - CASE - WHEN (((ps2.start_time >= ps1.end_time) AND (ps2.start_time <= (ps1.end_time + ((40 || ' minute'::text))::interval))) OR ((ps1.start_time >= ps2.end_time) AND (ps1.start_time <= (ps2.end_time + ((40 || ' minute'::text))::interval)))) THEN true - ELSE false - END AS back_to_back + ps2.room_id AS conflict_room_id FROM (public.person_schedules ps1 - JOIN public.person_schedules ps2 ON (((ps2.person_id = ps1.person_id) AND (ps2.session_id <> ps1.session_id) AND (ps2.start_time >= ps1.start_time) AND ((ps2.start_time <= (ps1.end_time + ((40 || ' minute'::text))::interval)) OR ((ps2.end_time >= (ps1.start_time - ((40 || ' minute'::text))::interval)) AND (ps2.end_time <= ps1.end_time)))))) - ORDER BY ps1.person_id; + JOIN public.person_schedules ps2 ON (((ps2.person_id = ps1.person_id) AND (ps2.session_id <> ps1.session_id)))) + WHERE ((ps2.start_time >= ps1.end_time) AND (ps2.start_time <= (ps1.end_time + ((40 || ' minute'::text))::interval))); -- @@ -1117,9 +1159,8 @@ CREATE VIEW public.person_back_to_back_to_back AS psc2.conflict_session_assignment_role_type, psc2.conflict_session_assignment_name, psc2.conflict_room_id - FROM (public.person_schedule_conflicts psc1 - JOIN public.person_schedule_conflicts psc2 ON (((psc2.session_id = psc1.conflict_session_id) AND (psc2.back_to_back = true)))) - WHERE (psc1.back_to_back = true); + FROM (public.person_back_to_back psc1 + JOIN public.person_back_to_back psc2 ON ((psc2.session_id = psc1.conflict_session_id))); -- @@ -1137,20 +1178,6 @@ CREATE TABLE public.person_constraints ( ); --- --- Name: person_exclusions; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.person_exclusions ( - id uuid DEFAULT public.gen_random_uuid() NOT NULL, - person_id uuid, - exclusion_id uuid, - lock_version integer, - created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL -); - - -- -- Name: person_exclusion_conflicts; Type: VIEW; Schema: public; Owner: - -- @@ -1177,7 +1204,7 @@ CREATE VIEW public.person_exclusion_conflicts AS LEFT JOIN public.person_exclusions pe ON ((pe.person_id = person_schedules.person_id))) JOIN public.exclusions_sessions es ON ((es.exclusion_id = pe.exclusion_id))) LEFT JOIN public.sessions s ON ((s.id = es.session_id))) - WHERE ((person_schedules.session_id <> s.id) AND (person_schedules.start_time >= s.start_time) AND ((person_schedules.start_time <= (s.start_time + ((s.duration || ' minute'::text))::interval)) OR ((person_schedules.end_time >= s.start_time) AND (person_schedules.end_time <= (s.start_time + ((s.duration || ' minute'::text))::interval))))); + WHERE ((person_schedules.session_id <> s.id) AND (person_schedules.start_time >= s.start_time) AND ((person_schedules.start_time < (s.start_time + ((s.duration || ' minute'::text))::interval)) OR ((person_schedules.end_time > s.start_time) AND (person_schedules.end_time <= (s.start_time + ((s.duration || ' minute'::text))::interval))))); -- @@ -1194,6 +1221,39 @@ CREATE TABLE public.person_mailing_assignments ( ); +-- +-- Name: person_schedule_conflicts; Type: VIEW; Schema: public; Owner: - +-- + +CREATE VIEW public.person_schedule_conflicts AS + SELECT concat(ps1.person_id, ':', ps1.session_id, ':', ps2.session_id) AS id, + ps1.person_id, + ps1.name, + ps1.published_name, + ps1.con_state, + GREATEST(ps1.start_time, ps2.start_time) AS conflict_start_time, + ps1.session_id, + ps1.title, + ps1.start_time, + ps1.end_time, + ps1.duration, + ps1.session_assignment_id, + ps1.session_assignment_role_type_id, + ps1.session_assignment_name, + ps1.session_assignment_role_type, + ps1.room_id, + ps2.session_id AS conflict_session_id, + ps2.title AS conflict_session_title, + ps2.end_time AS conflict_end_time, + ps2.duration AS conflict_duration, + ps2.session_assignment_role_type_id AS conflict_session_assignment_role_type_id, + ps2.session_assignment_role_type AS conflict_session_assignment_role_type, + ps2.session_assignment_name AS conflict_session_assignment_name, + ps2.room_id AS conflict_room_id + FROM (public.person_schedules ps1 + JOIN public.person_schedules ps2 ON (((ps2.person_id = ps1.person_id) AND (ps2.session_id <> ps1.session_id) AND (ps2.start_time >= ps1.start_time) AND ((ps2.start_time <= ps1.end_time) OR ((ps2.end_time >= ps1.start_time) AND (ps2.end_time <= ps1.end_time)))))); + + -- -- Name: publication_dates; Type: TABLE; Schema: public; Owner: - -- @@ -1468,26 +1528,24 @@ UNION person_schedule_conflicts.id AS conflict_id, 'person_schedule_conflict'::text AS conflict_type FROM public.person_schedule_conflicts - WHERE (person_schedule_conflicts.back_to_back = false) UNION - SELECT person_schedule_conflicts.session_id, - person_schedule_conflicts.title AS session_title, - person_schedule_conflicts.start_time AS session_start_time, - person_schedule_conflicts.room_id, - person_schedule_conflicts.person_id, - person_schedule_conflicts.name AS person_name, - person_schedule_conflicts.published_name AS person_published_name, - person_schedule_conflicts.session_assignment_id, - person_schedule_conflicts.session_assignment_role_type_id, - person_schedule_conflicts.session_assignment_name, - person_schedule_conflicts.conflict_session_id, - person_schedule_conflicts.conflict_session_title, - person_schedule_conflicts.conflict_session_assignment_role_type_id, - person_schedule_conflicts.conflict_session_assignment_name, - person_schedule_conflicts.id AS conflict_id, + SELECT person_back_to_back.session_id, + person_back_to_back.title AS session_title, + person_back_to_back.start_time AS session_start_time, + person_back_to_back.room_id, + person_back_to_back.person_id, + person_back_to_back.name AS person_name, + person_back_to_back.published_name AS person_published_name, + person_back_to_back.session_assignment_id, + person_back_to_back.session_assignment_role_type_id, + person_back_to_back.session_assignment_name, + person_back_to_back.conflict_session_id, + person_back_to_back.conflict_session_title, + person_back_to_back.conflict_session_assignment_role_type_id, + person_back_to_back.conflict_session_assignment_name, + person_back_to_back.id AS conflict_id, 'person_back_to_back'::text AS conflict_type - FROM public.person_schedule_conflicts - WHERE (person_schedule_conflicts.back_to_back = true); + FROM public.person_back_to_back; -- @@ -1989,6 +2047,14 @@ ALTER TABLE ONLY public.formats ADD CONSTRAINT formats_pkey PRIMARY KEY (id); +-- +-- Name: ignored_conflicts ignored_conflicts_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ignored_conflicts + ADD CONSTRAINT ignored_conflicts_pkey PRIMARY KEY (id); + + -- -- Name: label_dimensions label_dimensions_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2477,6 +2543,13 @@ CREATE UNIQUE INDEX index_exclusions_sessions_on_exclusion_id_and_session_id ON CREATE INDEX index_exclusions_sessions_on_session_id ON public.exclusions_sessions USING btree (session_id); +-- +-- Name: index_ignored_conflicts_on_conflict_id_and_conflict_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_ignored_conflicts_on_conflict_id_and_conflict_type ON public.ignored_conflicts USING btree (conflict_id, conflict_type); + + -- -- Name: index_magic_links_on_person_id; Type: INDEX; Schema: public; Owner: - -- @@ -3069,6 +3142,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220623145514'), ('20220623172955'), ('20220624121252'), -('20220629132145'); +('20220628121934'), +('20220629132145'), +('20220630032544'); diff --git a/docs/index.md b/docs/index.md index 69d87acd8..0be43bbc7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,8 +7,8 @@ This software is open source! If you'd like to contribute, please email planoram [Planorama Data Privacy & Protection Policy](/planorama/privacy) -Production version: 1.4.1 +Production version: 1.4.2 -Staging version: 1.4.1 +Staging version: 1.4.2 diff --git a/lib/tasks/rbac.rake b/lib/tasks/rbac.rake index 2c43479b0..b74c5d162 100644 --- a/lib/tasks/rbac.rake +++ b/lib/tasks/rbac.rake @@ -497,7 +497,8 @@ namespace :rbac do }, "session_conflict": { "conflicts_with": true, - "conflicts_for": true + "conflicts_for": true, + "ignore": true } }) end @@ -752,7 +753,8 @@ namespace :rbac do }, "session_conflict": { "conflicts_with": true, - "conflicts_for": true + "conflicts_for": true, + "ignore": true } }) end diff --git a/test/factories/ignored_conflicts.rb b/test/factories/ignored_conflicts.rb new file mode 100644 index 000000000..4f79f435e --- /dev/null +++ b/test/factories/ignored_conflicts.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :ignored_conflict do + + end +end diff --git a/test/models/ignored_conflict_test.rb b/test/models/ignored_conflict_test.rb new file mode 100644 index 000000000..0bc33d10c --- /dev/null +++ b/test/models/ignored_conflict_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class IgnoredConflictTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end