Skip to content

Commit

Permalink
TA#66873 [MIG][16.0] stock_move_origin_link (#181)
Browse files Browse the repository at this point in the history
Co-authored-by: Lanto Razafindrabe <[email protected]>
Co-authored-by: majouda <[email protected]>
  • Loading branch information
3 people authored Oct 1, 2024
1 parent c856867 commit 5538166
Show file tree
Hide file tree
Showing 19 changed files with 364 additions and 0 deletions.
1 change: 1 addition & 0 deletions .docker_files/main/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"depends": [
"stock",
"stock_account_visibility",
"stock_move_origin_link",
"stock_picking_show_address",
"stock_quant_by_category",
],
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ RUN gitoo install-all --conf_file /gitoo.yml --destination "${THIRD_PARTY_ADDONS
USER odoo

COPY stock_account_visibility /mnt/extra-addons/stock_account_visibility
COPY stock_move_origin_link /mnt/extra-addons/stock_move_origin_link
COPY stock_picking_show_address /mnt/extra-addons/stock_picking_show_address
COPY stock_quant_by_category /mnt/extra-addons/stock_quant_by_category

Expand Down
49 changes: 49 additions & 0 deletions stock_move_origin_link/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
====================================
Stock Move Origin Link
====================================

This module adds the `Source Document` to the list view of stock pickings and stock moves.

.. image:: static/description/stock_move_list.png

The text is clickable if it refers to an object within the supported types.

For example, in the example above, when clicking on `MO/00003`, the user is redirected to the
form view of the production order.

.. image:: static/description/mrp_production_form.png

Stock Pickings
--------------
The feature is available for all stock picking list views.
Here is an example with the list view of delivery orders.

.. image:: static/description/delivery_order_list.png

When clicking on `SO020`, the user is redirected to the sale order.

.. image:: static/description/sale_order_form.png

Form Views
----------
The feature is also available on form views of stock pickings and stock moves.

.. image:: static/description/delivery_order_form.png

Supported Origin Documents
--------------------------
The module supports the following documents as origin of a stock move:

* Sale Orders
* Purchase Orders
* Manufacturing Orders

However, the module only depends on the `Inventory` app. It detects automatically whether the `Sales`, `Purchases` and `MRP` apps are installed.

