[MIG] web_widget_one2many_tree_line_duplicate: Migration to 15.0

pull/2844/head
ThiagoMForgeFlow 2024-03-06 10:46:56 +01:00
parent 9857c82c94
commit 3e62bb4deb
8 changed files with 118 additions and 616 deletions

View File

@ -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.

View File

@ -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",
],
},
} }

View File

@ -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&amp;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&amp;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>

View File

@ -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;
},
});
});

View File

@ -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;
});
}
},
});
}
);

View File

@ -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;
},
});

View File

@ -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>