Course update permission check and warnings
This commit is contained in:
parent
795473a580
commit
0d793fce8f
|
@ -73,3 +73,47 @@ export function format_date(d,short){
|
|||
year: 'numeric', month: monthformat, day: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides standardized information about the period between two dates
|
||||
* As
|
||||
* @param {Date|string} first First day of the span
|
||||
* @param {Date|string} last Last day of the span
|
||||
* @returns {Object} Object containing formatted start and end dates and span information
|
||||
*/
|
||||
export function datespaninfo(first,last){
|
||||
if(!(first instanceof Date)){ first = new Date(first);}
|
||||
if(!(last instanceof Date)){ last = new Date(last);}
|
||||
|
||||
// Make sure the end date is at the very end of the day and the start date at the very beginning
|
||||
first.setHours(0);
|
||||
first.setMinutes(0);
|
||||
first.setSeconds(0);
|
||||
first.setMilliseconds(0);
|
||||
last.setHours(23);
|
||||
last.setMinutes(59);
|
||||
last.setSeconds(59);
|
||||
last.setMilliseconds(999);
|
||||
|
||||
const dayspan = Math.round(((last - first)+1)/(24*60*60*1000)); // Add one millisecond to offset the 999 ms
|
||||
const years = Math.floor(dayspan/365); // Yes, we ignore leap years/leap days
|
||||
const ydaysleft = dayspan % 365;
|
||||
|
||||
const weeks = Math.floor(ydaysleft/7);
|
||||
const wdaysleft = ydaysleft % 7;
|
||||
|
||||
return {
|
||||
first: first,
|
||||
last: last,
|
||||
totaldays: dayspan,
|
||||
years: years,
|
||||
weeks: weeks,
|
||||
days: wdaysleft,
|
||||
formatted: {
|
||||
first: format_date(first),
|
||||
last: format_date(last),
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {SimpleLine} from "./simpleline";
|
|||
import {call} from 'core/ajax';
|
||||
import notification from 'core/notification';
|
||||
import {get_strings} from 'core/str';
|
||||
import {load_stringkeys, load_strings, format_date} from './string-helper';
|
||||
import {load_stringkeys, load_strings, format_date, datespaninfo} from './string-helper';
|
||||
import {objCopy,transportItem} from './studyplan-processor';
|
||||
import Debugger from './debugger';
|
||||
import {download,upload} from './downloader';
|
||||
|
@ -148,6 +148,26 @@ export default {
|
|||
startdate: 'studyplan_startdate',
|
||||
enddate: 'studyplan_enddate',
|
||||
},
|
||||
course_timing: {
|
||||
title: 'course_timing_title',
|
||||
desc: 'course_timing_desc',
|
||||
question: 'course_timing_question',
|
||||
warning: 'course_timing_warning',
|
||||
course: 'course$core',
|
||||
period: 'period',
|
||||
yes: 'yes$core',
|
||||
no: 'no$core',
|
||||
duration: 'duration',
|
||||
years: 'years$core',
|
||||
year: 'year$core',
|
||||
weeks: 'weeks$core',
|
||||
week: 'week$core',
|
||||
days: 'days$core',
|
||||
day: 'day$core',
|
||||
rememberchoice: 'course_timing_rememberchoice',
|
||||
hidewarning: 'course_timing_hidewarning,'
|
||||
|
||||
},
|
||||
studyplan_associate: {
|
||||
associations: 'associations',
|
||||
associated_cohorts: 'associated_cohorts',
|
||||
|
@ -1526,6 +1546,7 @@ export default {
|
|||
:line="line"
|
||||
:plan="value"
|
||||
:page="page"
|
||||
:period="page.perioddesc[index-1]"
|
||||
:layer="layeridx-1"
|
||||
:class="'t-studyline ' + ((lineindex%2==0)?' odd ':' even ')
|
||||
+ ((lineindex==0 && layeridx==1)?' first ':' ')
|
||||
|
@ -1787,7 +1808,14 @@ export default {
|
|||
type: Object, // Studyplan data
|
||||
default(){ return null;},
|
||||
},
|
||||
|
||||
page: {
|
||||
type: Object, // Studyplan data
|
||||
default(){ return null;},
|
||||
},
|
||||
period: {
|
||||
type: Object, // Studyplan data
|
||||
default(){ return null;},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const self=this;
|
||||
|
@ -1807,6 +1835,9 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
slotkey(){
|
||||
return `${this.type}'-'${this.line.id}-${this.slotindex}-${this.layer}`;
|
||||
},
|
||||
item(){
|
||||
for(const ix in this.value){
|
||||
const itm = this.value[ix];
|
||||
|
@ -1832,10 +1863,18 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
text: strings.course_timing,
|
||||
resizeListener: null,
|
||||
hover: {
|
||||
component:null,
|
||||
type: null,
|
||||
},
|
||||
datechanger: {
|
||||
coursespan: null,
|
||||
periodspan: null,
|
||||
default: false,
|
||||
defaultchoice: false,
|
||||
hidewarn: false,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -1861,15 +1900,18 @@ export default {
|
|||
const self = this;
|
||||
if(self.dragacceptitem().includes(event.type)) {
|
||||
let item = event.data;
|
||||
|
||||
// Perform layer update - set this slot and layer here
|
||||
self.relocateStudyItem(item).done(()=>{
|
||||
item.layer = this.layer;
|
||||
item.slot = this.slotindex;
|
||||
self.value.push(item);
|
||||
self.$emit("input",self.value);
|
||||
self.validate_course_period();
|
||||
});
|
||||
}
|
||||
else if(self.dragacceptcomponent().includes(event.type) ){
|
||||
|
||||
if(event.type == "course"){
|
||||
call([{
|
||||
methodname: 'local_treestudyplan_add_studyitem',
|
||||
|
@ -1892,6 +1934,7 @@ export default {
|
|||
self.relocateStudyItem(item).done(()=>{
|
||||
self.value.push(item);
|
||||
self.$emit("input",self.value);
|
||||
self.validate_course_period();
|
||||
});
|
||||
}).fail(notification.exception);
|
||||
}
|
||||
|
@ -1944,7 +1987,58 @@ export default {
|
|||
this.hover.component = null;
|
||||
this.hover.type = null;
|
||||
},
|
||||
validate_course_period() {
|
||||
const self = this;
|
||||
debug.info("Validating course and period",self.item,self.period);
|
||||
if(self.item && self.item.type == 'course'){
|
||||
self.datechanger.coursespan = datespaninfo(self.item.course.startdate,self.item.course.enddate);
|
||||
self.datechanger.periodspan = datespaninfo(self.period.startdate,self.period.enddate);
|
||||
if( self.datechanger.coursespan.first != self.datechanger.periodspan.first
|
||||
|| self.datechanger.coursespan.last != self.datechanger.periodspan.last ){
|
||||
debug.info("Course timing does not match period timing");
|
||||
|
||||
if(self.item.course.canupdatecourse){
|
||||
if(!self.datechanger.default){
|
||||
// Periods do not match, pop up the date change request
|
||||
this.$bvModal.show("t-course-date-matching-"+this.slotkey);
|
||||
} else if (self.datechanger.defaultvalue){
|
||||
// go for it without asking
|
||||
self.change_course_period();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user is not able to change course timing - show a warning
|
||||
if(!self.datechanger.hidewarn){
|
||||
this.$bvModal.show("t-course-date-warning-"+this.slotkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug.info("Course timing matches period",self.datechanger);
|
||||
}
|
||||
}
|
||||
},
|
||||
change_course_period() {
|
||||
const self=this;
|
||||
return call([{
|
||||
methodname: 'local_treestudyplan_course_period_timing',
|
||||
args: { page_id: self.page_id,
|
||||
period: self.period.period,
|
||||
course_id: this.item.course.id,
|
||||
}
|
||||
}])[0].fail(notification.exception);
|
||||
},
|
||||
format_duration(dsi){
|
||||
let s = "";
|
||||
if(dsi.years == 1){ s += `1 ${this.text.year}, `;}
|
||||
else if(dsi.years > 1){ s += `${dsi.years} ${this.text.years}, `;}
|
||||
if(dsi.weeks == 1){ s += `1 ${this.text.week}, `;}
|
||||
else if(dsi.weeks > 1){ s += `${dsi.weeks} ${this.text.weeks}, `;}
|
||||
if(dsi.days == 1){ s += `1 ${this.text.day}, `;}
|
||||
else if(dsi.days > 1){ s += `${dsi.days} ${this.text.days}, `;}
|
||||
|
||||
return s.toLocaleLowerCase();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="'t-studyline-slot '+type + ' t-studyline-slot-'+slotindex + ' ' + ((slotindex==0)?' t-studyline-firstcolumn ':' ')"
|
||||
|
@ -1987,8 +2081,77 @@ export default {
|
|||
class="t-slot-item feedback"
|
||||
:key="hover.type">--{{ hover.type }}--</div
|
||||
></template
|
||||
></drop
|
||||
></div>
|
||||
></drop>
|
||||
<b-modal
|
||||
:id="'t-course-date-matching-'+this.slotkey"
|
||||
size="lg"
|
||||
:title="text.title"
|
||||
@ok="change_course_period"
|
||||
:ok-title="text.yes"
|
||||
cancel-disabled
|
||||
>
|
||||
<b-container v-if="datechanger.coursespan && datechanger.periodspan && item && item.course">
|
||||
<b-row><b-col cols="12">{{ text.desc }}</b-col></b-row>
|
||||
<b-row><b-col cols="12"><div class="generalbox alert alert-warning">{{ text.question }}</div></b-col></b-row>
|
||||
<b-row>
|
||||
<b-col cols="6">
|
||||
<h3> {{ text.course }} </h3>
|
||||
<p><b>{{ item.course.fullname }}</b></p>
|
||||
<p><b>{{ item.course.shortname }}</b></p>
|
||||
<p>{{ datechanger.coursespan.formatted.first}} - {{ datechanger.coursespan.formatted.last}}</p>
|
||||
<p><b>{{ text.duration }}</b><br>
|
||||
{{ format_duration(datechanger.coursespan)}}</p>
|
||||
</b-col>
|
||||
<b-col cols="6">
|
||||
<h3> {{ text.period }} </h3>
|
||||
<p><b>{{ period.fullname }}</b></p>
|
||||
<p><b>{{ period.shortname }}</b></p>
|
||||
<p>{{ datechanger.periodspan.formatted.first}} - {{ datechanger.periodspan.formatted.last}}</p>
|
||||
<p><b>{{ text.duration }}</b><br>
|
||||
{{ format_duration(datechanger.periodspan)}}</p>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row><b-col cols="12">
|
||||
<b-form-checkbox type="checkbox" v-model="datechanger.default">{{ text.rememberchoice }}</b-form-checkbox>
|
||||
</b-col></b-row>
|
||||
</b-container>
|
||||
</b-modal>
|
||||
<b-modal
|
||||
:id="'t-course-date-warning-'+this.slotkey"
|
||||
size="lg"
|
||||
ok-variant="danger"
|
||||
:title="text.title"
|
||||
:ok-title="text.yes"
|
||||
:cancel-title="text.no"
|
||||
cancel-variant="primary"
|
||||
>
|
||||
<b-container v-if="datechanger.coursespan && datechanger.periodspan && item && item.course">
|
||||
<b-row><b-col cols="12">{{ text.desc }}</b-col></b-row>
|
||||
<b-row><b-col cols="12"><div class="generalbox alert alert-warning">{{ text.warning }}</div></b-col></b-row>
|
||||
<b-row>
|
||||
<b-col cols="6">
|
||||
<h3> {{ text.course }} </h3>
|
||||
<p><b>{{ item.course.fullname }}</b></p>
|
||||
<p><b>{{ item.course.shortname }}</b></p>
|
||||
<p>{{ datechanger.coursespan.formatted.first}} - {{ datechanger.coursespan.formatted.last}}</p>
|
||||
<p><b>{{ text.duration }}</b><br>
|
||||
{{ format_duration(datechanger.coursespan)}}</p>
|
||||
</b-col>
|
||||
<b-col cols=>"6">
|
||||
<h3> {{ text.period }} </h3>
|
||||
<p><b>{{ period.fullname }}</b></p>
|
||||
<p><b>{{ period.shortname }}</b></p>
|
||||
<p>{{ datechanger.periodspan.formatted.first}} - {{ datechanger.periodspan.formatted.last}}</p>
|
||||
<p><b>{{ text.duration }}</b><br>
|
||||
{{ format_duration(datechanger.periodspan)}}</p>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row><b-col cols="12">
|
||||
<b-form-checkbox type="checkbox" v-model="datechanger.hidewarn">{{ text.hidewarning }}</b-form-checkbox>
|
||||
</b-col></b-row>
|
||||
</b-container>
|
||||
</b-modal>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ export default {
|
|||
>{{ value.fullname }}<br>
|
||||
<span class="s-studyline-header-period-datespan">
|
||||
<span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>
|
||||
<span>
|
||||
</span>
|
||||
</b-tooltip>
|
||||
<slot></slot
|
||||
><p class="s-studyline-header-period-datespan small">
|
||||
|
|
|
@ -178,6 +178,7 @@ class courseinfo {
|
|||
"startdate" => new \external_value(PARAM_TEXT, 'Course start date'),
|
||||
"enddate" => new \external_value(PARAM_TEXT, 'Course end date'),
|
||||
"amteacher" => new \external_value(PARAM_BOOL, 'Requesting user is teacher in this course'),
|
||||
"canupdatecourse" => new \external_value(PARAM_BOOL, "If the current user can update this course"),
|
||||
"canselectgradables" => new \external_value(PARAM_BOOL, 'Requesting user can change selected gradables'),
|
||||
"tag" => new \external_value(PARAM_TEXT, 'Tag'),
|
||||
], 'referenced course information',$value);
|
||||
|
@ -200,6 +201,7 @@ class courseinfo {
|
|||
'startdate' => date("Y-m-d",$this->course->startdate,),
|
||||
'enddate' => date("Y-m-d",$this->course->enddate),
|
||||
'amteacher' => $this->amTeacher(),
|
||||
'canupdatecourse' => \has_capability("moodle/course:update",$this->coursecontext),
|
||||
'canselectgradables' => $this->iCanSelectGradables(),
|
||||
'tag' => "Editormodel",
|
||||
'grades' => [],
|
||||
|
|
|
@ -1179,6 +1179,44 @@ class studyplanservice extends \external_api
|
|||
|
||||
}
|
||||
|
||||
/************************
|
||||
* *
|
||||
* edit_period *
|
||||
* *
|
||||
************************/
|
||||
|
||||
public static function course_period_timing_parameters()
|
||||
{
|
||||
return new \external_function_parameters( [
|
||||
"page_id" => new \external_value(PARAM_INT, 'Studyplan page id'),
|
||||
"period" => new \external_value(PARAM_INT, 'Period number within page'),
|
||||
"course_id"=> new \external_value(PARAM_INT, 'Id of course to adjust dates for'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function course_period_timing_returns()
|
||||
{
|
||||
return success::structure();
|
||||
}
|
||||
public static function course_period_timing($page_id, $period, $course_id){
|
||||
$page = studyplanpage::findById($page_id);
|
||||
$course = \get_course($course_id);
|
||||
$coursecontext = \context_course::instance($course_id);
|
||||
// Check for studyplan edit permissions
|
||||
webservicehelper::require_capabilities(self::CAP_EDIT,$page->studyplan()->context());
|
||||
|
||||
if(webservicehelper::has_capabilities("moodle/course:update",$coursecontext)){
|
||||
//TODO: Actually perform the timing changes, while also updating the module times
|
||||
// Like what happens on a course "reset"
|
||||
|
||||
|
||||
return success::success()->model();
|
||||
} else {
|
||||
// probably should return a nice message
|
||||
return success::fail("You do not have date change permissions on this course")->model();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -291,3 +291,11 @@ $string["badgeissuedstats"] = "Issuing progress";
|
|||
|
||||
$string["period_default_fullname"] = 'Period {$a}';
|
||||
$string["period_default_shortname"] = 'P{$a}';
|
||||
$string["course_timing_title"] = 'Course timing does not match period timing';
|
||||
$string["course_timing_desc"] = 'The start and end date of the course you are dropping into this period does not match the start and end date of the period.';
|
||||
$string["course_timing_question"] = 'Do you want to update the course\'s start and end time to match that of the period?';
|
||||
$string["course_timing_warning"] = 'You do not have permission to automatically update this course start and end date. Automatic timing update not available';
|
||||
$string["period"] = 'Period';
|
||||
$string["duration"] = 'Duration';
|
||||
$string["course_timing_rememberchoice"] = 'Remember my choice for future date mismatches';
|
||||
$string["course_timing_hidewarning"] = 'Hide this warning next time';
|
|
@ -294,3 +294,11 @@ $string["badgeissuedstats"] = "Voortgang van uitgifte";
|
|||
|
||||
$string["period_default_fullname"] = 'Periode {$a}';
|
||||
$string["period_default_shortname"] = 'P{$a}';
|
||||
$string["course_timing_title"] = 'Cursustiming en periodetiming komen niet overeen';
|
||||
$string["course_timing_desc"] = 'De start- en einddatum van de cursus, komen niet overen met die van de periode waarin je hem hebt gedropt.';
|
||||
$string["course_timing_question"] = 'Wil je de start- en eindtijd van de cursus aanpassen naar doe van de periode?';
|
||||
$string["course_timing_warning"] = 'Je hebt geen rechten om de start- en eindtijd van deze cursus aan te passen. Aanpassen van cursus naar periodetiming is niet beschikbaar.';
|
||||
$string["period"] = 'Periode';
|
||||
$string["duration"] = 'Duur';
|
||||
$string["course_timing_rememberchoice'"] = 'Onthoud mijn keuze voor toekomstige mismatches tussen cursus en periode';
|
||||
$string["course_timing_hidewarning"] = 'Hide this warning next time';
|
Loading…
Reference in New Issue
Block a user