Contributors
------------
* Numigi (tm) and all its contributors (https://bit.ly/numigiens)

More information
----------------
* Meet us at https://bit.ly/numigi-com
4 changes: 4 additions & 0 deletions stock_move_origin_link/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import models
21 changes: 21 additions & 0 deletions stock_move_origin_link/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{
"name": "Stock Move Origin Link",
"version": "16.0.1.0.0",
"category": "Stock",
"description": "Add a link to the origin document from stock moves.",
"summary": "Add a link to the origin document from stock moves.",
"maintainer": "numigi",
"website": "https://bit.ly/numigi-com",
"license": "LGPL-3",
"depends": ["stock"],
"data": [
"views/stock_move_views.xml",
"views/stock_move_line_views.xml",
],
"assets": {
"web.assets_backend": ["stock_move_origin_link/static/src/*"],
},
}
28 changes: 28 additions & 0 deletions stock_move_origin_link/i18n/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_move_origin_link
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-10 19:54+0000\n"
"PO-Revision-Date: 2024-04-10 14:55-0500\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"X-Generator: Poedit 2.0.6\n"

#. module: stock_move_origin_link
#: model:ir.model,name:stock_move_origin_link.model_stock_move_line
msgid "Product Moves (Stock Move Line)"
msgstr ""

#. module: stock_move_origin_link
#: model:ir.model.fields,field_description:stock_move_origin_link.field_stock_move_line__origin
msgid "Source Document"
msgstr "Document d'origine"
3 changes: 3 additions & 0 deletions stock_move_origin_link/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import stock_move_line_with_origin
11 changes: 11 additions & 0 deletions stock_move_origin_link/models/stock_move_line_with_origin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import fields, models


class StockMoveLineWithOrigin(models.Model):

_inherit = "stock.move.line"

origin = fields.Char(related="move_id.origin", readonly=True)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
185 changes: 185 additions & 0 deletions stock_move_origin_link/static/src/char_field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/** @odoo-module **/

import { CharField } from "@web/views/fields/char/char_field";
import { patch } from '@web/core/utils/patch';
import { useService } from "@web/core/utils/hooks";
import { onWillStart, onMounted, onWillUpdateProps, Component } from "@odoo/owl";
import rpc from "web.rpc";

const VALID_MODELS = ["stock.picking", "stock.move", "stock.move.line"];

// AccessVerifier Class for caching access checks
class AccessVerifier extends Component {
constructor() {
super(...arguments);
this._accessCache = new Map();
}

// Check if the model is readable, with caching
async isModelReadable(model) {
if (!this._accessCache.has(model)) {
const result = await rpc.query({
model: model,
method: "check_access_rights",
kwargs: { operation: "read", raise_exception: false }
});
this._accessCache.set(model, result);
}
return this._accessCache.get(model);
}
}

// Global instance of AccessVerifier
const accessVerifier = new AccessVerifier();
const originCache = new Map();

// Patch CharField prototype
patch(CharField.prototype, 'stock_move_origin_link_charfield', {
setup() {
this.props.hasOrigin = this.hasOriginField();
this.actionService = useService("action");
this._super(...arguments);

// Setup hooks
onWillStart(this.updateOriginIfNeeded.bind(this));
onWillUpdateProps(this.handleUpdateProps.bind(this));
onMounted(this.updateOriginIfNeeded.bind(this));
},

// Consolidate origin update logic
async updateOriginIfNeeded() {
if (!this.props.originUrl && this.props.hasOrigin) {
await this.updateOriginUrl(this.props.record);
}
},

async handleUpdateProps(nextProps) {
if (nextProps.record !== this.props.record && !nextProps.originUrl && this.props.hasOrigin) {
await this.updateOriginUrl(nextProps.record);
}
},

async updateOriginUrl(record) {
try {
if (originCache.has(record.id)) {
const cachedOrigin = originCache.get(record.id);
this.props.originRecord = cachedOrigin.record;
this.props.originUrl = cachedOrigin.url;
return;
}

if (this.hasOriginField()) {
const originRecord = await this.computeOriginUrl(record);
const originUrl = originRecord ? `#id=${originRecord.id}&model=${originRecord.model}` : null;
this.props.originRecord = originRecord;
this.props.originUrl = originUrl;

if (originRecord) {
originCache.set(record.id, { record: originRecord, url: originUrl });
}
}
} catch (error) {
this.handleError('Failed to update origin URL:', error);
}
},

// Check if the field has an origin
hasOriginField() {
return this.props.name === 'origin' && VALID_MODELS.includes(this.props.record.resModel) && this.props.record.data.origin;
},

// Fetch the form view action
async getRecordFormViewAction(record) {
try {
return await rpc.query({
model: record.model,
method: "get_formview_action",
args: [[record.id]],
});
} catch (error) {
this.handleError('Error fetching form view action:', error);
return null;
}
},

// Handle link click event
async onLinkClick(event) {
event.preventDefault();
event.stopPropagation();

if (this.props.originRecord) {
const action = await this.getRecordFormViewAction(this.props.originRecord);
this.actionService.doAction(action);
}
},

// Check if a module is installed, with caching
async isModuleInstalled(moduleName) {
try {
const result = await rpc.query({
model: "ir.module.module",
method: "search_read",
args: [[["name", "=", moduleName], ["state", "=", "installed"]]],
});
return result.length > 0;
} catch (error) {
this.handleError(`Error checking if module ${moduleName} is installed:`, error);
return false;
}
},

// Fetch documents based on origin field
async fetchDocumentsByOrigin(modelName, moduleName, record) {
try {
if (!(await this.isModuleInstalled(moduleName))) {
return false;
}

if (!(await accessVerifier.isModelReadable(modelName))) {
return false;
}

const data = await rpc.query({
model: modelName,
method: "search_read",
args: [[["name", "=", record.data.origin]], ["name"]],
});

return data.length ? { ...data[0], model: modelName } : false;

} catch (error) {
this.handleError(`Error fetching documents for model ${modelName}:`, error);
return false;
}
},

// Compute origin URL by checking various models
async computeOriginUrl(record) {
try {
return (
await this.fetchDocumentsByOrigin("purchase.order", "purchase", record) ||
await this.fetchDocumentsByOrigin("sale.order", "sale", record) ||
await this.fetchDocumentsByOrigin("mrp.production", "mrp", record) ||
null
);
} catch (error) {
this.handleError('Error computing origin URL:', error);
return null;
}
},

// Error handling helper
handleError(message, error) {
console.error(message, error);
this.props.originUrl = null;
this.props.originRecord = null;
}
});

// Extend CharField props
CharField.props = {
...CharField.props,
hasOrigin: { type: Boolean, optional: true },
originUrl: { type: String, optional: true },
originRecord: { type: Object, optional: true },
};
15 changes: 15 additions & 0 deletions stock_move_origin_link/static/src/char_fiield.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="web.CharField" owl="1" t-inherit-mode="extension" >
<xpath expr="//span[@t-esc='formattedValue']" position="replace">
<t t-if="props.hasOrigin and props.originUrl">
<a t-attf-href="{{props.originUrl}}" t-on-click.prevent="onLinkClick">
<span t-esc="formattedValue" />
</a>
</t>
<t t-else="">
<span t-esc="formattedValue" />
</t>
</xpath>
</t>
</templates>
14 changes: 14 additions & 0 deletions stock_move_origin_link/static/src/list_renderer.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="web.ListRenderer.RecordRow" owl="1" t-inherit-mode="extension" >
<xpath expr="//t[@t-if='!isInvisible']" position="replace">
<t t-if="!isInvisible">
<t t-if="column.name === 'origin' and ['stock.picking', 'stock.move', 'stock.move.line'].includes(record.resModel)">
<Field name="column.name" record="record" type="column.widget" class="getFieldClass(column)" fieldInfo="props.archInfo.fieldNodes[column.name]" setDirty="(isDirty) => this.setDirty(isDirty)" readonly="props.activeActions?.edit === false and !record.isNew"/>
</t>
<t t-elif="canUseFormatter(column, record)" t-out="getFormattedValue(column, record)"/>
<Field t-else="" name="column.name" record="record" type="column.widget" class="getFieldClass(column)" fieldInfo="props.archInfo.fieldNodes[column.name]" setDirty="(isDirty) => this.setDirty(isDirty)" readonly="props.activeActions?.edit === false and !record.isNew"/>
</t>
</xpath>
</t>
</templates>
16 changes: 16 additions & 0 deletions stock_move_origin_link/views/stock_move_line_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version='1.0' encoding='UTF-8'?>
<odoo>

<record id="view_move_line_tree" model="ir.ui.view">
<field name="name">Stock Move Line List: Add origin</field>
<field name="model">stock.move.line</field>
<field name="type">tree</field>
<field name="inherit_id" ref="stock.view_move_line_tree"/>
<field name="arch" type="xml">
<field name="reference" position="after">
<field name="origin" widget="origin"/>
</field>
</field>
</record>

</odoo>
16 changes: 16 additions & 0 deletions stock_move_origin_link/views/stock_move_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version='1.0' encoding='UTF-8'?>
<odoo>

<record id="view_move_tree" model="ir.ui.view">
<field name="name">Stock Move List: Add origin</field>
<field name="model">stock.move</field>
<field name="type">tree</field>
<field name="inherit_id" ref="stock.view_move_tree"/>
<field name="arch" type="xml">
<field name="reference" position="after">
<field name="origin" widget="origin"/>
</field>
</field>
</record>

</odoo>

0 comments on commit 5538166

Please sign in to comment.