mirror of https://github.com/OCA/web.git
[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 !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:9cd7a43eab907d7d3ed3380c20cda1be40d288c01feb25dd54108e69cb350314
|
||||
!! source digest: sha256:620c16e6d6bd0ad72a9d7eb3c8f243fbbf721898252fea9c26d70b2e0a7822d7
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |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
|
||||
:alt: License: AGPL-3
|
||||
.. |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
|
||||
.. |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
|
||||
.. |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
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
@ -61,7 +61,7 @@ Bug Tracker
|
|||
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.
|
||||
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.
|
||||
|
||||
|
@ -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
|
||||
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.
|
||||
|
|
|
@ -4,12 +4,17 @@
|
|||
{
|
||||
"name": "Web Widget One2many Tree Line Duplicate",
|
||||
"category": "web",
|
||||
"version": "13.0.1.0.2",
|
||||
"version": "15.0.1.0.0",
|
||||
"author": "Tecnativa, Odoo Community Association (OCA)",
|
||||
"license": "AGPL-3",
|
||||
"website": "https://github.com/OCA/web",
|
||||
"depends": ["web"],
|
||||
"data": ["view/assets.xml"],
|
||||
"auto_install": False,
|
||||
"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 !!
|
||||
!! 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><strong>Table of contents</strong></p>
|
||||
<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>.
|
||||
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
|
||||
<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>
|
||||
</div>
|
||||
<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
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
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>
|
||||
</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