Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ addon | version | maintainers | summary
[web_tree_many2one_clickable](web_tree_many2one_clickable/) | 19.0.1.0.0 | | Open the linked resource when clicking on their name
[web_widget_bokeh_chart](web_widget_bokeh_chart/) | 19.0.1.0.1 | <a href='https://github.com/LoisRForgeFlow'><img src='https://github.com/LoisRForgeFlow.png' width='32' height='32' style='border-radius:50%;' alt='LoisRForgeFlow'/></a> <a href='https://github.com/JasminSForgeFlow'><img src='https://github.com/JasminSForgeFlow.png' width='32' height='32' style='border-radius:50%;' alt='JasminSForgeFlow'/></a> | This widget allows to display charts using Bokeh library.
[web_widget_numeric_step](web_widget_numeric_step/) | 19.0.1.0.0 | <a href='https://github.com/rafaelbn'><img src='https://github.com/rafaelbn.png' width='32' height='32' style='border-radius:50%;' alt='rafaelbn'/></a> <a href='https://github.com/yajo'><img src='https://github.com/yajo.png' width='32' height='32' style='border-radius:50%;' alt='yajo'/></a> | Web Widget Numeric Step
[web_widget_one2many_tree_line_duplicate](web_widget_one2many_tree_line_duplicate/) | 19.0.1.0.0 | | Web Widget One2many Tree Line Duplicate
[web_widget_one2many_tree_line_duplicate](web_widget_one2many_tree_line_duplicate/) | 19.0.1.0.1 | | Web Widget One2many Tree Line Duplicate
[web_widget_product_label_section_and_note_full_label](web_widget_product_label_section_and_note_full_label/) | 19.0.1.0.0 | | Display the full label in the product_label_section_and_note widget.
[web_widget_product_label_section_and_note_name_visibility](web_widget_product_label_section_and_note_name_visibility/) | 19.0.1.0.0 | <a href='https://github.com/carlos-lopez-tecnativa'><img src='https://github.com/carlos-lopez-tecnativa.png' width='32' height='32' style='border-radius:50%;' alt='carlos-lopez-tecnativa'/></a> | Alternate the visibility of the product and description.
[web_widget_section_and_note_text_scrollable](web_widget_section_and_note_text_scrollable/) | 19.0.1.0.0 | <a href='https://github.com/ivantodorovich'><img src='https://github.com/ivantodorovich.png' width='32' height='32' style='border-radius:50%;' alt='ivantodorovich'/></a> | Make the text field of Section and Note widget scrollable
Expand Down
2 changes: 1 addition & 1 deletion web_widget_one2many_tree_line_duplicate/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Web Widget One2many Tree Line Duplicate
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e5b95135142c797c5a7addebf73d134f9acbec555ff441d0c2baf718accd04f0
!! source digest: sha256:3b891d75bd44e3d0168c0baf1a0dc436298293a61dc316fe13c5f60afb0b3ff4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down
2 changes: 1 addition & 1 deletion web_widget_one2many_tree_line_duplicate/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"name": "Web Widget One2many Tree Line Duplicate",
"category": "web",
"version": "19.0.1.0.0",
"version": "19.0.1.0.1",
"author": "Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/web",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ <h1>Web Widget One2many Tree Line Duplicate</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e5b95135142c797c5a7addebf73d134f9acbec555ff441d0c2baf718accd04f0
!! source digest: sha256:3b891d75bd44e3d0168c0baf1a0dc436298293a61dc316fe13c5f60afb0b3ff4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/19.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-19-0/web-19-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=19.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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ import {ListRenderer} from "@web/views/list/list_renderer";
import {patch} from "@web/core/utils/patch";
import {exprToBoolean} from "@web/core/utils/strings";
import {browser} from "@web/core/browser/browser";
import {x2ManyCommands} from "@web/core/orm_service";

const SCALAR_TYPES = [
"integer",
"float",
"monetary",
"char",
"text",
"boolean",
"selection",
"date",
"datetime",
];
const MANY2ONE_TYPES = ["many2one", "many2one_reference", "reference"];

