diff --git a/addons/stock/report/report_stock_reception.py b/addons/stock/report/report_stock_reception.py
index 1bd43b93a19..a1247230afc 100644
--- a/addons/stock/report/report_stock_reception.py
+++ b/addons/stock/report/report_stock_reception.py
@@ -11,6 +11,16 @@ class ReceptionReport(models.AbstractModel):
_name = 'report.stock.report_reception'
_description = "Stock Reception Report"
+ @api.model
+ def get_report_data(self, docids, data):
+ report_values = self._get_report_values(docids, data)
+ report_values['docs'] = self._format_html_docs(report_values.get('docs', False))
+ report_values['sources_info'] = self._format_html_sources_info(report_values.get('sources_to_lines', {}))
+ report_values['sources_to_lines'] = self._format_html_sources_to_lines(report_values.get('sources_to_lines', {}))
+ report_values['sources_to_formatted_scheduled_date'] = self._format_html_sources_to_date(report_values.get('sources_to_formatted_scheduled_date', {}))
+ report_values['show_uom'] = self.env.user.has_group('uom.group_uom')
+ return report_values
+
@api.model
def _get_report_values(self, docids, data=None):
''' This report is flexibly designed to work with both individual and batch pickings.
@@ -325,3 +335,42 @@ class ReceptionReport(models.AbstractModel):
def _action_unassign(self, in_move, out_move):
""" For extension purposes only """
return
+
+ def _format_html_docs(self, docs):
+ """ Format docs to be sent in an html request. """
+ return [{
+ 'id': doc.id,
+ 'name': doc.display_name,
+ 'state': doc.state,
+ 'display_state': dict(doc._fields['state']._description_selection(self.env)).get(doc.state),
+ } for doc in docs] if docs else docs
+
+ def _format_html_sources_to_date(self, sources_to_dates):
+ """ Format sources_to_formatted_scheduled_date to be sent in an html request. """
+ return {str(source): date for (source, date) in sources_to_dates.items()}
+
+ def _format_html_sources_to_lines(self, sources_to_lines):
+ """ Format sources_to_lines to be sent in an html request, while adding an index for OWL's t-foreach. """
+ return {
+ str(source): [{**line, 'index': i, 'move_out_id': line['move_out'].id} for i, line in enumerate(lines)]
+ for source, lines in sources_to_lines.items()
+ }
+
+ def _format_html_sources_info(self, sources_to_lines):
+ """ Format used info from sources of sources_to_lines to be sent in an html request. """
+ return {str(source): [self._format_html_source(s, index == 0) for index, s in enumerate(source)] for source in sources_to_lines.keys()}
+
+ def _format_html_source(self, source, is_picking=False):
+ """ Format used info from a single source to be sent in an html request. """
+ formatted = {
+ 'id': source.id,
+ 'model': source._name,
+ 'name': source.display_name,
+ }
+ if is_picking:
+ formatted.update({
+ 'priority': source.priority,
+ 'partner_id': source.partner_id.id if source.partner_id else False,
+ 'partner_name': source.partner_id.name if source.partner_id else False,
+ })
+ return formatted
diff --git a/addons/stock/report/report_stock_reception.xml b/addons/stock/report/report_stock_reception.xml
index da29ac41954..700c3ec3fb5 100644
--- a/addons/stock/report/report_stock_reception.xml
+++ b/addons/stock/report/report_stock_reception.xml
@@ -59,9 +59,9 @@
-
+
-
+
@@ -78,71 +78,19 @@
Expected Delivery:
-
-
- Assign All
-
-
-
-
- Print Labels
-
-
-
-
+
-
-
-
-
-
- Assign
-
-
- Unassign
-
-
-
-
- Print Label
-
-
@@ -151,16 +99,13 @@
No allocation need found.
-
+
-
-
-
-
+
diff --git a/addons/stock/static/src/components/reception_report_line/stock_reception_report_line.js b/addons/stock/static/src/components/reception_report_line/stock_reception_report_line.js
new file mode 100644
index 00000000000..9581b800044
--- /dev/null
+++ b/addons/stock/static/src/components/reception_report_line/stock_reception_report_line.js
@@ -0,0 +1,75 @@
+/** @odoo-module **/
+import { useService } from "@web/core/utils/hooks";
+import { formatFloat } from "@web/views/fields/formatters";
+
+const { Component } = owl;
+
+export class ReceptionReportLine extends Component {
+ setup() {
+ this.ormService = useService("orm");
+ this.actionService = useService("action");
+ this.formatFloat = (val) => formatFloat(val, { digits: [false, this.props.precision] });
+ }
+
+ //---- Handlers ----
+
+ async onClickForecast() {
+ const action = await this.ormService.call(
+ "stock.move",
+ "action_product_forecast_report",
+ [[this.data.move_out_id]],
+ );
+
+ return this.actionService.doAction(action);
+ }
+
+ async onClickPrint() {
+ if (!this.data.move_out_id) {
+ return;
+ }
+ const reportFile = 'stock.report_reception_report_label';
+ const modelIds = [this.data.move_out_id];
+ const productQtys = [Math.ceil(this.data.quantity) || '1'];
+
+ return this.actionService.doAction({
+ type: "ir.actions.report",
+ report_type: "qweb-pdf",
+ report_name: `${reportFile}?docids=${modelIds}&quantity=${productQtys}`,
+ report_file: reportFile,
+ });
+ }
+
+ async onClickAssign() {
+ await this.ormService.call(
+ "report.stock.report_reception",
+ "action_assign",
+ [false, [this.data.move_out_id], [this.data.quantity], [this.data.move_ins]],
+ );
+ this.env.bus.trigger("update-assign-state", { isAssigned: true, tableIndex: this.props.parentIndex, lineIndex: this.data.index });
+ }
+
+ async onClickUnassign() {
+ const done = await this.ormService.call(
+ "report.stock.report_reception",
+ "action_unassign",
+ [false, this.data.move_out_id, this.data.quantity, this.data.move_ins]
+ )
+ if (done) {
+ this.env.bus.trigger("update-assign-state", { isAssigned: false, tableIndex: this.props.parentIndex, lineIndex: this.data.index });
+ }
+ }
+
+ //---- Getters ----
+
+ get data() {
+ return this.props.data;
+ }
+}
+
+ReceptionReportLine.template = "stock.ReceptionReportLine";
+ReceptionReportLine.props = {
+ data: Object,
+ parentIndex: String,
+ showUom: Boolean,
+ precision: Number,
+};
diff --git a/addons/stock/static/src/components/reception_report_line/stock_reception_report_line.xml b/addons/stock/static/src/components/reception_report_line/stock_reception_report_line.xml
new file mode 100644
index 00000000000..0e3b7257259
--- /dev/null
+++ b/addons/stock/static/src/components/reception_report_line/stock_reception_report_line.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+ Assign
+
+
+ Unassign
+
+
+
+
+ Print Label
+
+
+
+
+
diff --git a/addons/stock/static/src/components/reception_report_main/stock_reception_report_main.js b/addons/stock/static/src/components/reception_report_main/stock_reception_report_main.js
new file mode 100644
index 00000000000..6d3edf26927
--- /dev/null
+++ b/addons/stock/static/src/components/reception_report_main/stock_reception_report_main.js
@@ -0,0 +1,150 @@
+/** @odoo-module **/
+
+import { registry } from "@web/core/registry";
+import { useBus, useService } from "@web/core/utils/hooks";
+import { ControlPanel } from "@web/search/control_panel/control_panel";
+import { ReceptionReportTable } from "../reception_report_table/stock_reception_report_table";
+
+const { Component, onWillStart, useState } = owl;
+
+export class ReceptionReportMain extends Component {
+ setup() {
+ this.controlPanelDisplay = {
+ "top-right": false,
+ "bottom-right": false,
+ };
+ this.ormService = useService("orm");
+ this.actionService = useService("action");
+ this.reportName = "stock.report_reception";
+ this.state = useState({
+ sourcesToLines: {},
+ });
+ useBus(this.env.bus, "update-assign-state", (ev) => this._changeAssignedState(ev.detail));
+
+ onWillStart(async () => {
+ this.data = await this.getReportData();
+ this.state.sourcesToLines = this.data.sources_to_lines;
+ });
+ }
+
+ async getReportData() {
+ const args = [
+ this.context.default_picking_ids,
+ { context: this.context, report_type: "html" },
+ ];
+ return this.ormService.call(
+ "report.stock.report_reception",
+ "get_report_data",
+ args,
+ { context: this.context }
+ );
+ }
+
+ //---- Handlers ----
+
+ async onClickAssignAll() {
+ const moveIds = [];
+ const quantities = [];
+ const inIds = [];
+
+ for (const lines of Object.values(this.state.sourcesToLines)) {
+ for (const line of lines) {
+ if (line.is_assigned) continue;
+ moveIds.push(line.move_out_id);
+ quantities.push(line.quantity);
+ inIds.push(line.move_ins);
+ }
+ }
+
+ await this.ormService.call(
+ "report.stock.report_reception",
+ "action_assign",
+ [false, moveIds, quantities, inIds],
+ );
+ this._changeAssignedState({ isAssigned: true });
+ }
+
+ async onClickTitle(docId) {
+ return this.actionService.doAction({
+ type: "ir.actions.act_window",
+ res_model: this.data.doc_model,
+ res_id: docId,
+ views: [[false, "form"]],
+ target: "current",
+ });
+ }
+
+ onClickPrint() {
+ return this.actionService.doAction({
+ type: "ir.actions.report",
+ report_type: "qweb-pdf",
+ report_name: `${this.reportName}/${this.context.default_picking_ids.join(",")}`,
+ report_file: this.reportName,
+ });
+ }
+
+ onClickPrintLabels() {
+ const reportFile = 'stock.report_reception_report_label';
+ const modelIds = [];
+ const quantities = [];
+
+ for (const lines of Object.values(this.state.sourcesToLines)) {
+ for (const line of lines) {
+ if (!line.is_assigned) continue;
+ modelIds.push(line.move_out_id);
+ quantities.push(line.quantity || 1);
+ }
+ }
+ if (!modelIds.length) {
+ return;
+ }
+
+ return this.actionService.doAction({
+ type: "ir.actions.report",
+ report_type: "qweb-pdf",
+ report_name: `${reportFile}?docids=${modelIds}&quantity=${quantities}`,
+ report_file: reportFile,
+ });
+ }
+
+ //---- Utils ----
+
+ _changeAssignedState(options) {
+ const { isAssigned, tableIndex, lineIndex } = options;
+
+ for (const [tabIndex, lines] of Object.entries(this.state.sourcesToLines)) {
+ if (tableIndex && tableIndex != tabIndex) continue;
+ lines.forEach(line => {
+ if (isNaN(lineIndex) || lineIndex == line.index) {
+ line.is_assigned = isAssigned;
+ }
+ });
+ }
+ }
+
+ //---- Getters ----
+
+ get context() {
+ return this.props.action.context;
+ }
+
+ get hasContent() {
+ return this.data.sources_to_lines && Object.keys(this.data.sources_to_lines).length > 0;
+ }
+
+ get isAssignAllDisabled() {
+ return Object.values(this.state.sourcesToLines).every(lines => lines.every(line => line.is_assigned));
+ }
+
+ get isPrintLabelDisabled() {
+ return Object.values(this.state.sourcesToLines).every(lines => lines.every(line => !line.is_assigned));
+ }
+}
+
+ReceptionReportMain.components = {
+ ControlPanel,
+ ReceptionReportTable,
+};
+ReceptionReportMain.template = "stock.ReceptionReportMain";
+
+registry.category("actions").add("reception_report", ReceptionReportMain);
diff --git a/addons/stock/static/src/components/reception_report_main/stock_reception_report_main.xml b/addons/stock/static/src/components/reception_report_main/stock_reception_report_main.xml
new file mode 100644
index 00000000000..e9c042982cd
--- /dev/null
+++ b/addons/stock/static/src/components/reception_report_main/stock_reception_report_main.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No allocation need found.
+
+
+
+
+
diff --git a/addons/stock/static/src/components/reception_report_table/stock_reception_report_table.js b/addons/stock/static/src/components/reception_report_table/stock_reception_report_table.js
new file mode 100644
index 00000000000..a67df566cbb
--- /dev/null
+++ b/addons/stock/static/src/components/reception_report_table/stock_reception_report_table.js
@@ -0,0 +1,96 @@
+/** @odoo-module **/
+
+import { useService } from "@web/core/utils/hooks";
+import { ReceptionReportLine } from "../reception_report_line/stock_reception_report_line";
+
+const { Component } = owl;
+
+export class ReceptionReportTable extends Component {
+ setup() {
+ this.actionService = useService("action");
+ this.ormService = useService("orm");
+ }
+
+ //---- Handlers ----
+
+ async onClickAssignAll() {
+ const moveIds = [];
+ const quantities = [];
+ const inIds = [];
+ for (const line of this.props.lines) {
+ if (line.is_assigned) continue;
+ moveIds.push(line.move_out_id);
+ quantities.push(line.quantity);
+ inIds.push(line.move_ins);
+ }
+
+ await this.ormService.call(
+ "report.stock.report_reception",
+ "action_assign",
+ [false, moveIds, quantities, inIds],
+ );
+ this.env.bus.trigger("update-assign-state", { isAssigned: true, tableIndex: this.props.index });
+ }
+
+ async onClickLink(resModel, resId, viewType) {
+ return this.actionService.doAction({
+ type: "ir.actions.act_window",
+ res_model: resModel,
+ res_id: resId,
+ views: [[false, viewType]],
+ target: "current",
+ });
+ }
+
+ async onClickPrintLabels() {
+ const reportFile = 'stock.report_reception_report_label';
+ const modelIds = [];
+ const quantities = [];
+ for (const line of this.props.lines) {
+ if (!line.is_assigned) continue;
+ modelIds.push(line.move_out_id);
+ quantities.push(line.quantity || 1);
+ }
+ if (!modelIds.length) {
+ return;
+ }
+
+ return this.actionService.doAction({
+ type: "ir.actions.report",
+ report_type: "qweb-pdf",
+ report_name: `${reportFile}?docids=${modelIds}&quantity=${quantities}`,
+ report_file: reportFile,
+ });
+ }
+
+ //---- Getters ----
+
+ get hasMovesIn() {
+ return this.props.lines.some(line => line.move_ins && line.move_ins.length > 0);
+ }
+
+ get hasAssignAllButton() {
+ return this.props.lines.some(line => line.is_qty_assignable);
+ }
+
+ get isAssignAllDisabled() {
+ return this.props.lines.every(line => line.is_assigned);
+ }
+
+ get isPrintLabelDisabled() {
+ return this.props.lines.every(line => !line.is_assigned);
+ }
+}
+
+ReceptionReportTable.template = "stock.ReceptionReportTable";
+ReceptionReportTable.components = {
+ ReceptionReportLine,
+};
+ReceptionReportTable.props = {
+ index: String,
+ scheduledDate: { type: String, optional: true },
+ lines: Array,
+ source: Array,
+ showUom: Boolean,
+ precision: Number,
+};
diff --git a/addons/stock/static/src/components/reception_report_table/stock_reception_report_table.xml b/addons/stock/static/src/components/reception_report_table/stock_reception_report_table.xml
new file mode 100644
index 00000000000..2146aa7502a
--- /dev/null
+++ b/addons/stock/static/src/components/reception_report_table/stock_reception_report_table.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+ ( )
+
+ :
+
+
+
+ Expected Delivery:
+
+
+ Assign All
+
+
+
+
+ Print Labels
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/stock/static/src/js/report_stock_reception.js b/addons/stock/static/src/js/report_stock_reception.js
deleted file mode 100644
index 877e733072e..00000000000
--- a/addons/stock/static/src/js/report_stock_reception.js
+++ /dev/null
@@ -1,202 +0,0 @@
-/** @odoo-module **/
-
-import clientAction from 'report.client_action';
-import core from 'web.core';
-
-const qweb = core.qweb;
-const _t = core._t;
-
-const ReceptionReport = clientAction.extend({
- /**
- * @override
- */
- init: function (parent, action, options) {
- this._super(...arguments);
- this.context = Object.assign(action.context || {}, {
- active_ids: action.context.default_picking_ids,
- });
- this.report_name = `stock.report_reception`;
- this.report_url = `/report/html/${this.report_name}/?context=${JSON.stringify(this.context)}`;
- this._title = action.name;
- },
-
- /**
- * @override
- */
- start: function () {
- return Promise.all([
- this._super(...arguments),
- ]).then(() => {
- this._renderButtons();
- });
- },
-
- /**
- * @override
- */
- on_attach_callback: function () {
- this._super();
- this.iframe.addEventListener("load",
- () => this._bindAdditionalActionHandlers(),
- { once: true }
- );
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Renders extra report buttons in control panel
- */
- _renderButtons: function () {
- this.$buttons.append(qweb.render('reception_report_buttons', {}));
- this.$buttons.on('click', '.o_report_reception_assign', this._onClickAssign.bind(this));
- this.$buttons.on('click', '.o_print_label', this._onClickPrintLabel.bind(this));
- this.controlPanelProps.cp_content = {
- $buttons: this.$buttons,
- };
- },
-
- /**
- * Bind additional action handlers
- */
- _bindAdditionalActionHandlers: function () {
- const rr = $(this.iframe).contents().find('.o_report_reception');
- rr.on('click', '.o_report_reception_assign', this._onClickAssign.bind(this));
- rr.on('click', '.o_report_reception_unassign', this._onClickUnassign.bind(this));
- rr.on('click', '.o_report_reception_forecasted', this._onClickForecastReport.bind(this));
- rr.on('click', '.o_print_label', this._onClickPrintLabel.bind(this));
- },
-
-
- _switchButton: function (button) {
- button.innerText = button.innerText.includes(_t("Unassign")) ? _t("Assign") : _t("Unassign");
- button.name = button.name === 'assign_link' ? 'unassign_link' : 'assign_link';
- button.classList.toggle("o_report_reception_assign");
- button.classList.toggle("o_report_reception_unassign");
- },
-
- /**
- * Assign the specified move(s)
- *
- * @returns {Promise}
- */
- _onClickAssign: function (ev) {
- const el = ev.currentTarget;
- const quantities = []; // incoming qty amounts to assign
- const moveIds = [];
- const inIds = [];
- let nodeToAssign = [];
- if (el.name === 'assign_link') { // One line "Assign"
- nodeToAssign = [el];
- el.closest('tbody').previousElementSibling.querySelectorAll('.o_print_label_all').forEach(button => button.removeAttribute('disabled'));
- } else {
- el.style.display = 'none';
- if (el.name === "assign_all_link") { // Global "Assign All"
- const iframe = this.iframe.contentDocument;
- iframe.querySelectorAll('.o_assign_all').forEach(button => button.style.display = 'none');
- iframe.querySelectorAll('.o_print_label_all').forEach(button => button.removeAttribute('disabled'));
- nodeToAssign = iframe.querySelectorAll('.o_report_reception_assign:not(.o_assign_all)');
- } else { // Local assign all
- nodeToAssign = el.closest('thead').nextElementSibling.querySelectorAll('.o_report_reception_assign:not(.o_assign_all)');
- const thead = el.closest('thead');
- thead.querySelector('.o_print_label_all').removeAttribute('disabled');
- thead.nextElementSibling.querySelectorAll('.o_print_label_all').forEach(button => button.removeAttribute('disabled'));
- }
- }
- nodeToAssign.forEach(node => {
- node.closest('td').nextElementSibling.querySelectorAll('.o_print_label').forEach(button => button.removeAttribute('disabled'));
- moveIds.push(parseInt(node.getAttribute('move-id')));
- quantities.push(parseFloat(node.getAttribute('qty')));
- inIds.push(JSON.parse(node.getAttribute('move-ins-ids')));
- this._switchButton(node);
- });
-
- return this._rpc({
- model: 'report.stock.report_reception',
- args: [false, moveIds, quantities, inIds],
- method: 'action_assign'
- });
- },
-
- /**
- * Unassign the specified move
- *
- * @returns {Promise}
- */
- _onClickUnassign: function (ev) {
- const el = ev.currentTarget;
- const quantity = parseFloat(el.getAttribute('qty'));
- const modelId = parseInt(el.getAttribute('move-id'));
- const inIds = JSON.parse("[" + el.getAttribute('move-ins-ids') + "]");
- return this._rpc({
- model: 'report.stock.report_reception',
- args: [false, modelId, quantity, inIds[0]],
- method: 'action_unassign'
- }).then(() => {
- // only switch buttons if successful
- this._switchButton(el);
- el.closest('td').nextElementSibling.querySelectorAll('.o_print_label').forEach(button => button.setAttribute('disabled', true));
- });
- },
-
- /**
- * Open the forecast report for the product of the selected move.
- *
- * @returns {Promise}
- */
- _onClickForecastReport: function (ev) {
- const modelId = parseInt(ev.currentTarget.getAttribute('move-id'));
- return this._rpc({
- model: 'stock.move',
- args: [[modelId]],
- method: 'action_product_forecast_report'
- }).then((action) => {
- return this.do_action(action);
- });
- },
-
- /**
- * Print the corresponding source label
- */
- _onClickPrintLabel: function (ev) {
- const el = ev.currentTarget;
- const modelIds = [];
- const productQtys = [];
- const report_file = 'stock.report_reception_report_label';
- let nodeToPrint = [];
-
- if (el.name === 'print_label') { // One line print
- nodeToPrint = [el];
- } else {
- if (el.name === "print_all_labels") { // Global "Print Labels"
- nodeToPrint = this.iframe.contentDocument.querySelectorAll('.o_print_label:not(.o_print_label_all):not(:disabled)');
- } else { // Local "Print Labels"
- nodeToPrint = el.closest('thead').nextElementSibling.querySelectorAll('.o_print_label:not(.o_print_label_all):not(:disabled)');
- }
- }
-
- nodeToPrint.forEach(node => {
- modelIds.push(parseInt(node.getAttribute('move-id')));
- productQtys.push(Math.ceil(node.getAttribute('qty')) || '1');
- });
-
- if (!modelIds.length) { // Nothing to print for this model.
- return Promise.resolve();
- }
- const report_name = `${report_file}?docids=${modelIds}&report_type=qweb-pdf&quantity=${productQtys}`;
- const action = {
- type: 'ir.actions.report',
- report_type: 'qweb-pdf',
- report_name,
- report_file,
- };
- return this.do_action(action);
- }
-
-});
-
-core.action_registry.add('reception_report', ReceptionReport);
-
-export default ReceptionReport;