forked from Techsystech/web
[MIG] web_widget_one2many_tree_line_duplicate: Migration to 15.0
parent
9857c82c94
commit
3e62bb4deb
|
@ -7,7 +7,7 @@ Web Widget One2many Tree Line Duplicate
|
||||||
!! This file is generated by oca-gen-addon-readme !!
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
!! changes will be overwritten. !!
|
!! changes will be overwritten. !!
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
!! source digest: sha256:9cd7a43eab907d7d3ed3380c20cda1be40d288c01feb25dd54108e69cb350314
|
!! source digest: sha256:620c16e6d6bd0ad72a9d7eb3c8f243fbbf721898252fea9c26d70b2e0a7822d7
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
@ -17,13 +17,13 @@ Web Widget One2many Tree Line Duplicate
|
||||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
:alt: License: AGPL-3
|
:alt: License: AGPL-3
|
||||||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
|
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
|
||||||
:target: https://github.com/OCA/web/tree/13.0/web_widget_one2many_tree_line_duplicate
|
:target: https://github.com/OCA/web/tree/15.0/web_widget_one2many_tree_line_duplicate
|
||||||
:alt: OCA/web
|
:alt: OCA/web
|
||||||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||||
:target: https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_widget_one2many_tree_line_duplicate
|
:target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_widget_one2many_tree_line_duplicate
|
||||||
:alt: Translate me on Weblate
|
:alt: Translate me on Weblate
|
||||||
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
||||||
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=13.0
|
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=15.0
|
||||||
:alt: Try me on Runboat
|
:alt: Try me on Runboat
|
||||||
|
|
||||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||||
|
@ -61,7 +61,7 @@ Bug Tracker
|
||||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
|
||||||
In case of trouble, please check there if your issue has already been reported.
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||||
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_widget_one2many_tree_line_duplicate%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_widget_one2many_tree_line_duplicate%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||||
|
|
||||||
Do not contact contributors directly about support or help with technical issues.
|
Do not contact contributors directly about support or help with technical issues.
|
||||||
|
|
||||||
|
@ -93,6 +93,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||||
mission is to support the collaborative development of Odoo features and
|
mission is to support the collaborative development of Odoo features and
|
||||||
promote its widespread use.
|
promote its widespread use.
|
||||||
|
|
||||||
This module is part of the `OCA/web <https://github.com/OCA/web/tree/13.0/web_widget_one2many_tree_line_duplicate>`_ project on GitHub.
|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/15.0/web_widget_one2many_tree_line_duplicate>`_ project on GitHub.
|
||||||
|
|
||||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||||
|
|
|
@ -4,12 +4,17 @@
|
||||||
{
|
{
|
||||||
"name": "Web Widget One2many Tree Line Duplicate",
|
"name": "Web Widget One2many Tree Line Duplicate",
|
||||||
"category": "web",
|
"category": "web",
|
||||||
"version": "13.0.1.0.2",
|
"version": "15.0.1.0.0",
|
||||||
"author": "Tecnativa, Odoo Community Association (OCA)",
|
"author": "Tecnativa, Odoo Community Association (OCA)",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"website": "https://github.com/OCA/web",
|
"website": "https://github.com/OCA/web",
|
||||||
"depends": ["web"],
|
"depends": ["web"],
|
||||||
"data": ["view/assets.xml"],
|
|
||||||
"auto_install": False,
|
"auto_install": False,
|
||||||
"installable": True,
|
"installable": True,
|
||||||
|
"assets": {
|
||||||
|
"web.assets_backend": [
|
||||||
|
"/web_widget_one2many_tree_line_duplicate/static/src/list/list_renderer.scss",
|
||||||
|
"/web_widget_one2many_tree_line_duplicate/static/src/list/list_renderer.esm.js",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -367,9 +367,9 @@ ul.auto-toc {
|
||||||
!! This file is generated by oca-gen-addon-readme !!
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
!! changes will be overwritten. !!
|
!! changes will be overwritten. !!
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
!! source digest: sha256:9cd7a43eab907d7d3ed3380c20cda1be40d288c01feb25dd54108e69cb350314
|
!! source digest: sha256:620c16e6d6bd0ad72a9d7eb3c8f243fbbf721898252fea9c26d70b2e0a7822d7
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||||
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/13.0/web_widget_one2many_tree_line_duplicate"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_widget_one2many_tree_line_duplicate"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=13.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/15.0/web_widget_one2many_tree_line_duplicate"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_widget_one2many_tree_line_duplicate"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=15.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
||||||
<p>Allow to add a icon to clone the line.</p>
|
<p>Allow to add a icon to clone the line.</p>
|
||||||
<p><strong>Table of contents</strong></p>
|
<p><strong>Table of contents</strong></p>
|
||||||
<div class="contents local topic" id="contents">
|
<div class="contents local topic" id="contents">
|
||||||
|
@ -408,7 +408,7 @@ ul.auto-toc {
|
||||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
|
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
|
||||||
In case of trouble, please check there if your issue has already been reported.
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||||
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_widget_one2many_tree_line_duplicate%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_widget_one2many_tree_line_duplicate%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="credits">
|
<div class="section" id="credits">
|
||||||
|
@ -435,7 +435,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
|
||||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||||
mission is to support the collaborative development of Odoo features and
|
mission is to support the collaborative development of Odoo features and
|
||||||
promote its widespread use.</p>
|
promote its widespread use.</p>
|
||||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/13.0/web_widget_one2many_tree_line_duplicate">OCA/web</a> project on GitHub.</p>
|
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/15.0/web_widget_one2many_tree_line_duplicate">OCA/web</a> project on GitHub.</p>
|
||||||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,420 +0,0 @@
|
||||||
/* Copyright 2021 Tecnativa - Alexandre Díaz
|
|
||||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
|
|
||||||
|
|
||||||
odoo.define("web_widget_one2many_tree_line_duplicate.BasicModel", function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const BasicModel = require("web.BasicModel");
|
|
||||||
const rpc = require("web.rpc");
|
|
||||||
|
|
||||||
function dateToServer(date) {
|
|
||||||
return date.clone().utc().locale("en").format("YYYY-MM-DD HH:mm:ss");
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicModel.include({
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_applyChange: function (recordID, changes) {
|
|
||||||
// The normal way is to have only one change with the 'CLONE' operation
|
|
||||||
// but to ensure that "omitOnchange" is used we check that almost one change
|
|
||||||
// is a 'CLONE' operation.
|
|
||||||
// TODO: This is done in this way to don't override other "big" methods
|
|
||||||
const has_clone_oper = !_.chain(changes)
|
|
||||||
.values()
|
|
||||||
.filter({operation: "CLONE"})
|
|
||||||
.isEmpty()
|
|
||||||
.value();
|
|
||||||
if (has_clone_oper) {
|
|
||||||
return this._applyChangeOmitOnchange.apply(this, arguments);
|
|
||||||
}
|
|
||||||
return this._super.apply(this, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force use "omitOnchange" when 'CLONE' operation are performed
|
|
||||||
*
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_applyX2ManyChange: function (record, fieldName, command) {
|
|
||||||
if (command.operation === "CLONE") {
|
|
||||||
// Return this._applyX2ManyChangeOmitOnchange.apply(this, arguments);
|
|
||||||
return this._cloneX2Many.apply(this, arguments);
|
|
||||||
}
|
|
||||||
return this._super.apply(this, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified implementation of '_applyX2ManyChange' to allow create
|
|
||||||
* without trigger onchanges
|
|
||||||
*
|
|
||||||
* @param {String} recordID
|
|
||||||
* @param {Object} changes
|
|
||||||
* @param {Object} options
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
_cloneX2Many: function (record, fieldName, command, options) {
|
|
||||||
const localID =
|
|
||||||
(record._changes && record._changes[fieldName]) ||
|
|
||||||
record.data[fieldName];
|
|
||||||
const list = this.localData[localID];
|
|
||||||
const context = _.extend({}, this._getContext(list));
|
|
||||||
const position = (command && command.position) || "bottom";
|
|
||||||
const viewType = (options && options.viewType) || record.viewType;
|
|
||||||
const fieldInfo = record.fieldsInfo[viewType][fieldName];
|
|
||||||
const record_command = this.get(command.id);
|
|
||||||
// Trigger addFieldsInfo on every possible view in the field to avoid
|
|
||||||
// errors when loading such views from the cloned line
|
|
||||||
const loaded_views = Object.keys(list.fieldsInfo);
|
|
||||||
const field_views = Object.keys(fieldInfo.views);
|
|
||||||
const to_load_views = field_views.filter(
|
|
||||||
(value) => !loaded_views.includes(value)
|
|
||||||
);
|
|
||||||
_.each(to_load_views, (name) => {
|
|
||||||
this.addFieldsInfo(localID, {
|
|
||||||
fields: fieldInfo.views[name].fields,
|
|
||||||
fieldInfo: fieldInfo.views[name].fieldsInfo[name],
|
|
||||||
viewType: name,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Only load fields available in the views. Otherwise we could get into
|
|
||||||
// problems when some process try to get their states.
|
|
||||||
var whitelisted_fields = [];
|
|
||||||
_.each(_.allKeys(record_command.fieldsInfo), function (view) {
|
|
||||||
_.each(_.allKeys(record_command.fieldsInfo[view]), function (field) {
|
|
||||||
if (!whitelisted_fields.includes(field)) {
|
|
||||||
whitelisted_fields.push(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
context: context,
|
|
||||||
fields: list.fields,
|
|
||||||
fieldsInfo: list.fieldsInfo,
|
|
||||||
parentID: list.id,
|
|
||||||
position: position,
|
|
||||||
allowWarning: options && options.allowWarning,
|
|
||||||
viewType: viewType,
|
|
||||||
views: fieldInfo.views,
|
|
||||||
clone_parent_record_id: command.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let read_data = Promise.resolve();
|
|
||||||
if (this.isNew(command.id)) {
|
|
||||||
// We need the 'copy_data' of the original parent record
|
|
||||||
if (!_.isEmpty(record_command.clone_data)) {
|
|
||||||
params.clone_copy_data = record_command.clone_copy_data;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Record state only has loaded data and only for the fields defined in the views.
|
|
||||||
// For this reason we need ensure copy all the model fields.
|
|
||||||
read_data = rpc.query({
|
|
||||||
model: list.model,
|
|
||||||
method: "copy_data",
|
|
||||||
args: [record_command.res_id],
|
|
||||||
kwargs: {context: record.getContext()},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return read_data.then((result) => {
|
|
||||||
const clone_values = _.defaults(
|
|
||||||
{},
|
|
||||||
this._getValuesToClone(record_command, params),
|
|
||||||
_.pick(result, whitelisted_fields)
|
|
||||||
);
|
|
||||||
return this._makeCloneRecord(list.model, params, clone_values)
|
|
||||||
.then((id) => {
|
|
||||||
const ids = [id];
|
|
||||||
list._changes = list._changes || [];
|
|
||||||
list._changes.push({
|
|
||||||
operation: "ADD",
|
|
||||||
id: id,
|
|
||||||
position: position,
|
|
||||||
isNew: true,
|
|
||||||
});
|
|
||||||
const local_record = this.localData[id];
|
|
||||||
list._cache[local_record.res_id] = id;
|
|
||||||
if (list.orderedResIDs) {
|
|
||||||
const index =
|
|
||||||
list.offset + (position !== "top" ? list.limit : 0);
|
|
||||||
list.orderedResIDs.splice(index, 0, local_record.res_id);
|
|
||||||
// List could be a copy of the original one
|
|
||||||
this.localData[list.id].orderedResIDs = list.orderedResIDs;
|
|
||||||
}
|
|
||||||
return ids;
|
|
||||||
})
|
|
||||||
.then((ids) => {
|
|
||||||
this._readUngroupedList(list).then(() => {
|
|
||||||
const x2ManysDef = this._fetchX2ManysBatched(list);
|
|
||||||
const referencesDef = this._fetchReferencesBatched(list);
|
|
||||||
return Promise.all([x2ManysDef, referencesDef]).then(
|
|
||||||
() => ids
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified implementation of '_applyChange' to allow changes
|
|
||||||
* without trigger onchanges
|
|
||||||
*
|
|
||||||
* @param {String} recordID
|
|
||||||
* @param {Object} changes
|
|
||||||
* @param {Object} options
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
_applyChangeOmitOnchange: function (recordID, changes, options) {
|
|
||||||
var record = this.localData[recordID];
|
|
||||||
var field = false;
|
|
||||||
var defs = [];
|
|
||||||
options = options || {};
|
|
||||||
record._changes = record._changes || {};
|
|
||||||
if (!options.doNotSetDirty) {
|
|
||||||
record._isDirty = true;
|
|
||||||
}
|
|
||||||
// Apply changes to local data
|
|
||||||
for (var fieldName in changes) {
|
|
||||||
field = record.fields[fieldName];
|
|
||||||
if (
|
|
||||||
field &&
|
|
||||||
(field.type === "one2many" || field.type === "many2many")
|
|
||||||
) {
|
|
||||||
defs.push(
|
|
||||||
this._applyX2ManyChange(
|
|
||||||
record,
|
|
||||||
fieldName,
|
|
||||||
changes[fieldName],
|
|
||||||
options
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
field &&
|
|
||||||
(field.type === "many2one" || field.type === "reference")
|
|
||||||
) {
|
|
||||||
defs.push(
|
|
||||||
this._applyX2OneChange(record, fieldName, changes[fieldName])
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
record._changes[fieldName] = changes[fieldName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.all(defs).then(() => _.keys(changes));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified implementation of '_performOnChange' to properly unfold the cloned
|
|
||||||
* record properties son we can interact with it as if we'd created it. We
|
|
||||||
* don't really do an onchange and the only fields that we're getting are those
|
|
||||||
* relational.
|
|
||||||
*
|
|
||||||
* @param {Object} record
|
|
||||||
* @param {String} [viewType] current viewType. If not set, we will assume
|
|
||||||
* main viewType from the record
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
_pseudoOnChange: function (record, viewType) {
|
|
||||||
var self = this;
|
|
||||||
var onchangeSpec = this._buildOnchangeSpecs(record, viewType);
|
|
||||||
if (!onchangeSpec) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
var idList = record.data.id ? [record.data.id] : [];
|
|
||||||
var options = {
|
|
||||||
full: true,
|
|
||||||
};
|
|
||||||
var context = this._getContext(record, options);
|
|
||||||
var currentData = this._generateOnChangeData(record, {changesOnly: false});
|
|
||||||
|
|
||||||
return self
|
|
||||||
._rpc({
|
|
||||||
model: record.model,
|
|
||||||
method: "onchange",
|
|
||||||
args: [idList, currentData, [], onchangeSpec],
|
|
||||||
context: context,
|
|
||||||
})
|
|
||||||
.then(function (result) {
|
|
||||||
if (!record._changes) {
|
|
||||||
// If the _changes key does not exist anymore, it means that
|
|
||||||
// it was removed by discarding the changes after the rpc
|
|
||||||
// to onchange. So, in that case, the proper response is to
|
|
||||||
// ignore the onchange.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (result.warning) {
|
|
||||||
self.trigger_up("warning", result.warning);
|
|
||||||
record._warning = true;
|
|
||||||
}
|
|
||||||
if (result.domain) {
|
|
||||||
record._domains = _.extend(record._domains, result.domain);
|
|
||||||
}
|
|
||||||
// We're only interested in relational fields
|
|
||||||
const values = _.pick(result.value, (v) => {
|
|
||||||
return typeof v === "object";
|
|
||||||
});
|
|
||||||
return self._applyOnChange(values, record).then(function () {
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_makeCloneRecord: function (modelName, params, values) {
|
|
||||||
const targetView = params.viewType;
|
|
||||||
let fields = params.fields;
|
|
||||||
const fieldsInfo = params.fieldsInfo;
|
|
||||||
|
|
||||||
// Get available fields
|
|
||||||
for (const view_type in params.views) {
|
|
||||||
fields = _.defaults({}, fields, params.views[view_type].fields);
|
|
||||||
}
|
|
||||||
let fieldNames = Object.keys(fields);
|
|
||||||
// Fields that are present in the originating view, that need to be initialized
|
|
||||||
// Hence preventing their value to crash when getting back to the originating view
|
|
||||||
const parentRecord =
|
|
||||||
params.parentID && this.localData[params.parentID].type === "list"
|
|
||||||
? this.localData[params.parentID]
|
|
||||||
: null;
|
|
||||||
if (parentRecord && parentRecord.viewType in parentRecord.fieldsInfo) {
|
|
||||||
const originView = parentRecord.viewType;
|
|
||||||
fieldNames = _.union(
|
|
||||||
fieldNames,
|
|
||||||
Object.keys(parentRecord.fieldsInfo[originView])
|
|
||||||
);
|
|
||||||
fieldsInfo[targetView] = _.defaults(
|
|
||||||
{},
|
|
||||||
fieldsInfo[targetView],
|
|
||||||
parentRecord.fieldsInfo[originView]
|
|
||||||
);
|
|
||||||
fields = _.defaults({}, fields, parentRecord.fields);
|
|
||||||
}
|
|
||||||
const record = this._makeDataPoint({
|
|
||||||
modelName: modelName,
|
|
||||||
fields: fields,
|
|
||||||
fieldsInfo: fieldsInfo,
|
|
||||||
context: params.context,
|
|
||||||
parentID: params.parentID,
|
|
||||||
res_ids: params.res_ids,
|
|
||||||
viewType: targetView,
|
|
||||||
});
|
|
||||||
// Extend dataPoint with clone info
|
|
||||||
record.clone_data = {
|
|
||||||
record_parent_id: params.clone_parent_record_id,
|
|
||||||
copy_data: params.clone_copy_data || values,
|
|
||||||
};
|
|
||||||
|
|
||||||
// We want to overwrite the default value of the handle field (if any),
|
|
||||||
// in order for new lines to be added at the correct position.
|
|
||||||
// -> This is a rare case where the defaul_get from the server
|
|
||||||
// will be ignored by the view for a certain field (usually "sequence").
|
|
||||||
|
|
||||||
var overrideDefaultFields = this._computeOverrideDefaultFields(
|
|
||||||
params.parentID,
|
|
||||||
params.position
|
|
||||||
);
|
|
||||||
|
|
||||||
if (overrideDefaultFields) {
|
|
||||||
values[overrideDefaultFields.field] = overrideDefaultFields.value;
|
|
||||||
}
|
|
||||||
const _this = this;
|
|
||||||
return (
|
|
||||||
this.applyDefaultValues(record.id, values, {fieldNames: fieldNames})
|
|
||||||
// This will ensure we refresh the proper properties
|
|
||||||
.then(() => {
|
|
||||||
var def = new Promise(function (resolve, reject) {
|
|
||||||
var always = function () {
|
|
||||||
if (record._warning) {
|
|
||||||
if (params.allowWarning) {
|
|
||||||
delete record._warning;
|
|
||||||
} else {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
_this
|
|
||||||
._pseudoOnChange(record, targetView)
|
|
||||||
.then(always)
|
|
||||||
.guardedCatch(always);
|
|
||||||
});
|
|
||||||
return def;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return this._fetchRelationalData(record);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return this._postprocess(record);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// Save initial changes, so they can be restored later,
|
|
||||||
// if we need to discard.
|
|
||||||
this.save(record.id, {savePoint: true});
|
|
||||||
return record.id;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the values formatted to clone
|
|
||||||
*
|
|
||||||
* @param {Object} line_state
|
|
||||||
* @param {Object} params
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
_getValuesToClone: function (line_state, params) {
|
|
||||||
const values_to_clone = {};
|
|
||||||
const line_data = line_state.data;
|
|
||||||
for (const field_name in line_data) {
|
|
||||||
if (field_name === "id") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const value = line_data[field_name];
|
|
||||||
const field_info = params.fields[field_name];
|
|
||||||
if (!field_info) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (field_info.type !== "boolean" && !value) {
|
|
||||||
values_to_clone[field_name] = value;
|
|
||||||
} else if (field_info.type === "many2one") {
|
|
||||||
const rec_id = value.data && value.data.id;
|
|
||||||
values_to_clone[field_name] = rec_id || false;
|
|
||||||
} else if (field_info.type === "many2many") {
|
|
||||||
values_to_clone[field_name] = [
|
|
||||||
[
|
|
||||||
6,
|
|
||||||
0,
|
|
||||||
_.map(value.data || [], (item) => {
|
|
||||||
return item.data.id;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
} else if (field_info.type === "one2many") {
|
|
||||||
values_to_clone[field_name] = _.map(value.data || [], (item) => {
|
|
||||||
return [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
this._getValuesToClone(item, {fields: value.fields}),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
field_info.type === "date" ||
|
|
||||||
field_info.type === "datetime"
|
|
||||||
) {
|
|
||||||
values_to_clone[field_name] = dateToServer(value);
|
|
||||||
} else {
|
|
||||||
values_to_clone[field_name] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return values_to_clone;
|
|
||||||
},
|
|
||||||
|
|
||||||
_generateChanges: function (record) {
|
|
||||||
let res = this._super.apply(this, arguments);
|
|
||||||
if (!_.isEmpty(record.clone_data)) {
|
|
||||||
// If a cloned record, ensure that all fields are written (and not only the view fields)
|
|
||||||
res = _.extend({}, record.clone_data.copy_data, res);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,163 +0,0 @@
|
||||||
/* Copyright 2021 Tecnativa - Alexandre Díaz
|
|
||||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
|
|
||||||
|
|
||||||
odoo.define(
|
|
||||||
"web_widget_one2many_tree_line_duplicate.One2manyTreeLineDuplicate",
|
|
||||||
function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const core = require("web.core");
|
|
||||||
const FieldOne2Many = require("web.relational_fields").FieldOne2Many;
|
|
||||||
const ListRenderer = require("web.ListRenderer");
|
|
||||||
|
|
||||||
const _t = core._t;
|
|
||||||
|
|
||||||
ListRenderer.include({
|
|
||||||
events: _.extend({}, ListRenderer.prototype.events, {
|
|
||||||
"click tr .o_list_record_clone": "_onCloneIconClick",
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
init: function (parent) {
|
|
||||||
this._super.apply(this, arguments);
|
|
||||||
let allow_clone =
|
|
||||||
parent.attrs &&
|
|
||||||
parent.attrs.options &&
|
|
||||||
parent.attrs.options.allow_clone;
|
|
||||||
allow_clone = typeof allow_clone === "undefined" ? false : allow_clone;
|
|
||||||
this.addCloneIcon =
|
|
||||||
allow_clone &&
|
|
||||||
!parent.isReadonly &&
|
|
||||||
parent.activeActions &&
|
|
||||||
parent.activeActions.create;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_renderHeader: function () {
|
|
||||||
var $thead = this._super.apply(this, arguments);
|
|
||||||
if (this.addCloneIcon) {
|
|
||||||
$thead
|
|
||||||
.find("tr")
|
|
||||||
.append($("<th>", {class: "o_list_record_clone_header"}));
|
|
||||||
}
|
|
||||||
return $thead;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_renderFooter: function () {
|
|
||||||
const $footer = this._super.apply(this, arguments);
|
|
||||||
if (this.addCloneIcon) {
|
|
||||||
$footer.find("tr").append($("<td>"));
|
|
||||||
}
|
|
||||||
return $footer;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject the icon for clone action
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_renderRow: function (record, index) {
|
|
||||||
const $row = this._super.apply(this, arguments);
|
|
||||||
if (this.addCloneIcon) {
|
|
||||||
const $icon = $("<button>", {
|
|
||||||
class: "fa fa-clone",
|
|
||||||
name: "clone",
|
|
||||||
"aria-label": _t("Clone row ") + (index + 1),
|
|
||||||
});
|
|
||||||
const $td = $("<td>", {class: "o_list_record_clone"}).append($icon);
|
|
||||||
$row.append($td);
|
|
||||||
}
|
|
||||||
return $row;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_getNumberOfCols: function () {
|
|
||||||
var n = this._super();
|
|
||||||
if (this.addCloneIcon) {
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger the clonation of the record
|
|
||||||
*
|
|
||||||
* @param {MouseEvent} ev
|
|
||||||
*/
|
|
||||||
_onCloneIconClick: function (ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
var $row = $(ev.target).closest("tr");
|
|
||||||
var id = $row.data("id");
|
|
||||||
this.unselectRow().then(() => {
|
|
||||||
this.trigger_up("clone_record", {
|
|
||||||
context: ev.currentTarget.dataset.context && [
|
|
||||||
ev.currentTarget.dataset.context,
|
|
||||||
],
|
|
||||||
id: id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
FieldOne2Many.include({
|
|
||||||
custom_events: _.extend({}, FieldOne2Many.prototype.custom_events, {
|
|
||||||
clone_record: "_onCloneRecord",
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {CustomEvent} ev
|
|
||||||
*/
|
|
||||||
_onCloneRecord: function (ev) {
|
|
||||||
const data = ev.data || {};
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (!this.cloningRecord && this.activeActions.create) {
|
|
||||||
this.cloningRecord = true;
|
|
||||||
this.trigger_up("edited_list", {id: this.value.id});
|
|
||||||
this._setValue(
|
|
||||||
{
|
|
||||||
operation: "CLONE", // This operation is a special case implemented only in this module
|
|
||||||
position: "bottom",
|
|
||||||
context: data.context,
|
|
||||||
id: ev.data.id,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
allowWarning: data.allowWarning,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.cloningRecord = false;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// This is necessary to propagate the changes
|
|
||||||
// to the main record
|
|
||||||
this._setValue({
|
|
||||||
operation: "TRIGGER_ONCHANGE",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
if (data.onSuccess) {
|
|
||||||
data.onSuccess();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.guardedCatch(() => {
|
|
||||||
this.cloningRecord = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
/* Copyright 2024 Tecnativa - Alexandre D. Díaz
|
||||||
|
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
||||||
|
|
||||||
|
import ListRenderer from "web.ListRenderer";
|
||||||
|
import core from "web.core";
|
||||||
|
|
||||||
|
var _t = core._t;
|
||||||
|
|
||||||
|
ListRenderer.include({
|
||||||
|
events: _.extend({}, ListRenderer.prototype.events, {
|
||||||
|
"click tr .o_list_record_clone": "_onCloneIconClick",
|
||||||
|
}),
|
||||||
|
|
||||||
|
init: function (parent, state, params) {
|
||||||
|
this._super(parent, state, params);
|
||||||
|
this.addCloneIcon =
|
||||||
|
parent &&
|
||||||
|
parent.attrs &&
|
||||||
|
parent.attrs.options &&
|
||||||
|
parent.attrs.options.allow_clone &&
|
||||||
|
!parent.isReadonly &&
|
||||||
|
parent.activeActions &&
|
||||||
|
parent.activeActions.create;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCloneIconClick: function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
var $row = $(event.target).closest("tr");
|
||||||
|
var id = $row.data("id");
|
||||||
|
var record = this.state.data.find(function (rec) {
|
||||||
|
return rec.id === id;
|
||||||
|
});
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.unselectRow().then(function () {
|
||||||
|
self._rpc({
|
||||||
|
model: record.model,
|
||||||
|
method: "copy_data",
|
||||||
|
args: [record.res_id],
|
||||||
|
}).then(function (dict) {
|
||||||
|
var newContext = {};
|
||||||
|
for (var key in dict) {
|
||||||
|
if (dict.hasOwnProperty(key)) {
|
||||||
|
newContext["default_" + key] = dict[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.trigger_up("add_record", {
|
||||||
|
context: [newContext],
|
||||||
|
forceEditable: "bottom",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getNumberOfCols: function () {
|
||||||
|
var n = this._super();
|
||||||
|
if (this.addCloneIcon) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderHeader: function () {
|
||||||
|
var $thead = this._super.apply(this, arguments);
|
||||||
|
if (this.addCloneIcon) {
|
||||||
|
$thead.find("tr").append($("<th>", {class: "o_list_record_clone"}));
|
||||||
|
}
|
||||||
|
return $thead;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderFooter: function () {
|
||||||
|
const $footer = this._super.apply(this, arguments);
|
||||||
|
if (this.addCloneIcon) {
|
||||||
|
$footer.find("tr").append($("<td>"));
|
||||||
|
}
|
||||||
|
return $footer;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderRow: function (record, index) {
|
||||||
|
var $row = this._super.apply(this, arguments);
|
||||||
|
if (this.addCloneIcon) {
|
||||||
|
const isDisabled = !record.res_id;
|
||||||
|
const $icon = $("<button>", {
|
||||||
|
class: "fa fa-copy",
|
||||||
|
name: "clone",
|
||||||
|
"aria-label": _t("Clone row ") + (index + 1),
|
||||||
|
disabled: isDisabled,
|
||||||
|
});
|
||||||
|
const $td = $("<td>", {class: "o_list_record_clone"}).append($icon);
|
||||||
|
const $deleteIconTd = $row.find(".o_list_record_remove");
|
||||||
|
if ($deleteIconTd.length > 0) {
|
||||||
|
$deleteIconTd.before($td);
|
||||||
|
} else {
|
||||||
|
$row.append($td);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $row;
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<odoo>
|
|
||||||
<template id="assets_backend" inherit_id="web.assets_backend">
|
|
||||||
<xpath expr="." position="inside">
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="/web_widget_one2many_tree_line_duplicate/static/src/scss/one2many_tree_line_duplicate.scss"
|
|
||||||
/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//script[1]" position="before">
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="/web_widget_one2many_tree_line_duplicate/static/src/js/basic_model.js"
|
|
||||||
/>
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="/web_widget_one2many_tree_line_duplicate/static/src/js/one2many_tree_line_duplicate.js"
|
|
||||||
/>
|
|
||||||
</xpath>
|
|
||||||
</template>
|
|
||||||
</odoo>
|
|
Loading…
Reference in New Issue