patch(ListRenderer.prototype, {
setup() {
Expand Down Expand Up @@ -47,40 +61,146 @@ patch(ListRenderer.prototype, {
}
return nbCols;
},
async onCloneIconClick(record) {
const list = this.props.list;
const left = await list.leaveEditMode();
if (!left) {
return;
}
// Snapshot of the source's scalar values, including the readonly computed
// ones (subtotals...), so the copy is exact. duplicateRecords creates the
// line running its onchanges, which recompute editable fields (e.g.
// price_unit from the product) and discard the manual values. We re-apply
// this snapshot with withoutOnchange so those values are kept.
const scalarTypes = [
"integer",
"float",
"monetary",
"char",
"text",
"boolean",
"selection",
"date",
"datetime",
];
/**
* Snapshot of the record's values, including the readonly computed ones
* (subtotals...), so the copy is exact. duplicateRecords creates the line
* running its onchanges, which recompute editable fields (e.g. price_unit
* from the product) and discard the manual values. We re-apply this
* snapshot with withoutOnchange so those values are kept.
*
* @param {Record} record record to copy the values from
* @param {StaticList} list list the record belongs to
* @returns {Object} changes to apply on the copy
*/
getDuplicateSnapshot(record, list) {
const snapshot = {};
for (const [name, value] of Object.entries(record.data)) {
const field = record.fields[name];
if (
field &&
name !== "display_name" &&
name !== list.handleField &&
scalarTypes.includes(field.type)
!field ||
name === "display_name" ||
name === list.handleField ||
name === list.config.relationField
) {
continue;
}
if (SCALAR_TYPES.includes(field.type)) {
snapshot[name] = value;
} else if (MANY2ONE_TYPES.includes(field.type)) {
// Always invisible many2ones are read as a plain id (see
// getFieldsSpec), so we wrap it and let the model complete the
// value (display_name...) when applying the change. The rest are
// copied as a new object, so both records don't share it.
snapshot[name] =
typeof value === "number"
? {id: value}
: value && Object.assign({}, value);
} else if (field.type === "many2many") {
// SET (and not LINK) so the copy ends up with exactly the same
// records as the source: this way we also drop the ones the
// onchange added on its own.
snapshot[name] = [x2ManyCommands.set([...value.currentIds])];
}
// One2many fields can't be set through a snapshot: they are copied
// sub-record by sub-record in duplicateOne2manyValues().
}
return snapshot;
},
/**
* Fetches the values of the sub-records of a one2many that is always
* invisible in the list: those are read with their ids only (see
* getFieldsSpec), so their datapoints are empty and copying them as they are
* would end up creating empty (and usually invalid) records.
*
* @param {Record} record source record
* @param {String} fieldName name of the one2many field
*/
async loadOne2manyValues(record, fieldName) {
const invisible = record.activeFields[fieldName].invisible;
if (invisible !== "True" && invisible !== "1") {
return;
}
const list = record.data[fieldName];
const lines = list.records.filter((line) => line.resId);
if (!lines.length) {
return;
}
const values = await list.model._loadRecords(
{...list.config, resIds: lines.map((line) => line.resId)},
list.evalContext
);
for (const line of lines) {
const lineValues = values.find((vals) => vals.id === line.resId);
if (lineValues) {
line._applyValues(lineValues);
}
}
},
/**
* Recreates on the copy the sub-records of every one2many field of the
* source. They are the only values the snapshot can't carry, as each
* sub-record has to be created (and filled) on its own. The lines the
* onchanges may have generated on the copy are dropped first, so both
* records end up with exactly the same content.
*
* @param {Record} record source record
* @param {Record} newRecord copy of the source record
*/
async duplicateOne2manyValues(record, newRecord) {
for (const [name, sourceList] of Object.entries(record.data)) {
const field = record.fields[name];
if (!field || field.type !== "one2many" || !(name in newRecord.data)) {
continue;
}
const targetList = newRecord.data[name];
if (
!Object.keys(sourceList.activeFields).length ||
!Object.keys(targetList.activeFields).length
) {
// The sub-records have no fields to copy (the field has no
// sub-view here).
continue;
}
await this.loadOne2manyValues(record, name);
if (targetList.records.length) {
// DELETE for the lines the onchange created on the fly, UNLINK
// for the existing ones it linked: this way we only discard the
// pending commands, without touching any stored record.
await targetList._applyCommands(
targetList.records.map((line) =>
line.resId
? [x2ManyCommands.UNLINK, line.resId]
: [x2ManyCommands.DELETE, line._virtualId]
)
);
}
for (const line of sourceList.records) {
const newLine = await targetList._createNewRecordDatapoint({
mode: "readonly",
});
const snapshot = this.getDuplicateSnapshot(line, targetList);
if (Object.keys(snapshot).length) {
await newLine._update(snapshot, {
withoutOnchange: true,
withoutParentUpdate: true,
});
}
// Nested one2manys are copied the same way, recursively.
await this.duplicateOne2manyValues(line, newLine);
// Added at the end, like the core does when duplicating: the
// record is filled before being part of the list, so the
// intermediate values are never rendered.
await targetList._addRecord(newLine, {position: "bottom"});
}
}
},
async onCloneIconClick(record) {
const list = this.props.list;
const left = await list.leaveEditMode();
if (!left) {
return;
}
const snapshot = this.getDuplicateSnapshot(record, list);
// Run everything in a single model transaction and notify only once, at
// the end, so the intermediate (onchange-recomputed) values are never
// rendered: the line appears directly with the original values, without
Expand All @@ -91,15 +211,14 @@ patch(ListRenderer.prototype, {
// The duplicate is inserted right after the source line; identify it
// by position (references aren't stable across the re-wrapping).
const newRecord = list.records[sourceIndex + 1];
if (
newRecord &&
newRecord.id !== record.id &&
Object.keys(snapshot).length
) {
await newRecord._update(snapshot, {
withoutOnchange: true,
withoutParentUpdate: true,
});
if (newRecord && newRecord.id !== record.id) {
if (Object.keys(snapshot).length) {
await newRecord._update(snapshot, {
withoutOnchange: true,
withoutParentUpdate: true,
});
}
await this.duplicateOne2manyValues(record, newRecord);
}
await list._onUpdate();
});
Expand Down