mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 06:07:21 +01:00
Enabled repeatables (#8572)
* Enabled repeatables * Added every x to weekly * Updated new recur logic to work with tests * Added repeatable tests back * Added custom day start support * Moved back to zone function * Added zone back * Added nextDue field * Abstracted set next due logic, set offset, and mapped to ISO * Removed extra codes * Removed clone deep * Added summary local * Fixed every x weekly * Prevented edit of repeats on * Added next due date * Fixed display of next due dates * Fixed broken tests * added next due date as today for weekly * Fixed integration tests * Updated common test * Use user's format * Allow user to deselect all days during week * Removed let from front end
This commit is contained in:
committed by
Sabe Jones
parent
ba66a1c098
commit
cc532fa993
@@ -215,6 +215,13 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
expect(task.isDue).to.equal(true);
|
expect(task.isDue).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('computes nextDue', async () => {
|
||||||
|
await user.post(`/tasks/${daily._id}/score/up`);
|
||||||
|
let task = await user.get(`/tasks/${daily._id}`);
|
||||||
|
|
||||||
|
expect(task.nextDue.length).to.eql(6);
|
||||||
|
});
|
||||||
|
|
||||||
it('scores up daily even if it is already completed'); // Yes?
|
it('scores up daily even if it is already completed'); // Yes?
|
||||||
|
|
||||||
it('scores down daily even if it is already uncompleted'); // Yes?
|
it('scores down daily even if it is already uncompleted'); // Yes?
|
||||||
|
|||||||
@@ -510,6 +510,7 @@ describe('POST /tasks/user', () => {
|
|||||||
expect(task.weeksOfMonth).to.eql([3]);
|
expect(task.weeksOfMonth).to.eql([3]);
|
||||||
expect(new Date(task.startDate)).to.eql(now);
|
expect(new Date(task.startDate)).to.eql(now);
|
||||||
expect(task.isDue).to.be.true;
|
expect(task.isDue).to.be.true;
|
||||||
|
expect(task.nextDue.length).to.eql(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates multiple dailys', async () => {
|
it('creates multiple dailys', async () => {
|
||||||
|
|||||||
@@ -404,6 +404,7 @@ describe('PUT /tasks/:id', () => {
|
|||||||
expect(savedDaily.frequency).to.eql('daily');
|
expect(savedDaily.frequency).to.eql('daily');
|
||||||
expect(savedDaily.everyX).to.eql(5);
|
expect(savedDaily.everyX).to.eql(5);
|
||||||
expect(savedDaily.isDue).to.be.false;
|
expect(savedDaily.isDue).to.be.false;
|
||||||
|
expect(savedDaily.nextDue.length).to.eql(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can update checklists (replace it)', async () => {
|
it('can update checklists (replace it)', async () => {
|
||||||
|
|||||||
@@ -366,6 +366,14 @@ describe('cron', () => {
|
|||||||
expect(tasksByType.dailys[0].isDue).to.be.false;
|
expect(tasksByType.dailys[0].isDue).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('computes nextDue', () => {
|
||||||
|
tasksByType.dailys[0].frequency = 'daily';
|
||||||
|
tasksByType.dailys[0].everyX = 5;
|
||||||
|
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
expect(tasksByType.dailys[0].nextDue.length).to.eql(6);
|
||||||
|
});
|
||||||
|
|
||||||
it('should add history', () => {
|
it('should add history', () => {
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
expect(tasksByType.dailys[0].history).to.be.lengthOf(1);
|
expect(tasksByType.dailys[0].history).to.be.lengthOf(1);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -263,6 +263,7 @@ angular.module('habitrpg')
|
|||||||
modalScope.task._tags = !user.preferences.tagsCollapsed;
|
modalScope.task._tags = !user.preferences.tagsCollapsed;
|
||||||
modalScope.task._advanced = !user.preferences.advancedCollapsed;
|
modalScope.task._advanced = !user.preferences.advancedCollapsed;
|
||||||
modalScope.task._edit = angular.copy(task);
|
modalScope.task._edit = angular.copy(task);
|
||||||
|
modalScope.user = user;
|
||||||
if($rootScope.charts[task._id]) $rootScope.charts[task.id] = false;
|
if($rootScope.charts[task._id]) $rootScope.charts[task.id] = false;
|
||||||
|
|
||||||
modalScope.taskStatus = taskStatus;
|
modalScope.taskStatus = taskStatus;
|
||||||
@@ -294,6 +295,7 @@ angular.module('habitrpg')
|
|||||||
$scope.$watch('task._edit', function (newValue, oldValue) {
|
$scope.$watch('task._edit', function (newValue, oldValue) {
|
||||||
if ($scope.task.type !== 'daily' || !task._edit) return;
|
if ($scope.task.type !== 'daily' || !task._edit) return;
|
||||||
$scope.summary = generateSummary($scope.task);
|
$scope.summary = generateSummary($scope.task);
|
||||||
|
$scope.nextDue = generateNextDue($scope.task._edit, $scope.user);
|
||||||
|
|
||||||
$scope.repeatSuffix = generateRepeatSuffix($scope.task);
|
$scope.repeatSuffix = generateRepeatSuffix($scope.task);
|
||||||
if ($scope.task._edit.repeatsOn == 'dayOfMonth') {
|
if ($scope.task._edit.repeatsOn == 'dayOfMonth') {
|
||||||
@@ -352,7 +354,11 @@ angular.module('habitrpg')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var summary = 'Repeats ' + task._edit.frequency + ' every ' + task._edit.everyX + ' ' + frequencyPlural;
|
var summary = window.env.t('summaryStart', {
|
||||||
|
frequency: task._edit.frequency,
|
||||||
|
everyX: task._edit.everyX,
|
||||||
|
frequencyPlural: frequencyPlural,
|
||||||
|
});
|
||||||
|
|
||||||
if (task._edit.frequency === 'weekly') summary += ' on ' + repeatDays;
|
if (task._edit.frequency === 'weekly') summary += ' on ' + repeatDays;
|
||||||
|
|
||||||
@@ -381,9 +387,24 @@ angular.module('habitrpg')
|
|||||||
} else if (task._edit.frequency === 'yearly') {
|
} else if (task._edit.frequency === 'yearly') {
|
||||||
return task._edit.everyX == 1 ? window.env.t('year') : window.env.t('years');
|
return task._edit.everyX == 1 ? window.env.t('year') : window.env.t('years');
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function generateNextDue (task, user) {
|
||||||
|
var options = angular.copy(user);
|
||||||
|
options.nextDue = true;
|
||||||
|
var nextDueDates = Shared.shouldDo(new Date, task, options);
|
||||||
|
if (!nextDueDates) return '';
|
||||||
|
|
||||||
|
var dateFormat = 'MM-DD-YYYY';
|
||||||
|
if (user.preferences.dateFormat) dateFormat = user.preferences.dateFormat.toUpperCase();
|
||||||
|
|
||||||
|
var nextDue = nextDueDates.map(function (date) {
|
||||||
|
return date.format(dateFormat);
|
||||||
|
});
|
||||||
|
|
||||||
|
return nextDue.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
function cancelTaskEdit(task) {
|
function cancelTaskEdit(task) {
|
||||||
task._edit = undefined;
|
task._edit = undefined;
|
||||||
task._editing = false;
|
task._editing = false;
|
||||||
|
|||||||
@@ -167,5 +167,7 @@
|
|||||||
"taskNotes": "Task Notes",
|
"taskNotes": "Task Notes",
|
||||||
"monthlyRepeatHelpContent": "This task will be due every X months",
|
"monthlyRepeatHelpContent": "This task will be due every X months",
|
||||||
"yearlyRepeatHelpContent": "This task will be due every X years",
|
"yearlyRepeatHelpContent": "This task will be due every X years",
|
||||||
"resets": "Resets"
|
"resets": "Resets",
|
||||||
|
"summaryStart": "Repeats <%= frequency %> every <%= everyX %> <%= frequencyPlural %> ",
|
||||||
|
"nextDue": "Next Due Dates"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
Cron and time / day functions
|
Cron and time / day functions
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
import _ from 'lodash'; // eslint-disable-line lodash/import-scope
|
import defaults from 'lodash/defaults';
|
||||||
|
import invert from 'lodash/invert';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import 'moment-recur';
|
||||||
|
|
||||||
export const DAY_MAPPING = {
|
export const DAY_MAPPING = {
|
||||||
0: 'su',
|
0: 'su',
|
||||||
@@ -17,6 +19,8 @@ export const DAY_MAPPING = {
|
|||||||
6: 's',
|
6: 's',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DAY_MAPPING_STRING_TO_NUMBER = invert(DAY_MAPPING);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences.
|
Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences.
|
||||||
Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values.
|
Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values.
|
||||||
@@ -25,13 +29,14 @@ export const DAY_MAPPING = {
|
|||||||
|
|
||||||
function sanitizeOptions (o) {
|
function sanitizeOptions (o) {
|
||||||
let ref = Number(o.dayStart || 0);
|
let ref = Number(o.dayStart || 0);
|
||||||
let dayStart = !_.isNaN(ref) && ref >= 0 && ref <= 24 ? ref : 0;
|
let dayStart = !Number.isNaN(ref) && ref >= 0 && ref <= 24 ? ref : 0;
|
||||||
|
|
||||||
let timezoneOffset;
|
let timezoneOffset;
|
||||||
let timezoneOffsetDefault = Number(moment().zone());
|
let timezoneOffsetDefault = Number(moment().zone());
|
||||||
if (_.isFinite(o.timezoneOffsetOverride)) {
|
|
||||||
|
if (isFinite(o.timezoneOffsetOverride)) {
|
||||||
timezoneOffset = Number(o.timezoneOffsetOverride);
|
timezoneOffset = Number(o.timezoneOffsetOverride);
|
||||||
} else if (_.isFinite(o.timezoneOffset)) {
|
} else if (Number.isFinite(o.timezoneOffset)) {
|
||||||
timezoneOffset = Number(o.timezoneOffset);
|
timezoneOffset = Number(o.timezoneOffset);
|
||||||
} else {
|
} else {
|
||||||
timezoneOffset = timezoneOffsetDefault;
|
timezoneOffset = timezoneOffsetDefault;
|
||||||
@@ -81,7 +86,7 @@ export function startOfDay (options = {}) {
|
|||||||
export function daysSince (yesterday, options = {}) {
|
export function daysSince (yesterday, options = {}) {
|
||||||
let o = sanitizeOptions(options);
|
let o = sanitizeOptions(options);
|
||||||
|
|
||||||
return startOfDay(_.defaults({ now: o.now }, o)).diff(startOfDay(_.defaults({ now: yesterday }, o)), 'days');
|
return startOfDay(defaults({ now: o.now }, o)).diff(startOfDay(defaults({ now: yesterday }, o)), 'days');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -93,32 +98,94 @@ export function shouldDo (day, dailyTask, options = {}) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let o = sanitizeOptions(options);
|
let o = sanitizeOptions(options);
|
||||||
let startOfDayWithCDSTime = startOfDay(_.defaults({ now: day }, o));
|
let startOfDayWithCDSTime = startOfDay(defaults({ now: day }, o));
|
||||||
|
|
||||||
// The time portion of the Start Date is never visible to or modifiable by the user so we must ignore it.
|
// The time portion of the Start Date is never visible to or modifiable by the user so we must ignore it.
|
||||||
// Therefore, we must also ignore the time portion of the user's day start (startOfDayWithCDSTime), otherwise the date comparison will be wrong for some times.
|
// Therefore, we must also ignore the time portion of the user's day start (startOfDayWithCDSTime), otherwise the date comparison will be wrong for some times.
|
||||||
// NB: The user's day start date has already been converted to the PREVIOUS day's date if the time portion was before CDS.
|
// NB: The user's day start date has already been converted to the PREVIOUS day's date if the time portion was before CDS.
|
||||||
let taskStartDate = moment(dailyTask.startDate).zone(o.timezoneOffset);
|
|
||||||
|
|
||||||
taskStartDate = moment(taskStartDate).startOf('day');
|
let startDate = moment(dailyTask.startDate).zone(o.timezoneOffset).startOf('day');
|
||||||
if (taskStartDate > startOfDayWithCDSTime.startOf('day')) {
|
|
||||||
|
if (startDate > startOfDayWithCDSTime.startOf('day') && !options.nextDue) {
|
||||||
return false; // Daily starts in the future
|
return false; // Daily starts in the future
|
||||||
}
|
}
|
||||||
if (dailyTask.frequency === 'daily') { // "Every X Days"
|
|
||||||
if (!dailyTask.everyX) {
|
|
||||||
return false; // error condition
|
|
||||||
}
|
|
||||||
let daysSinceTaskStart = startOfDayWithCDSTime.startOf('day').diff(taskStartDate, 'days');
|
|
||||||
|
|
||||||
return daysSinceTaskStart % dailyTask.everyX === 0;
|
let daysOfTheWeek = [];
|
||||||
} else if (dailyTask.frequency === 'weekly') { // "On Certain Days of the Week"
|
|
||||||
if (!dailyTask.repeat) {
|
|
||||||
return false; // error condition
|
|
||||||
}
|
|
||||||
let dayOfWeekNum = startOfDayWithCDSTime.day(); // e.g., 0 for Sunday
|
|
||||||
|
|
||||||
return dailyTask.repeat[DAY_MAPPING[dayOfWeekNum]];
|
if (dailyTask.repeat) {
|
||||||
} else {
|
for (let [repeatDay, active] of Object.entries(dailyTask.repeat)) {
|
||||||
return false; // error condition - unexpected frequency string
|
if (active) daysOfTheWeek.push(parseInt(DAY_MAPPING_STRING_TO_NUMBER[repeatDay], 10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (dailyTask.frequency === 'daily') {
|
||||||
|
if (!dailyTask.everyX) return false; // error condition
|
||||||
|
let schedule = moment(startDate).recur()
|
||||||
|
.every(dailyTask.everyX).days();
|
||||||
|
|
||||||
|
if (options.nextDue) return schedule.fromDate(startOfDayWithCDSTime).next(6);
|
||||||
|
|
||||||
|
return schedule.matches(startOfDayWithCDSTime);
|
||||||
|
} else if (dailyTask.frequency === 'weekly') {
|
||||||
|
let schedule = moment(startDate).recur();
|
||||||
|
|
||||||
|
let differenceInWeeks = moment(startOfDayWithCDSTime).week() - moment(startDate).week();
|
||||||
|
let matchEveryX = differenceInWeeks % dailyTask.everyX === 0;
|
||||||
|
|
||||||
|
if (daysOfTheWeek.length === 0) return false;
|
||||||
|
schedule = schedule.every(daysOfTheWeek).daysOfWeek();
|
||||||
|
|
||||||
|
if (options.nextDue) {
|
||||||
|
let dates = schedule.fromDate(startOfDayWithCDSTime.subtract('1', 'days')).next(6);
|
||||||
|
let filterDates = dates.filter((momentDate) => {
|
||||||
|
let weekDiff = momentDate.week() - moment(startDate).week();
|
||||||
|
let matchX = weekDiff % dailyTask.everyX === 0;
|
||||||
|
return matchX;
|
||||||
|
});
|
||||||
|
return filterDates;
|
||||||
|
}
|
||||||
|
|
||||||
|
return schedule.matches(startOfDayWithCDSTime) && matchEveryX;
|
||||||
|
} else if (dailyTask.frequency === 'monthly') {
|
||||||
|
let schedule = moment(startDate).recur();
|
||||||
|
|
||||||
|
let differenceInMonths = moment(startOfDayWithCDSTime).month() - moment(startDate).month();
|
||||||
|
let matchEveryX = differenceInMonths % dailyTask.everyX === 0;
|
||||||
|
|
||||||
|
if (dailyTask.weeksOfMonth && dailyTask.weeksOfMonth.length > 0) {
|
||||||
|
schedule = schedule.every(daysOfTheWeek).daysOfWeek()
|
||||||
|
.every(dailyTask.weeksOfMonth).weeksOfMonthByDay();
|
||||||
|
} else if (dailyTask.daysOfMonth && dailyTask.daysOfMonth.length > 0) {
|
||||||
|
schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.nextDue) {
|
||||||
|
let dates = schedule.fromDate(startOfDayWithCDSTime).next(6);
|
||||||
|
let filterDates = dates.filter((momentDate) => {
|
||||||
|
let monthDiff = momentDate.month() - moment(startDate).month();
|
||||||
|
let matchX = monthDiff % dailyTask.everyX === 0;
|
||||||
|
return matchX;
|
||||||
|
});
|
||||||
|
return filterDates;
|
||||||
|
}
|
||||||
|
|
||||||
|
return schedule.matches(startOfDayWithCDSTime) && matchEveryX;
|
||||||
|
} else if (dailyTask.frequency === 'yearly') {
|
||||||
|
let schedule = moment(startDate).recur();
|
||||||
|
|
||||||
|
schedule = schedule.every(dailyTask.everyX).years();
|
||||||
|
|
||||||
|
if (options.nextDue) {
|
||||||
|
let dates = schedule.fromDate(startOfDayWithCDSTime).next(6);
|
||||||
|
let filterDates = dates.filter((momentDate) => {
|
||||||
|
let monthDiff = momentDate.years() - moment(startDate).years();
|
||||||
|
let matchX = monthDiff % dailyTask.everyX === 0;
|
||||||
|
return matchX;
|
||||||
|
});
|
||||||
|
return filterDates;
|
||||||
|
}
|
||||||
|
|
||||||
|
return schedule.matches(startOfDayWithCDSTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
createTasks,
|
createTasks,
|
||||||
getTasks,
|
getTasks,
|
||||||
moveTask,
|
moveTask,
|
||||||
|
setNextDue,
|
||||||
} from '../../libs/taskManager';
|
} from '../../libs/taskManager';
|
||||||
import common from '../../../common';
|
import common from '../../../common';
|
||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird';
|
||||||
@@ -456,9 +457,7 @@ api.updateTask = {
|
|||||||
task.group.approval.required = true;
|
task.group.approval.required = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sanitizedObj.type === 'daily') {
|
setNextDue(task, user);
|
||||||
task.isDue = common.shouldDo(Date.now(), sanitizedObj, user.preferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
let savedTask = await task.save();
|
let savedTask = await task.save();
|
||||||
|
|
||||||
@@ -589,6 +588,8 @@ api.scoreTask = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setNextDue(task, user);
|
||||||
|
|
||||||
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
|
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
|
||||||
user._ABtests.counter++;
|
user._ABtests.counter++;
|
||||||
if (user._ABtests.counter > 1) {
|
if (user._ABtests.counter > 1) {
|
||||||
@@ -601,10 +602,6 @@ api.scoreTask = {
|
|||||||
user.markModified('_ABtests');
|
user.markModified('_ABtests');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.type === 'daily') {
|
|
||||||
task.isDue = common.shouldDo(Date.now(), task, user.preferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
let results = await Bluebird.all([
|
let results = await Bluebird.all([
|
||||||
user.save(),
|
user.save(),
|
||||||
task.save(),
|
task.save(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { model as User } from '../models/user';
|
|||||||
import common from '../../common/';
|
import common from '../../common/';
|
||||||
import { preenUserHistory } from '../libs/preening';
|
import { preenUserHistory } from '../libs/preening';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
|
||||||
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
||||||
@@ -314,7 +315,15 @@ export function cron (options = {}) {
|
|||||||
value: task.value,
|
value: task.value,
|
||||||
});
|
});
|
||||||
task.completed = false;
|
task.completed = false;
|
||||||
task.isDue = common.shouldDo(Date.now(), task, user.preferences);
|
|
||||||
|
let optionsForShouldDo = cloneDeep(user.preferences.toObject());
|
||||||
|
task.isDue = common.shouldDo(now, task, optionsForShouldDo);
|
||||||
|
optionsForShouldDo.nextDue = true;
|
||||||
|
let nextDue = common.shouldDo(now, task, optionsForShouldDo);
|
||||||
|
|
||||||
|
if (nextDue && nextDue.length > 0) {
|
||||||
|
task.nextDue = nextDue;
|
||||||
|
}
|
||||||
|
|
||||||
if (completed || scheduleMisses > 0) {
|
if (completed || scheduleMisses > 0) {
|
||||||
if (task.checklist) {
|
if (task.checklist) {
|
||||||
|
|||||||
@@ -22,6 +22,19 @@ async function _validateTaskAlias (tasks, res) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setNextDue (task, user) {
|
||||||
|
if (task.type !== 'daily') return;
|
||||||
|
|
||||||
|
let optionsForShouldDo = user.preferences.toObject();
|
||||||
|
task.isDue = shared.shouldDo(Date.now(), task, optionsForShouldDo);
|
||||||
|
optionsForShouldDo.nextDue = true;
|
||||||
|
let nextDue = shared.shouldDo(Date.now(), task, optionsForShouldDo);
|
||||||
|
if (nextDue && nextDue.length > 0) {
|
||||||
|
task.nextDue = nextDue.map((dueDate) => {
|
||||||
|
return dueDate.toISOString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates tasks for a user, challenge or group.
|
* Creates tasks for a user, challenge or group.
|
||||||
@@ -64,7 +77,7 @@ export async function createTasks (req, res, options = {}) {
|
|||||||
newTask.userId = user._id;
|
newTask.userId = user._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newTask.type === 'daily') newTask.isDue = shared.shouldDo(Date.now(), newTask, user.preferences);
|
setNextDue(newTask, user);
|
||||||
|
|
||||||
// Validate that the task is valid and throw if it isn't
|
// Validate that the task is valid and throw if it isn't
|
||||||
// otherwise since we're saving user/challenge/group and task in parallel it could save the user/challenge/group with a tasksOrder that doens't match reality
|
// otherwise since we're saving user/challenge/group and task in parallel it could save the user/challenge/group with a tasksOrder that doens't match reality
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export let TaskSchema = new Schema({
|
|||||||
}, discriminatorOptions));
|
}, discriminatorOptions));
|
||||||
|
|
||||||
TaskSchema.plugin(baseModel, {
|
TaskSchema.plugin(baseModel, {
|
||||||
noSet: ['challenge', 'userId', 'completed', 'history', 'dateCompleted', '_legacyId', 'group', 'isDue'],
|
noSet: ['challenge', 'userId', 'completed', 'history', 'dateCompleted', '_legacyId', 'group', 'isDue', 'nextDue'],
|
||||||
sanitizeTransform (taskObj) {
|
sanitizeTransform (taskObj) {
|
||||||
if (taskObj.type && taskObj.type !== 'reward') { // value should be settable directly only for rewards
|
if (taskObj.type && taskObj.type !== 'reward') { // value should be settable directly only for rewards
|
||||||
delete taskObj.value;
|
delete taskObj.value;
|
||||||
@@ -243,6 +243,7 @@ export let DailySchema = new Schema(_.defaults({
|
|||||||
daysOfMonth: {type: [Number], default: []}, // Days of the month that the daily should repeat on
|
daysOfMonth: {type: [Number], default: []}, // Days of the month that the daily should repeat on
|
||||||
weeksOfMonth: {type: [Number], default: []}, // Weeks of the month that the daily should repeat on
|
weeksOfMonth: {type: [Number], default: []}, // Weeks of the month that the daily should repeat on
|
||||||
isDue: {type: Boolean},
|
isDue: {type: Boolean},
|
||||||
|
nextDue: [{type: String}],
|
||||||
}, habitDailySchema(), dailyTodoSchema()), subDiscriminatorOptions);
|
}, habitDailySchema(), dailyTodoSchema()), subDiscriminatorOptions);
|
||||||
export let daily = Task.discriminator('daily', DailySchema);
|
export let daily = Task.discriminator('daily', DailySchema);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.form-group(ng-if='task._edit.frequency !== "weekly"')
|
.form-group
|
||||||
legend.option-title
|
legend.option-title
|
||||||
span.hint(popover-trigger='mouseenter', popover-title=env.t('repeatHelpTitle'),
|
span.hint(popover-trigger='mouseenter', popover-title=env.t('repeatHelpTitle'),
|
||||||
popover='{{env.t(task._edit.frequency + "RepeatHelpContent")}}')=env.t('repeatEvery')
|
popover='{{env.t(task._edit.frequency + "RepeatHelpContent")}}')=env.t('repeatEvery')
|
||||||
|
|||||||
@@ -8,27 +8,31 @@ fieldset.option-group.advanced-option(ng-show="task.type === 'daily'")
|
|||||||
|
|
||||||
br
|
br
|
||||||
|
|
||||||
//- select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
|
||||||
//- option(value='daily')=env.t('daily')
|
|
||||||
//- option(value='weekly')=env.t('weekly')
|
|
||||||
//- option(value='monthly')=env.t('monthly')
|
|
||||||
//- option(value='yearly')=env.t('yearly')
|
|
||||||
|
|
||||||
select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
||||||
option(value='weekly')=env.t('repeatWeek')
|
option(value='daily')=env.t('daily')
|
||||||
option(value='daily')=env.t('repeatDays')
|
option(value='weekly')=env.t('weekly')
|
||||||
|
option(value='monthly')=env.t('monthly')
|
||||||
|
option(value='yearly')=env.t('yearly')
|
||||||
|
|
||||||
|
//- select.form-control(ng-model='task._edit.frequency', ng-disabled='!canEdit(task)')
|
||||||
|
//- option(value='weekly')=env.t('repeatWeek')
|
||||||
|
//- option(value='daily')=env.t('repeatDays')
|
||||||
|
|
||||||
include ./dailies/repeat_options
|
include ./dailies/repeat_options
|
||||||
|
|
||||||
.form-group(ng-show='task._edit.frequency === "monthly"')
|
.form-group(ng-show='task._edit.frequency === "monthly"')
|
||||||
legend.option-title=env.t('repeatsOn')
|
legend.option-title=env.t('repeatsOn')
|
||||||
label
|
label
|
||||||
input(type="radio", ng-model='task._edit.repeatsOn', value='dayOfMonth')
|
input(type="radio", ng-model='task._edit.repeatsOn', value='dayOfMonth', ng-disabled='!canEdit(task)')
|
||||||
=env.t('dayOfMonth')
|
=env.t('dayOfMonth')
|
||||||
label
|
label
|
||||||
input(type="radio", ng-model='task._edit.repeatsOn', value='dayOfWeek')
|
input(type="radio", ng-model='task._edit.repeatsOn', value='dayOfWeek', ng-disabled='!canEdit(task)')
|
||||||
=env.t('dayOfWeek')
|
=env.t('dayOfWeek')
|
||||||
|
|
||||||
//- .form-group
|
.form-group
|
||||||
//- legend.option-title=env.t('summary')
|
legend.option-title=env.t('summary')
|
||||||
//- div {{summary}}
|
div {{summary}}
|
||||||
|
|
||||||
|
.form-group(ng-if='nextDue')
|
||||||
|
legend.option-title=env.t('nextDue')
|
||||||
|
div {{nextDue}}
|
||||||
|
|||||||
Reference in New Issue
Block a user