diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index ef936b96576..37897a835a6 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -1379,6 +1379,10 @@ class Task(models.Model):
return action
+ # ------------
+ # Actions
+ # ------------
+
def action_recurring_tasks(self):
return {
'name': 'Tasks in Recurrence',
@@ -1388,6 +1392,15 @@ class Task(models.Model):
'domain': [('recurrence_id', 'in', self.recurrence_id.ids)],
}
+ def action_stop_recurrence(self):
+ tasks = self.env['project.task'].with_context(active_test=False).search([('recurrence_id', 'in', self.recurrence_id.ids)])
+ tasks.write({'recurring_task': False})
+ self.recurrence_id.unlink()
+
+ def action_continue_recurrence(self):
+ self.recurrence_id = False
+ self.recurring_task = False
+
# ---------------------------------------------------
# Rating business
# ---------------------------------------------------
diff --git a/addons/project/static/src/js/project_form.js b/addons/project/static/src/js/project_form.js
new file mode 100644
index 00000000000..9d82d876793
--- /dev/null
+++ b/addons/project/static/src/js/project_form.js
@@ -0,0 +1,109 @@
+odoo.define('project.ProjectFormView', function (require) {
+ "use strict";
+
+ const Dialog = require('web.Dialog');
+ const FormView = require('web.FormView');
+ const FormController = require('web.FormController');
+ const core = require('web.core');
+ const view_registry = require('web.view_registry');
+
+ const _t = core._t;
+
+ const ProjectFormController = FormController.extend({
+ _getActionMenuItems(state) {
+ if (!this.archiveEnabled || !state.data['recurrence_id']) {
+ return this._super(...arguments);
+ }
+
+ this.archiveEnabled = false;
+ let actions = this._super(...arguments);
+ this.archiveEnabled = true;
+
+ if (actions) {
+ const activeField = this.model.getActiveField(state);
+ actions.items.other.unshift({
+ description: state.data[activeField] ? _t('Archive') : _t('Unarchive'),
+ callback: () => this._stopRecurrence(state.data['id'], state.data[activeField]?'archive':'unarchive'),
+ });
+ }
+
+ return actions;
+ },
+
+ _onDeleteRecord() {
+ const record = this.model.get(this.handle);
+
+ if(!record.data.recurrence_id) {
+ return this._super(...arguments);
+ }
+ this._stopRecurrence(record.res_id, 'delete');
+ },
+
+ _stopRecurrence(res_id, mode) {
+ new Dialog(this, {
+ buttons: [
+ {
+ classes: 'btn-primary',
+ click: () => {
+ this._rpc({
+ model: 'project.task',
+ method: 'action_stop_recurrence',
+ args: [res_id],
+ }).then(() => {
+ if (mode === 'archive') {
+ this._toggleArchiveState(true);
+ } else if (mode === 'unarchive') {
+ this._toggleArchiveState(false);
+ } else if (mode === 'delete') {
+ this._deleteRecords([this.handle]);
+ }
+ });
+ },
+ close: true,
+ text: _t('Stop Recurrence'),
+ },
+ {
+ click: () => {
+ this._rpc({
+ model: 'project.task',
+ method: 'action_continue_recurrence',
+ args: [res_id],
+ }).then(() => {
+ if (mode === 'archive') {
+ this._toggleArchiveState(true);
+ } else if (mode === 'unarchive') {
+ this._toggleArchiveState(false);
+ } else if (mode === 'delete') {
+ this._deleteRecords([this.handle]);
+ }
+ });
+ },
+ close: true,
+ text: _t('Continue Recurrence'),
+ },
+ {
+ close: true,
+ text: _t('Discard'),
+ }
+ ],
+ size: 'medium',
+ title: _t('Confirmation'),
+ $content: $('', {
+ role: 'alert',
+ text: _t('It seems that this task is part of a recurrence.'),
+ }),
+ }).open();
+ }
+
+ });
+
+ const ProjectFormView = FormView.extend({
+ config: _.extend({}, FormView.prototype.config, {
+ Controller: ProjectFormController,
+ }),
+ });
+
+ view_registry.add('project_form', ProjectFormView);
+
+ return ProjectFormView;
+});
diff --git a/addons/project/static/src/js/project_list.js b/addons/project/static/src/js/project_list.js
new file mode 100644
index 00000000000..48be808876d
--- /dev/null
+++ b/addons/project/static/src/js/project_list.js
@@ -0,0 +1,112 @@
+odoo.define('project.ProjectListView', function (require) {
+ "use strict";
+
+ const Dialog = require('web.Dialog');
+ const ListView = require('web.ListView');
+ const ListController = require('web.ListController');
+ const core = require('web.core');
+ const view_registry = require('web.view_registry');
+
+ const _t = core._t;
+
+ const ProjectListController = ListController.extend({
+ _getActionMenuItems(state) {
+ if(!this.archiveEnabled) {
+ return this._super(...arguments);
+ }
+
+ const recurringRecords = this.getSelectedRecords().filter(rec => rec.data.recurrence_id).map(rec => rec.data.id);
+ this.archiveEnabled = recurringRecords.length == 0;
+ let actions = this._super(...arguments);
+ this.archiveEnabled = true;
+
+ if(actions && recurringRecords.length > 0) {
+ actions.items.other.unshift({
+ description: _t('Archive'),
+ callback: () => this._stopRecurrence(recurringRecords, this.selectedRecords, 'archive'),
+ }, {
+ description: _t('Unarchive'),
+ callback: () => this._toggleArchiveState(false)
+ });
+ }
+ return actions;
+ },
+
+ _onDeleteSelectedRecords() {
+ const recurringRecords = this.getSelectedRecords().filter(rec => rec.data.recurrence_id).map(rec => rec.data.id);
+ if(recurringRecords.length > 0) {
+ return this._stopRecurrence(recurringRecords, this.selectedRecords, 'delete');
+ }
+
+ return this._super(...arguments);
+ },
+
+ _stopRecurrence(recurring_res_ids, res_ids, mode) {
+ let warning;
+ if (res_ids.length > 1) {
+ warning = _t('It seems that some tasks are part of a recurrence.');
+ } else {
+ warning = _t('It seems that this task is part of a recurrence.');
+ }
+ return new Dialog(this, {
+ buttons: [
+ {
+ classes: 'btn-primary',
+ click: () => {
+ this._rpc({
+ model: 'project.task',
+ method: 'action_stop_recurrence',
+ args: [recurring_res_ids],
+ }).then(() => {
+ if (mode === 'archive') {
+ this._toggleArchiveState(true);
+ } else if (mode === 'delete') {
+ this._deleteRecords(res_ids);
+ }
+ });
+ },
+ close: true,
+ text: _t('Stop Recurrence'),
+ },
+ {
+ click: () => {
+ this._rpc({
+ model: 'project.task',
+ method: 'action_continue_recurrence',
+ args: [recurring_res_ids],
+ }).then(() => {
+ if (mode === 'archive') {
+ this._toggleArchiveState(true);
+ } else if (mode === 'delete') {
+ this._deleteRecords(res_ids);
+ }
+ });
+ },
+ close: true,
+ text: _t('Continue Recurrence'),
+ },
+ {
+ close: true,
+ text: _t('Discard'),
+ }
+ ],
+ size: 'medium',
+ title: _t('Confirmation'),
+ $content: $('', {
+ role: 'alert',
+ text: warning,
+ }),
+ }).open();
+ }
+ });
+
+ const ProjectListView = ListView.extend({
+ config: _.extend({}, ListView.prototype.config, {
+ Controller: ProjectListController,
+ }),
+ });
+
+ view_registry.add('project_list', ProjectListView);
+
+ return ProjectListView;
+});
diff --git a/addons/project/views/project_assets.xml b/addons/project/views/project_assets.xml
index 4a520675e9d..76c047170b5 100644
--- a/addons/project/views/project_assets.xml
+++ b/addons/project/views/project_assets.xml
@@ -3,7 +3,9 @@
+
+
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index be4a1deeed5..671634a7806 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -614,7 +614,7 @@
project.task
-