356 lines
10 KiB
JavaScript
356 lines
10 KiB
JavaScript
const { DataTypes } = require('sequelize');
|
|
const { uid } = require('../utils/uid');
|
|
|
|
module.exports = (sequelize) => {
|
|
const Task = sequelize.define(
|
|
'Task',
|
|
{
|
|
id: {
|
|
type: DataTypes.INTEGER,
|
|
primaryKey: true,
|
|
autoIncrement: true,
|
|
},
|
|
uid: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
unique: true,
|
|
defaultValue: uid,
|
|
},
|
|
name: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
},
|
|
due_date: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true,
|
|
},
|
|
defer_until: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true,
|
|
},
|
|
priority: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
defaultValue: 0,
|
|
validate: {
|
|
min: 0,
|
|
max: 2,
|
|
},
|
|
},
|
|
status: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
defaultValue: 0,
|
|
validate: {
|
|
min: 0,
|
|
max: 6,
|
|
},
|
|
},
|
|
note: {
|
|
type: DataTypes.TEXT,
|
|
allowNull: true,
|
|
},
|
|
recurrence_type: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
defaultValue: 'none',
|
|
},
|
|
recurrence_interval: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
},
|
|
recurrence_end_date: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true,
|
|
},
|
|
recurrence_weekday: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
validate: {
|
|
min: 0,
|
|
max: 6,
|
|
},
|
|
},
|
|
recurrence_weekdays: {
|
|
type: DataTypes.TEXT,
|
|
allowNull: true,
|
|
get() {
|
|
const rawValue = this.getDataValue('recurrence_weekdays');
|
|
return rawValue ? JSON.parse(rawValue) : null;
|
|
},
|
|
set(value) {
|
|
this.setDataValue(
|
|
'recurrence_weekdays',
|
|
value ? JSON.stringify(value) : null
|
|
);
|
|
},
|
|
},
|
|
recurrence_month_day: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
validate: {
|
|
min: -1,
|
|
max: 31,
|
|
},
|
|
},
|
|
recurrence_week_of_month: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
validate: {
|
|
min: 1,
|
|
max: 5,
|
|
},
|
|
},
|
|
completion_based: {
|
|
type: DataTypes.BOOLEAN,
|
|
allowNull: false,
|
|
defaultValue: false,
|
|
},
|
|
user_id: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
references: {
|
|
model: 'users',
|
|
key: 'id',
|
|
},
|
|
},
|
|
project_id: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'projects',
|
|
key: 'id',
|
|
},
|
|
},
|
|
recurring_parent_id: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'tasks',
|
|
key: 'id',
|
|
},
|
|
},
|
|
parent_task_id: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'tasks',
|
|
key: 'id',
|
|
},
|
|
},
|
|
order: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Order position for subtasks within a parent task',
|
|
},
|
|
completed_at: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true,
|
|
},
|
|
habit_mode: {
|
|
type: DataTypes.BOOLEAN,
|
|
allowNull: false,
|
|
defaultValue: false,
|
|
},
|
|
habit_target_count: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
},
|
|
habit_frequency_period: {
|
|
type: DataTypes.STRING,
|
|
allowNull: true,
|
|
},
|
|
habit_streak_mode: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
defaultValue: 'calendar',
|
|
},
|
|
habit_flexibility_mode: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
defaultValue: 'flexible',
|
|
},
|
|
habit_current_streak: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
defaultValue: 0,
|
|
},
|
|
habit_best_streak: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
defaultValue: 0,
|
|
},
|
|
habit_total_completions: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: false,
|
|
defaultValue: 0,
|
|
},
|
|
habit_last_completion_at: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true,
|
|
},
|
|
},
|
|
{
|
|
tableName: 'tasks',
|
|
indexes: [
|
|
{
|
|
fields: ['user_id'],
|
|
},
|
|
{
|
|
fields: ['project_id'],
|
|
},
|
|
{
|
|
fields: ['recurrence_type'],
|
|
},
|
|
{
|
|
fields: ['parent_task_id'],
|
|
},
|
|
{
|
|
name: 'tasks_parent_task_id_order',
|
|
fields: ['parent_task_id', 'order'],
|
|
},
|
|
// Performance indexes for slow I/O systems (e.g., Synology NAS)
|
|
{
|
|
name: 'tasks_status_idx',
|
|
fields: ['status'],
|
|
},
|
|
{
|
|
name: 'tasks_due_date_idx',
|
|
fields: ['due_date'],
|
|
},
|
|
{
|
|
name: 'tasks_recurring_parent_id_idx',
|
|
fields: ['recurring_parent_id'],
|
|
},
|
|
{
|
|
name: 'tasks_completed_at_idx',
|
|
fields: ['completed_at'],
|
|
},
|
|
{
|
|
name: 'tasks_user_id_status_idx',
|
|
fields: ['user_id', 'status'],
|
|
},
|
|
{
|
|
name: 'tasks_user_status_parent_idx',
|
|
fields: ['user_id', 'status', 'parent_task_id'],
|
|
},
|
|
{
|
|
name: 'tasks_user_due_date_status_idx',
|
|
fields: ['user_id', 'due_date', 'status'],
|
|
},
|
|
{
|
|
name: 'tasks_user_completed_at_status_idx',
|
|
fields: ['user_id', 'completed_at', 'status'],
|
|
},
|
|
],
|
|
}
|
|
);
|
|
|
|
Task.associate = function (models) {
|
|
Task.belongsTo(models.Task, {
|
|
as: 'RecurringParent',
|
|
foreignKey: 'recurring_parent_id',
|
|
});
|
|
|
|
Task.hasMany(models.Task, {
|
|
as: 'RecurringChildren',
|
|
foreignKey: 'recurring_parent_id',
|
|
});
|
|
|
|
Task.belongsTo(models.Task, {
|
|
as: 'ParentTask',
|
|
foreignKey: 'parent_task_id',
|
|
});
|
|
|
|
Task.hasMany(models.Task, {
|
|
as: 'Subtasks',
|
|
foreignKey: 'parent_task_id',
|
|
});
|
|
};
|
|
|
|
Task.PRIORITY = {
|
|
LOW: 0,
|
|
MEDIUM: 1,
|
|
HIGH: 2,
|
|
};
|
|
|
|
Task.STATUS = {
|
|
NOT_STARTED: 0,
|
|
IN_PROGRESS: 1,
|
|
DONE: 2,
|
|
ARCHIVED: 3,
|
|
WAITING: 4,
|
|
CANCELLED: 5,
|
|
PLANNED: 6,
|
|
};
|
|
|
|
Task.RECURRENCE_TYPE = {
|
|
NONE: 'none',
|
|
DAILY: 'daily',
|
|
WEEKLY: 'weekly',
|
|
MONTHLY: 'monthly',
|
|
MONTHLY_WEEKDAY: 'monthly_weekday',
|
|
MONTHLY_LAST_DAY: 'monthly_last_day',
|
|
};
|
|
|
|
Task.HABIT_FREQUENCY_PERIOD = {
|
|
DAILY: 'daily',
|
|
WEEKLY: 'weekly',
|
|
MONTHLY: 'monthly',
|
|
};
|
|
|
|
Task.HABIT_STREAK_MODE = {
|
|
CALENDAR: 'calendar',
|
|
SCHEDULED: 'scheduled',
|
|
};
|
|
|
|
Task.HABIT_FLEXIBILITY_MODE = {
|
|
STRICT: 'strict',
|
|
FLEXIBLE: 'flexible',
|
|
};
|
|
|
|
const getPriorityName = (priorityValue) => {
|
|
const priorities = ['low', 'medium', 'high'];
|
|
return priorities[priorityValue] || 'low';
|
|
};
|
|
|
|
const getStatusName = (statusValue) => {
|
|
const statuses = [
|
|
'not_started',
|
|
'in_progress',
|
|
'done',
|
|
'archived',
|
|
'waiting',
|
|
'cancelled',
|
|
'planned',
|
|
];
|
|
return statuses[statusValue] || 'not_started';
|
|
};
|
|
|
|
const getPriorityValue = (priorityName) => {
|
|
const priorities = { low: 0, medium: 1, high: 2 };
|
|
return priorities[priorityName] !== undefined
|
|
? priorities[priorityName]
|
|
: 0;
|
|
};
|
|
|
|
const getStatusValue = (statusName) => {
|
|
const statuses = {
|
|
not_started: 0,
|
|
in_progress: 1,
|
|
done: 2,
|
|
archived: 3,
|
|
waiting: 4,
|
|
cancelled: 5,
|
|
planned: 6,
|
|
};
|
|
return statuses[statusName] !== undefined ? statuses[statusName] : 0;
|
|
};
|
|
|
|
Task.getPriorityName = getPriorityName;
|
|
Task.getStatusName = getStatusName;
|
|
Task.getPriorityValue = getPriorityValue;
|
|
Task.getStatusValue = getStatusValue;
|
|
|
|
return Task;
|
|
};
|