3
0
Fork 0

[MIG] web_widget_one2many_tree_line_duplicate: Migration to 15.0

15.0-ocabot-merge-pr-2789-by-pedrobaeza-bump-patch
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 !!
!! 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.

View File

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

View File

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

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>