Accept Merge Request #2268: (feature/新增plm模块 -> develop)
Merge Request: 新增 Created By: @胡尧 Accepted By: @胡尧 URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2268?initial=true
This commit is contained in:
5
mrp_plm/__init__.py
Normal file
5
mrp_plm/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import report
|
||||
41
mrp_plm/__manifest__.py
Normal file
41
mrp_plm/__manifest__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Product Lifecycle Management (PLM)',
|
||||
'version': '1.0',
|
||||
'category': 'Manufacturing/Product Lifecycle Management (PLM)',
|
||||
'sequence': 155,
|
||||
'summary': """Manage engineering change orders on products, bills of material""",
|
||||
'website': 'https://www.odoo.com/app/plm',
|
||||
'depends': ['mrp'],
|
||||
'description': """
|
||||
Product Life Management
|
||||
=======================
|
||||
|
||||
* Versioning of Bill of Materials and Products
|
||||
* Different approval flows possible depending on the type of change order
|
||||
|
||||
""",
|
||||
'data': [
|
||||
'security/mrp_plm.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/mail_activity_type_data.xml',
|
||||
'data/mrp_data.xml',
|
||||
'views/mrp_bom_views.xml',
|
||||
'views/mrp_document_views.xml',
|
||||
'views/mrp_eco_views.xml',
|
||||
'views/product_views.xml',
|
||||
'views/mrp_production_views.xml',
|
||||
'report/mrp_report_bom_structure.xml',
|
||||
],
|
||||
'application': True,
|
||||
'license': 'OEEL-1',
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'mrp_plm/static/src/**/*.js',
|
||||
'mrp_plm/static/src/**/*.scss',
|
||||
'mrp_plm/static/src/**/*.xml',
|
||||
],
|
||||
}
|
||||
}
|
||||
7
mrp_plm/data/mail_activity_type_data.xml
Normal file
7
mrp_plm/data/mail_activity_type_data.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mail_activity_eco_approval" model="mail.activity.type">
|
||||
<field name="name">ECO Approval</field>
|
||||
<field name="res_model">mrp.eco</field>
|
||||
</record>
|
||||
</odoo>
|
||||
44
mrp_plm/data/mrp_data.xml
Normal file
44
mrp_plm/data/mrp_data.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Eco type by default -->
|
||||
<record id="ecotype0" model="mrp.eco.type">
|
||||
<field name="name">New Product Introduction</field>
|
||||
</record>
|
||||
|
||||
<!-- ECO Stage records -->
|
||||
<record id="ecostage_new" model="mrp.eco.stage">
|
||||
<field name="name">New</field>
|
||||
<field name="type_ids" eval="[(4, ref('ecotype0'))]"/>
|
||||
<field name="folded" eval="False"/>
|
||||
</record>
|
||||
<record id="ecostage_progress" model="mrp.eco.stage">
|
||||
<field name="name">In Progress</field>
|
||||
<field name="type_ids" eval="[(4, ref('ecotype0'))]"/>
|
||||
<field name="folded" eval="False"/>
|
||||
</record>
|
||||
<record id="ecostage_validated" model="mrp.eco.stage">
|
||||
<field name="name">Validated</field>
|
||||
<field name="type_ids" eval="[(4, ref('ecotype0'))]"/>
|
||||
<field name="folded" eval="False"/>
|
||||
<field name="allow_apply_change" eval="True"/>
|
||||
</record>
|
||||
<record id="ecostage_effective" model="mrp.eco.stage">
|
||||
<field name="name">Effective</field>
|
||||
<field name="type_ids" eval="[(4, ref('ecotype0'))]"/>
|
||||
<field name="folded" eval="True"/>
|
||||
<field name="allow_apply_change" eval="True"/>
|
||||
<field name="final_stage" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ECO sequence -->
|
||||
<record id="seq_eco" model="ir.sequence">
|
||||
<field name="name">Engineering Change Order</field>
|
||||
<field name="code">mrp.eco</field>
|
||||
<field name="prefix">ECO</field>
|
||||
<field name="padding">4</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
1828
mrp_plm/i18n/af.po
Normal file
1828
mrp_plm/i18n/af.po
Normal file
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/am.po
Normal file
1833
mrp_plm/i18n/am.po
Normal file
File diff suppressed because it is too large
Load Diff
1897
mrp_plm/i18n/ar.po
Normal file
1897
mrp_plm/i18n/ar.po
Normal file
File diff suppressed because it is too large
Load Diff
1877
mrp_plm/i18n/az.po
Normal file
1877
mrp_plm/i18n/az.po
Normal file
File diff suppressed because it is too large
Load Diff
1888
mrp_plm/i18n/bg.po
Normal file
1888
mrp_plm/i18n/bg.po
Normal file
File diff suppressed because it is too large
Load Diff
1764
mrp_plm/i18n/bs.po
Normal file
1764
mrp_plm/i18n/bs.po
Normal file
File diff suppressed because it is too large
Load Diff
1924
mrp_plm/i18n/ca.po
Normal file
1924
mrp_plm/i18n/ca.po
Normal file
File diff suppressed because it is too large
Load Diff
1889
mrp_plm/i18n/cs.po
Normal file
1889
mrp_plm/i18n/cs.po
Normal file
File diff suppressed because it is too large
Load Diff
1895
mrp_plm/i18n/da.po
Normal file
1895
mrp_plm/i18n/da.po
Normal file
File diff suppressed because it is too large
Load Diff
1926
mrp_plm/i18n/de.po
Normal file
1926
mrp_plm/i18n/de.po
Normal file
File diff suppressed because it is too large
Load Diff
1832
mrp_plm/i18n/el.po
Normal file
1832
mrp_plm/i18n/el.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/en_GB.po
Normal file
1454
mrp_plm/i18n/en_GB.po
Normal file
File diff suppressed because it is too large
Load Diff
1921
mrp_plm/i18n/es.po
Normal file
1921
mrp_plm/i18n/es.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_AR.po
Normal file
1454
mrp_plm/i18n/es_AR.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_BO.po
Normal file
1454
mrp_plm/i18n/es_BO.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_CL.po
Normal file
1454
mrp_plm/i18n/es_CL.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_CO.po
Normal file
1454
mrp_plm/i18n/es_CO.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_CR.po
Normal file
1454
mrp_plm/i18n/es_CR.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_DO.po
Normal file
1454
mrp_plm/i18n/es_DO.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_EC.po
Normal file
1454
mrp_plm/i18n/es_EC.po
Normal file
File diff suppressed because it is too large
Load Diff
1929
mrp_plm/i18n/es_MX.po
Normal file
1929
mrp_plm/i18n/es_MX.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_PE.po
Normal file
1454
mrp_plm/i18n/es_PE.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_PY.po
Normal file
1454
mrp_plm/i18n/es_PY.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/es_VE.po
Normal file
1454
mrp_plm/i18n/es_VE.po
Normal file
File diff suppressed because it is too large
Load Diff
1915
mrp_plm/i18n/et.po
Normal file
1915
mrp_plm/i18n/et.po
Normal file
File diff suppressed because it is too large
Load Diff
1456
mrp_plm/i18n/eu.po
Normal file
1456
mrp_plm/i18n/eu.po
Normal file
File diff suppressed because it is too large
Load Diff
1908
mrp_plm/i18n/fa.po
Normal file
1908
mrp_plm/i18n/fa.po
Normal file
File diff suppressed because it is too large
Load Diff
1932
mrp_plm/i18n/fi.po
Normal file
1932
mrp_plm/i18n/fi.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/fo.po
Normal file
1454
mrp_plm/i18n/fo.po
Normal file
File diff suppressed because it is too large
Load Diff
1917
mrp_plm/i18n/fr.po
Normal file
1917
mrp_plm/i18n/fr.po
Normal file
File diff suppressed because it is too large
Load Diff
46
mrp_plm/i18n/fr_CA.po
Normal file
46
mrp_plm/i18n/fr_CA.po
Normal file
@@ -0,0 +1,46 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mrp_plm
|
||||
#
|
||||
# Translators:
|
||||
# Martin Trigaux <mat@odoo.com>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~18+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-10-02 11:50+0000\n"
|
||||
"PO-Revision-Date: 2017-10-02 11:50+0000\n"
|
||||
"Last-Translator: Martin Trigaux <mat@odoo.com>, 2017\n"
|
||||
"Language-Team: French (Canada) (https://www.transifex.com/odoo/teams/41243/"
|
||||
"fr_CA/)\n"
|
||||
"Language: fr_CA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. module: mrp_plm
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco__id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_approval__id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_approval_template__id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_bom_change__id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_routing_change__id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_stage__id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_tag__id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_type__id
|
||||
msgid "ID"
|
||||
msgstr "Identifiant"
|
||||
|
||||
#. module: mrp_plm
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco__product_tmpl_id
|
||||
#: model:ir.model.fields,field_description:mrp_plm.field_mrp_eco_bom_change__product_id
|
||||
#: model_terms:ir.ui.view,arch_db:mrp_plm.mrp_bom_view_kanban
|
||||
#: model_terms:ir.ui.view,arch_db:mrp_plm.mrp_eco_bom_change_view_form
|
||||
#: model_terms:ir.ui.view,arch_db:mrp_plm.mrp_eco_search
|
||||
msgid "Product"
|
||||
msgstr "Produit"
|
||||
|
||||
#. module: mrp_plm
|
||||
#: model:ir.model,name:mrp_plm.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Modèle de produit"
|
||||
1454
mrp_plm/i18n/gl.po
Normal file
1454
mrp_plm/i18n/gl.po
Normal file
File diff suppressed because it is too large
Load Diff
1841
mrp_plm/i18n/gu.po
Normal file
1841
mrp_plm/i18n/gu.po
Normal file
File diff suppressed because it is too large
Load Diff
1889
mrp_plm/i18n/he.po
Normal file
1889
mrp_plm/i18n/he.po
Normal file
File diff suppressed because it is too large
Load Diff
1837
mrp_plm/i18n/hi.po
Normal file
1837
mrp_plm/i18n/hi.po
Normal file
File diff suppressed because it is too large
Load Diff
1887
mrp_plm/i18n/hr.po
Normal file
1887
mrp_plm/i18n/hr.po
Normal file
File diff suppressed because it is too large
Load Diff
1895
mrp_plm/i18n/hu.po
Normal file
1895
mrp_plm/i18n/hu.po
Normal file
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/hy.po
Normal file
1833
mrp_plm/i18n/hy.po
Normal file
File diff suppressed because it is too large
Load Diff
1905
mrp_plm/i18n/id.po
Normal file
1905
mrp_plm/i18n/id.po
Normal file
File diff suppressed because it is too large
Load Diff
1824
mrp_plm/i18n/is.po
Normal file
1824
mrp_plm/i18n/is.po
Normal file
File diff suppressed because it is too large
Load Diff
1907
mrp_plm/i18n/it.po
Normal file
1907
mrp_plm/i18n/it.po
Normal file
File diff suppressed because it is too large
Load Diff
1863
mrp_plm/i18n/ja.po
Normal file
1863
mrp_plm/i18n/ja.po
Normal file
File diff suppressed because it is too large
Load Diff
1462
mrp_plm/i18n/ka.po
Normal file
1462
mrp_plm/i18n/ka.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/kab.po
Normal file
1454
mrp_plm/i18n/kab.po
Normal file
File diff suppressed because it is too large
Load Diff
1886
mrp_plm/i18n/km.po
Normal file
1886
mrp_plm/i18n/km.po
Normal file
File diff suppressed because it is too large
Load Diff
1869
mrp_plm/i18n/ko.po
Normal file
1869
mrp_plm/i18n/ko.po
Normal file
File diff suppressed because it is too large
Load Diff
1802
mrp_plm/i18n/lb.po
Normal file
1802
mrp_plm/i18n/lb.po
Normal file
File diff suppressed because it is too large
Load Diff
1863
mrp_plm/i18n/lo.po
Normal file
1863
mrp_plm/i18n/lo.po
Normal file
File diff suppressed because it is too large
Load Diff
1886
mrp_plm/i18n/lt.po
Normal file
1886
mrp_plm/i18n/lt.po
Normal file
File diff suppressed because it is too large
Load Diff
1874
mrp_plm/i18n/lv.po
Normal file
1874
mrp_plm/i18n/lv.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/mk.po
Normal file
1454
mrp_plm/i18n/mk.po
Normal file
File diff suppressed because it is too large
Load Diff
1843
mrp_plm/i18n/ml.po
Normal file
1843
mrp_plm/i18n/ml.po
Normal file
File diff suppressed because it is too large
Load Diff
1885
mrp_plm/i18n/mn.po
Normal file
1885
mrp_plm/i18n/mn.po
Normal file
File diff suppressed because it is too large
Load Diff
1838
mrp_plm/i18n/ms.po
Normal file
1838
mrp_plm/i18n/ms.po
Normal file
File diff suppressed because it is too large
Load Diff
1274
mrp_plm/i18n/my.po
Normal file
1274
mrp_plm/i18n/my.po
Normal file
File diff suppressed because it is too large
Load Diff
1851
mrp_plm/i18n/nb.po
Normal file
1851
mrp_plm/i18n/nb.po
Normal file
File diff suppressed because it is too large
Load Diff
1456
mrp_plm/i18n/ne.po
Normal file
1456
mrp_plm/i18n/ne.po
Normal file
File diff suppressed because it is too large
Load Diff
1908
mrp_plm/i18n/nl.po
Normal file
1908
mrp_plm/i18n/nl.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/nl_BE.po
Normal file
1454
mrp_plm/i18n/nl_BE.po
Normal file
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/no.po
Normal file
1833
mrp_plm/i18n/no.po
Normal file
File diff suppressed because it is too large
Load Diff
1927
mrp_plm/i18n/pl.po
Normal file
1927
mrp_plm/i18n/pl.po
Normal file
File diff suppressed because it is too large
Load Diff
1877
mrp_plm/i18n/pt.po
Normal file
1877
mrp_plm/i18n/pt.po
Normal file
File diff suppressed because it is too large
Load Diff
1914
mrp_plm/i18n/pt_BR.po
Normal file
1914
mrp_plm/i18n/pt_BR.po
Normal file
File diff suppressed because it is too large
Load Diff
1903
mrp_plm/i18n/ro.po
Normal file
1903
mrp_plm/i18n/ro.po
Normal file
File diff suppressed because it is too large
Load Diff
1916
mrp_plm/i18n/ru.po
Normal file
1916
mrp_plm/i18n/ru.po
Normal file
File diff suppressed because it is too large
Load Diff
1877
mrp_plm/i18n/sk.po
Normal file
1877
mrp_plm/i18n/sk.po
Normal file
File diff suppressed because it is too large
Load Diff
1870
mrp_plm/i18n/sl.po
Normal file
1870
mrp_plm/i18n/sl.po
Normal file
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/sq.po
Normal file
1454
mrp_plm/i18n/sq.po
Normal file
File diff suppressed because it is too large
Load Diff
1876
mrp_plm/i18n/sr.po
Normal file
1876
mrp_plm/i18n/sr.po
Normal file
File diff suppressed because it is too large
Load Diff
1457
mrp_plm/i18n/sr@latin.po
Normal file
1457
mrp_plm/i18n/sr@latin.po
Normal file
File diff suppressed because it is too large
Load Diff
1878
mrp_plm/i18n/sv.po
Normal file
1878
mrp_plm/i18n/sv.po
Normal file
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/sw.po
Normal file
1833
mrp_plm/i18n/sw.po
Normal file
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/ta.po
Normal file
1833
mrp_plm/i18n/ta.po
Normal file
File diff suppressed because it is too large
Load Diff
1897
mrp_plm/i18n/th.po
Normal file
1897
mrp_plm/i18n/th.po
Normal file
File diff suppressed because it is too large
Load Diff
1920
mrp_plm/i18n/tr.po
Normal file
1920
mrp_plm/i18n/tr.po
Normal file
File diff suppressed because it is too large
Load Diff
1902
mrp_plm/i18n/uk.po
Normal file
1902
mrp_plm/i18n/uk.po
Normal file
File diff suppressed because it is too large
Load Diff
1899
mrp_plm/i18n/vi.po
Normal file
1899
mrp_plm/i18n/vi.po
Normal file
File diff suppressed because it is too large
Load Diff
1866
mrp_plm/i18n/zh_CN.po
Normal file
1866
mrp_plm/i18n/zh_CN.po
Normal file
File diff suppressed because it is too large
Load Diff
1865
mrp_plm/i18n/zh_TW.po
Normal file
1865
mrp_plm/i18n/zh_TW.po
Normal file
File diff suppressed because it is too large
Load Diff
8
mrp_plm/models/__init__.py
Normal file
8
mrp_plm/models/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mrp_bom
|
||||
from . import mrp_document
|
||||
from . import mrp_eco
|
||||
from . import mrp_production
|
||||
from . import product
|
||||
179
mrp_plm/models/mrp_bom.py
Normal file
179
mrp_plm/models/mrp_bom.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpBom(models.Model):
|
||||
_inherit = 'mrp.bom'
|
||||
|
||||
version = fields.Integer('Version', default=1, readonly=True)
|
||||
previous_bom_id = fields.Many2one('mrp.bom', 'Previous BoM')
|
||||
active = fields.Boolean('Production Ready')
|
||||
image_128 = fields.Image(related='product_tmpl_id.image_128', readonly=False)
|
||||
eco_ids = fields.One2many(
|
||||
'mrp.eco', 'new_bom_id', 'ECO to be applied')
|
||||
eco_count = fields.Integer('# ECOs', compute='_compute_eco_data')
|
||||
eco_inprogress_count = fields.Integer("# ECOs in progress", compute='_compute_eco_data')
|
||||
|
||||
def _compute_eco_data(self):
|
||||
self.eco_inprogress_count = 0 # not used
|
||||
previous_boms_mapping = self._get_previous_boms()
|
||||
previous_boms_list = list(previous_boms_mapping.keys())
|
||||
eco_data = self.env['mrp.eco'].read_group([
|
||||
('bom_id', 'in', previous_boms_list),
|
||||
('stage_id.folded', '=', False)],
|
||||
['bom_id'], ['bom_id'])
|
||||
eco_count = dict((bom.id, 0) for bom in self)
|
||||
for eco in eco_data:
|
||||
previous_bom_id = eco['bom_id'][0]
|
||||
previous_bom_eco_count = eco['bom_id_count']
|
||||
for bom_id in previous_boms_mapping[previous_bom_id]:
|
||||
eco_count[bom_id] += previous_bom_eco_count
|
||||
for bom in self:
|
||||
bom.eco_count = eco_count[bom.id]
|
||||
|
||||
def apply_new_version(self):
|
||||
""" Put old BoM as deprecated - TODO: Set to stage that is production_ready """
|
||||
MrpEco = self.env['mrp.eco']
|
||||
for new_bom in self:
|
||||
new_bom.write({'active': True})
|
||||
# Move eco's into rebase state which is in progress state.
|
||||
ecos = MrpEco.search(['|',
|
||||
('bom_id', '=', new_bom.previous_bom_id.id),
|
||||
('current_bom_id', '=', new_bom.previous_bom_id.id),
|
||||
('new_bom_id', '!=', False),
|
||||
('new_bom_id', '!=', new_bom.id),
|
||||
('state', 'not in', ('done', 'new'))])
|
||||
ecos.write({'state': 'rebase', 'current_bom_id': new_bom.id})
|
||||
# Change old bom of eco which is in draft state.
|
||||
draft_ecos = MrpEco.search(['|',
|
||||
('bom_id', '=', new_bom.previous_bom_id.id),
|
||||
('current_bom_id', '=', new_bom.previous_bom_id.id),
|
||||
('new_bom_id', '=', False)])
|
||||
draft_ecos.write({'bom_id': new_bom.id})
|
||||
# Deactivate previous revision of BoM
|
||||
new_bom.previous_bom_id.write({'active': False})
|
||||
return True
|
||||
|
||||
def button_mrp_eco(self):
|
||||
self.ensure_one()
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("mrp_plm.mrp_eco_action_main")
|
||||
previous_boms = self._get_previous_boms()
|
||||
action['domain'] = [('bom_id', 'in', list(previous_boms.keys()))]
|
||||
action['context'] = {
|
||||
'default_bom_id': self.id,
|
||||
'default_product_tmpl_id': self.product_tmpl_id.id,
|
||||
'default_type': 'bom'
|
||||
}
|
||||
return action
|
||||
|
||||
def _get_previous_boms(self):
|
||||
""" Return a dictionary with the keys to be all the previous boms' id and
|
||||
the value to be a set of ids in self of which the key is their previous boms.
|
||||
"""
|
||||
boms_data = self.with_context(active_test=False).search_read(
|
||||
[('product_tmpl_id', 'in', self.product_tmpl_id.ids)],
|
||||
fields=['id', 'previous_bom_id'], load=False,
|
||||
order='id desc, version desc')
|
||||
previous_boms = dict((bom.id, {bom.id}) for bom in self)
|
||||
for bom_data in boms_data:
|
||||
if not bom_data['previous_bom_id']:
|
||||
continue
|
||||
bom_id = bom_data['id']
|
||||
previous_bom_id = bom_data['previous_bom_id']
|
||||
previous_boms[previous_bom_id] = previous_boms.get(bom_id, set()) | previous_boms.get(previous_bom_id, set())
|
||||
return previous_boms
|
||||
|
||||
def _get_active_version(self):
|
||||
self.ensure_one()
|
||||
boms = self.with_context(active_test=False).search([
|
||||
('product_id', '=', self.product_id.id),
|
||||
('version', '>', self.version)], order='version')
|
||||
previous_boms = self
|
||||
for bom in boms:
|
||||
if bom.previous_bom_id not in previous_boms:
|
||||
continue
|
||||
previous_boms += bom
|
||||
if bom.active:
|
||||
return bom
|
||||
return False
|
||||
|
||||
|
||||
class MrpBomLine(models.Model):
|
||||
_inherit = 'mrp.bom.line'
|
||||
|
||||
def _prepare_rebase_line(self, eco, change_type, product_id, uom_id, operation_id=None, new_qty=0):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'change_type': change_type,
|
||||
'product_id': product_id,
|
||||
'rebase_id': eco.id,
|
||||
'old_uom_id': self.product_uom_id.id,
|
||||
'new_uom_id': uom_id,
|
||||
'old_operation_id': self.operation_id.id,
|
||||
'new_operation_id': operation_id,
|
||||
'old_product_qty': 0.0 if change_type == 'add' else self.product_qty,
|
||||
'new_product_qty': new_qty,
|
||||
}
|
||||
|
||||
def _create_or_update_rebase_line(self, ecos, operation, product_id, uom_id, operation_id=None, new_qty=0):
|
||||
self.ensure_one()
|
||||
BomChange = self.env['mrp.eco.bom.change']
|
||||
for eco in ecos:
|
||||
# When product exist in new bill of material update line otherwise add line in rebase changes.
|
||||
rebase_line = BomChange.search([
|
||||
('product_id', '=', product_id),
|
||||
('rebase_id', '=', eco.id)], limit=1)
|
||||
if rebase_line:
|
||||
# Update existing rebase line or unlink it.
|
||||
if (rebase_line.old_product_qty, rebase_line.old_uom_id.id, rebase_line.old_operation_id.id) != (new_qty, uom_id, operation_id):
|
||||
if rebase_line.change_type == 'update':
|
||||
rebase_line.write({'new_product_qty': new_qty, 'new_operation_id': operation_id, 'new_uom_id': uom_id})
|
||||
else:
|
||||
rebase_line_vals = self._prepare_rebase_line(eco, 'add', product_id, uom_id, operation_id, new_qty)
|
||||
rebase_line.write(rebase_line_vals)
|
||||
else:
|
||||
rebase_line.unlink()
|
||||
else:
|
||||
rebase_line_vals = self._prepare_rebase_line(eco, operation, product_id, uom_id, operation_id, new_qty)
|
||||
BomChange.create(rebase_line_vals)
|
||||
eco.state = 'rebase' if eco.bom_rebase_ids or eco.previous_change_ids else 'progress'
|
||||
return True
|
||||
|
||||
def bom_line_change(self, vals, operation='update'):
|
||||
MrpEco = self.env['mrp.eco']
|
||||
for line in self:
|
||||
ecos = MrpEco.search([
|
||||
('bom_id', '=', line.bom_id.id), ('state', 'in', ('progress', 'rebase')),
|
||||
('type', 'in', ('bom', 'both'))
|
||||
])
|
||||
if ecos:
|
||||
# Latest bom line (product, uom, operation_id, product_qty)
|
||||
product_id = vals.get('product_id', line.product_id.id)
|
||||
uom_id = vals.get('product_uom_id', line.product_uom_id.id)
|
||||
operation_id = vals.get('operation_id', line.operation_id.id)
|
||||
product_qty = vals.get('product_qty', line.product_qty)
|
||||
line._create_or_update_rebase_line(ecos, operation, product_id, uom_id, operation_id, product_qty)
|
||||
return True
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
lines = super().create(vals_list)
|
||||
for line, vals in zip(lines, vals_list):
|
||||
line.bom_line_change(vals, operation='add')
|
||||
return lines
|
||||
|
||||
def write(self, vals):
|
||||
operation = 'update'
|
||||
if vals.get('product_id'):
|
||||
# It will create update rebase line with negative quantity.
|
||||
self.bom_line_change({'product_qty': 0.0}, operation)
|
||||
operation = 'add'
|
||||
self.bom_line_change(vals, operation)
|
||||
return super(MrpBomLine, self).write(vals)
|
||||
|
||||
def unlink(self):
|
||||
# It will create update rebase line.
|
||||
self.bom_line_change({'product_qty': 0.0})
|
||||
return super(MrpBomLine, self).unlink()
|
||||
10
mrp_plm/models/mrp_document.py
Normal file
10
mrp_plm/models/mrp_document.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpDocument(models.Model):
|
||||
_inherit = 'mrp.document'
|
||||
|
||||
origin_attachment_id = fields.Many2one('ir.attachment')
|
||||
834
mrp_plm/models/mrp_eco.py
Normal file
834
mrp_plm/models/mrp_eco.py
Normal file
@@ -0,0 +1,834 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from markupsafe import escape
|
||||
from random import randint
|
||||
|
||||
import ast
|
||||
|
||||
from odoo import api, fields, models, tools, Command, SUPERUSER_ID, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class MrpEcoType(models.Model):
|
||||
_name = "mrp.eco.type"
|
||||
_description = 'ECO Type'
|
||||
_inherit = ['mail.alias.mixin', 'mail.thread']
|
||||
|
||||
_order = "sequence, id"
|
||||
|
||||
name = fields.Char('Name', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence')
|
||||
nb_ecos = fields.Integer('ECOs', compute='_compute_nb')
|
||||
nb_approvals = fields.Integer('Waiting Approvals', compute='_compute_nb')
|
||||
nb_approvals_my = fields.Integer('Waiting my Approvals', compute='_compute_nb')
|
||||
nb_validation = fields.Integer('To Apply', compute='_compute_nb')
|
||||
color = fields.Integer('Color', default=1)
|
||||
stage_ids = fields.Many2many('mrp.eco.stage', 'mrp_eco_stage_type_rel', 'type_id', 'stage_id', string='Stages')
|
||||
|
||||
def _compute_nb(self):
|
||||
# TDE FIXME: this seems not good for performances, to check (replace by read_group later on)
|
||||
MrpEco = self.env['mrp.eco']
|
||||
for eco_type in self:
|
||||
eco_type.nb_ecos = MrpEco.search_count([
|
||||
('type_id', '=', eco_type.id), ('state', '!=', 'done')
|
||||
])
|
||||
eco_type.nb_validation = MrpEco.search_count([
|
||||
('type_id', '=', eco_type.id),
|
||||
('stage_id.allow_apply_change', '=', True),
|
||||
('state', '=', 'progress')
|
||||
])
|
||||
eco_type.nb_approvals = MrpEco.search_count([
|
||||
('type_id', '=', eco_type.id),
|
||||
('approval_ids.status', '=', 'none')
|
||||
])
|
||||
eco_type.nb_approvals_my = MrpEco.search_count([
|
||||
('type_id', '=', eco_type.id),
|
||||
('approval_ids.status', '=', 'none'),
|
||||
('approval_ids.required_user_ids', '=', self.env.user.id)
|
||||
])
|
||||
|
||||
def _alias_get_creation_values(self):
|
||||
values = super(MrpEcoType, self)._alias_get_creation_values()
|
||||
values['alias_model_id'] = self.env['ir.model']._get('mrp.eco').id
|
||||
if self.id:
|
||||
values['alias_defaults'] = defaults = ast.literal_eval(self.alias_defaults or "{}")
|
||||
defaults['type_id'] = self.id
|
||||
return values
|
||||
|
||||
|
||||
class MrpEcoApprovalTemplate(models.Model):
|
||||
_name = "mrp.eco.approval.template"
|
||||
_order = "sequence"
|
||||
_description = 'ECO Approval Template'
|
||||
|
||||
name = fields.Char('Role', required=True)
|
||||
sequence = fields.Integer('Sequence')
|
||||
approval_type = fields.Selection([
|
||||
('optional', 'Approves, but the approval is optional'),
|
||||
('mandatory', 'Is required to approve'),
|
||||
('comment', 'Comments only')], 'Approval Type',
|
||||
default='mandatory', required=True)
|
||||
user_ids = fields.Many2many('res.users', string='Users', domain=lambda self: [('groups_id', 'in', self.env.ref('mrp_plm.group_plm_user').id)], required=True)
|
||||
stage_id = fields.Many2one('mrp.eco.stage', 'Stage', required=True)
|
||||
|
||||
|
||||
class MrpEcoApproval(models.Model):
|
||||
_name = "mrp.eco.approval"
|
||||
_description = 'ECO Approval'
|
||||
_order = 'approval_date desc'
|
||||
|
||||
eco_id = fields.Many2one(
|
||||
'mrp.eco', 'ECO',
|
||||
ondelete='cascade', required=True)
|
||||
approval_template_id = fields.Many2one(
|
||||
'mrp.eco.approval.template', 'Template',
|
||||
ondelete='cascade', required=True)
|
||||
name = fields.Char('Role', related='approval_template_id.name', store=True, readonly=False)
|
||||
user_id = fields.Many2one(
|
||||
'res.users', 'Approved by')
|
||||
required_user_ids = fields.Many2many(
|
||||
'res.users', string='Requested Users', related='approval_template_id.user_ids', readonly=False)
|
||||
template_stage_id = fields.Many2one(
|
||||
'mrp.eco.stage', 'Approval Stage',
|
||||
related='approval_template_id.stage_id', store=True, readonly=False)
|
||||
eco_stage_id = fields.Many2one(
|
||||
'mrp.eco.stage', 'ECO Stage',
|
||||
related='eco_id.stage_id', store=True, readonly=False)
|
||||
status = fields.Selection([
|
||||
('none', 'Not Yet'),
|
||||
('comment', 'Commented'),
|
||||
('approved', 'Approved'),
|
||||
('rejected', 'Rejected')], string='Status',
|
||||
default='none', required=True)
|
||||
approval_date = fields.Datetime('Approval Date')
|
||||
is_closed = fields.Boolean()
|
||||
is_approved = fields.Boolean(
|
||||
compute='_compute_is_approved', store=True)
|
||||
is_rejected = fields.Boolean(
|
||||
compute='_compute_is_rejected', store=True)
|
||||
awaiting_my_validation = fields.Boolean(
|
||||
compute='_compute_awaiting_my_validation', search='_search_awaiting_my_validation')
|
||||
|
||||
@api.depends('status', 'approval_template_id.approval_type')
|
||||
def _compute_is_approved(self):
|
||||
for rec in self:
|
||||
if rec.approval_template_id.approval_type == 'mandatory':
|
||||
rec.is_approved = rec.status == 'approved'
|
||||
else:
|
||||
rec.is_approved = True
|
||||
|
||||
@api.depends('status', 'approval_template_id.approval_type')
|
||||
def _compute_is_rejected(self):
|
||||
for rec in self:
|
||||
if rec.approval_template_id.approval_type == 'mandatory':
|
||||
rec.is_rejected = rec.status == 'rejected'
|
||||
else:
|
||||
rec.is_rejected = False
|
||||
|
||||
@api.depends('status', 'approval_template_id.approval_type')
|
||||
def _compute_awaiting_my_validation(self):
|
||||
# trigger the search method and return a domain where approval ids satisfying the conditions in the search method
|
||||
awaiting_validation_approval = self.search([('id', 'in', self.ids), ('awaiting_my_validation', '=', True)])
|
||||
# set awaiting_my_validation values for approvals
|
||||
awaiting_validation_approval.awaiting_my_validation = True
|
||||
(self - awaiting_validation_approval).awaiting_my_validation = False
|
||||
|
||||
def _search_awaiting_my_validation(self, operator, value):
|
||||
if (operator, value) not in [('=', True), ('!=', False)]:
|
||||
raise NotImplementedError(_('Operation not supported'))
|
||||
return [('required_user_ids', 'in', self.env.uid),
|
||||
('approval_template_id.approval_type', 'in', ('mandatory', 'optional')),
|
||||
('status', '!=', 'approved'),
|
||||
('is_closed', '=', False)]
|
||||
|
||||
class MrpEcoStage(models.Model):
|
||||
_name = 'mrp.eco.stage'
|
||||
_description = 'ECO Stage'
|
||||
_order = "sequence, id"
|
||||
_fold_name = 'folded'
|
||||
|
||||
@api.model
|
||||
def _get_sequence(self):
|
||||
others = self.search([('sequence','<>',False)], order='sequence desc', limit=1)
|
||||
if others:
|
||||
return (others[0].sequence or 0) + 1
|
||||
return 1
|
||||
|
||||
name = fields.Char('Name', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', default=_get_sequence)
|
||||
folded = fields.Boolean('Folded in kanban view')
|
||||
allow_apply_change = fields.Boolean(string='Allow to apply changes', help='Allow to apply changes from this stage.')
|
||||
final_stage = fields.Boolean(string='Final Stage', help='Once the changes are applied, the ECOs will be moved to this stage.')
|
||||
type_ids = fields.Many2many('mrp.eco.type', 'mrp_eco_stage_type_rel', 'stage_id', 'type_id', string='Types', required=True)
|
||||
approval_template_ids = fields.One2many('mrp.eco.approval.template', 'stage_id', 'Approvals')
|
||||
approval_roles = fields.Char('Approval Roles', compute='_compute_approvals', store=True)
|
||||
is_blocking = fields.Boolean('Blocking Stage', compute='_compute_is_blocking', store=True)
|
||||
legend_blocked = fields.Char(
|
||||
'Red Kanban Label', default=lambda s: _('Blocked'), translate=True, required=True,
|
||||
help='Override the default value displayed for the blocked state for kanban selection, when the ECO is in that stage.')
|
||||
legend_done = fields.Char(
|
||||
'Green Kanban Label', default=lambda s: _('Ready'), translate=True, required=True,
|
||||
help='Override the default value displayed for the done state for kanban selection, when the ECO is in that stage.')
|
||||
legend_normal = fields.Char(
|
||||
'Grey Kanban Label', default=lambda s: _('In Progress'), translate=True, required=True,
|
||||
help='Override the default value displayed for the normal state for kanban selection, when the ECO is in that stage.')
|
||||
description = fields.Text(help="Description and tooltips of the stage states.")
|
||||
|
||||
@api.depends('approval_template_ids.name')
|
||||
def _compute_approvals(self):
|
||||
for rec in self:
|
||||
rec.approval_roles = ', '.join(rec.approval_template_ids.mapped('name'))
|
||||
|
||||
@api.depends('approval_template_ids.approval_type')
|
||||
def _compute_is_blocking(self):
|
||||
for rec in self:
|
||||
rec.is_blocking = any(template.approval_type == 'mandatory' for template in rec.approval_template_ids)
|
||||
|
||||
|
||||
class MrpEco(models.Model):
|
||||
_name = 'mrp.eco'
|
||||
_description = 'Engineering Change Order (ECO)'
|
||||
_inherit = ['mail.thread.cc', 'mail.activity.mixin']
|
||||
|
||||
@api.model
|
||||
def _get_type_selection(self):
|
||||
return [
|
||||
('bom', _('Bill of Materials')),
|
||||
('product', _('Product Only'))]
|
||||
|
||||
name = fields.Char('Reference', copy=False, required=True)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user, tracking=True, check_company=True)
|
||||
type_id = fields.Many2one('mrp.eco.type', 'Type', required=True)
|
||||
stage_id = fields.Many2one(
|
||||
'mrp.eco.stage', 'Stage', ondelete='restrict', copy=False, domain="[('type_ids', 'in', type_id)]",
|
||||
group_expand='_read_group_stage_ids', tracking=True,
|
||||
default=lambda self: self.env['mrp.eco.stage'].search([('type_ids', 'in', self._context.get('default_type_id'))], limit=1))
|
||||
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
|
||||
tag_ids = fields.Many2many('mrp.eco.tag', string='Tags')
|
||||
priority = fields.Selection([
|
||||
('0', 'Normal'),
|
||||
('1', 'High')], string='Priority', tracking=True,
|
||||
index=True)
|
||||
note = fields.Html('Note')
|
||||
effectivity = fields.Selection([
|
||||
('asap', 'As soon as possible'),
|
||||
('date', 'At Date')], string='Effective', # Is this English ?
|
||||
compute='_compute_effectivity', inverse='_set_effectivity', store=True,
|
||||
help='Date on which the changes should be applied. For reference only.')
|
||||
effectivity_date = fields.Datetime('Effective Date', tracking=True, help="For reference only.")
|
||||
approval_ids = fields.One2many('mrp.eco.approval', 'eco_id', 'Approvals', help='Approvals by stage')
|
||||
|
||||
state = fields.Selection([
|
||||
('confirmed', 'To Do'),
|
||||
('progress', 'In Progress'),
|
||||
('rebase', 'Rebase'),
|
||||
('conflict', 'Conflict'),
|
||||
('done', 'Done')], string='Status',
|
||||
copy=False, default='confirmed', readonly=True, required=True)
|
||||
user_can_approve = fields.Boolean(
|
||||
'Can Approve', compute='_compute_user_approval',
|
||||
help='Technical field to check if approval by current user is required')
|
||||
user_can_reject = fields.Boolean(
|
||||
'Can Reject', compute='_compute_user_approval',
|
||||
help='Technical field to check if reject by current user is possible')
|
||||
kanban_state = fields.Selection([
|
||||
('normal', 'In Progress'),
|
||||
('done', 'Approved'),
|
||||
('blocked', 'Blocked')], string='Kanban State',
|
||||
copy=False, compute='_compute_kanban_state', store=True, readonly=False)
|
||||
legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', related_sudo=False)
|
||||
legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', related_sudo=False)
|
||||
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', related_sudo=False)
|
||||
kanban_state_label = fields.Char(compute='_compute_kanban_state_label', string='Kanban State Label', tracking=True)
|
||||
allow_change_kanban_state = fields.Boolean(
|
||||
'Allow Change Kanban State', compute='_compute_allow_change_kanban_state')
|
||||
allow_change_stage = fields.Boolean(
|
||||
'Allow Change Stage', compute='_compute_allow_change_stage')
|
||||
allow_apply_change = fields.Boolean(
|
||||
'Show Apply Change', compute='_compute_allow_apply_change')
|
||||
|
||||
product_tmpl_id = fields.Many2one('product.template', "Product", check_company=True)
|
||||
type = fields.Selection(selection=_get_type_selection, string='Apply on',
|
||||
default='bom', required=True)
|
||||
bom_id = fields.Many2one(
|
||||
'mrp.bom', "Bill of Materials",
|
||||
domain="[('product_tmpl_id', '=', product_tmpl_id)]", check_company=True) # Should at least have bom or routing on which it is applied?
|
||||
new_bom_id = fields.Many2one(
|
||||
'mrp.bom', 'New Bill of Materials',
|
||||
copy=False)
|
||||
new_bom_revision = fields.Integer('BoM Revision', related='new_bom_id.version', store=True, readonly=False)
|
||||
bom_change_ids = fields.One2many(
|
||||
'mrp.eco.bom.change', 'eco_id', string="ECO BoM Changes",
|
||||
compute='_compute_bom_change_ids', help='Difference between old BoM and new BoM revision', store=True)
|
||||
bom_rebase_ids = fields.One2many('mrp.eco.bom.change', 'rebase_id', string="BoM Rebase")
|
||||
routing_change_ids = fields.One2many(
|
||||
'mrp.eco.routing.change', 'eco_id', string="ECO Routing Changes",
|
||||
compute='_compute_routing_change_ids', help='Difference between old operation and new operation revision', store=True)
|
||||
mrp_document_count = fields.Integer('# Attachments', compute='_compute_attachments')
|
||||
mrp_document_ids = fields.One2many(
|
||||
'mrp.document', 'res_id', string='Attachments',
|
||||
auto_join=True, domain=lambda self: [('res_model', '=', self._name)])
|
||||
displayed_image_id = fields.Many2one(
|
||||
'mrp.document', 'Displayed Image',
|
||||
domain="[('res_model', '=', 'mrp.eco'), ('res_id', '=', id), ('mimetype', 'ilike', 'image')]")
|
||||
displayed_image_attachment_id = fields.Many2one('ir.attachment', related='displayed_image_id.ir_attachment_id', readonly=False)
|
||||
color = fields.Integer('Color')
|
||||
active = fields.Boolean('Active', default=True, help="If the active field is set to False, it will allow you to hide the engineering change order without removing it.")
|
||||
current_bom_id = fields.Many2one('mrp.bom', string="New Bom")
|
||||
previous_change_ids = fields.One2many('mrp.eco.bom.change', 'eco_rebase_id', string="Previous ECO Changes", compute='_compute_previous_bom_change', store=True)
|
||||
|
||||
def _compute_attachments(self):
|
||||
for p in self:
|
||||
p.mrp_document_count = len(p.mrp_document_ids)
|
||||
|
||||
@api.depends('effectivity_date')
|
||||
def _compute_effectivity(self):
|
||||
for eco in self:
|
||||
eco.effectivity = 'date' if eco.effectivity_date else 'asap'
|
||||
|
||||
def _set_effectivity(self):
|
||||
for eco in self:
|
||||
if eco.effectivity == 'asap':
|
||||
eco.effectivity_date = False
|
||||
|
||||
def _is_conflict(self, new_bom_lines, changes=None):
|
||||
# Find rebase lines having conflict or not.
|
||||
reb_conflicts = self.env['mrp.eco.bom.change']
|
||||
for reb_line in changes:
|
||||
new_line = new_bom_lines.get(reb_line.product_id, None)
|
||||
if new_line and (reb_line.old_operation_id, reb_line.old_uom_id, reb_line.old_product_qty) != (new_line.operation_id, new_line.product_uom_id, new_line.product_qty):
|
||||
reb_conflicts |= reb_line
|
||||
reb_conflicts.write({'conflict': True})
|
||||
return reb_conflicts
|
||||
|
||||
def _get_difference_bom_lines(self, old_bom, new_bom):
|
||||
# Return difference lines from two bill of material.
|
||||
new_bom_commands = [(5,)]
|
||||
old_bom_lines = dict(((line.product_id, tuple(line.bom_product_template_attribute_value_ids.ids), line.operation_id._get_comparison_values()), line) for line in old_bom.bom_line_ids)
|
||||
if self.new_bom_id:
|
||||
for line in new_bom.bom_line_ids:
|
||||
old_line = old_bom_lines.pop((line.product_id, tuple(line.bom_product_template_attribute_value_ids.ids), line.operation_id._get_comparison_values()), None)
|
||||
if old_line and (line.product_uom_id != old_line.product_uom_id or tools.float_compare(line.product_qty, old_line.product_qty, precision_rounding=line.product_uom_id.rounding)):
|
||||
new_bom_commands += [(0, 0, {
|
||||
'change_type': 'update',
|
||||
'product_id': line.product_id.id,
|
||||
'old_uom_id': old_line.product_uom_id.id,
|
||||
'new_uom_id': line.product_uom_id.id,
|
||||
'old_operation_id': old_line.operation_id.id,
|
||||
'new_operation_id': line.operation_id.id,
|
||||
'new_product_qty': line.product_qty,
|
||||
'old_product_qty': old_line.product_qty})]
|
||||
elif not old_line:
|
||||
new_bom_commands += [(0, 0, {
|
||||
'change_type': 'add',
|
||||
'product_id': line.product_id.id,
|
||||
'new_uom_id': line.product_uom_id.id,
|
||||
'new_operation_id': line.operation_id.id,
|
||||
'new_product_qty': line.product_qty
|
||||
})]
|
||||
for key, old_line in old_bom_lines.items():
|
||||
new_bom_commands += [(0, 0, {
|
||||
'change_type': 'remove',
|
||||
'product_id': old_line.product_id.id,
|
||||
'old_uom_id': old_line.product_uom_id.id,
|
||||
'old_operation_id': old_line.operation_id.id,
|
||||
'old_product_qty': old_line.product_qty,
|
||||
})]
|
||||
return new_bom_commands
|
||||
|
||||
def rebase(self, old_bom_lines, new_bom_lines, rebase_lines):
|
||||
"""
|
||||
This method will apply changes in new revision of BoM
|
||||
old_bom_lines : Previous BoM or Old BoM version lines.
|
||||
new_bom_lines : New BoM version lines.
|
||||
rebase_lines : Changes done in previous version
|
||||
"""
|
||||
for reb_line in rebase_lines:
|
||||
new_bom_line = new_bom_lines.get(reb_line.product_id, None)
|
||||
if new_bom_line:
|
||||
if new_bom_line.product_qty + reb_line.upd_product_qty > 0.0:
|
||||
# Update line if it exist in new bom.
|
||||
new_bom_line.write({'product_qty': new_bom_line.product_qty + reb_line.upd_product_qty, 'operation_id': reb_line.new_operation_id.id, 'product_uom_id': reb_line.new_uom_id.id})
|
||||
else:
|
||||
# Unlink lines if old bom removed lines
|
||||
new_bom_line.unlink()
|
||||
else:
|
||||
# Add bom line in new bom for rebase.
|
||||
old_line = old_bom_lines.get(reb_line.product_id, None)
|
||||
if old_line:
|
||||
old_line.copy({'bom_id': self.new_bom_id.id})
|
||||
return True
|
||||
|
||||
def apply_rebase(self):
|
||||
""" Apply rebase changes in new version of BoM """
|
||||
self.ensure_one()
|
||||
# Rebase logic applied..
|
||||
vals = {'state': 'progress'}
|
||||
if self.bom_rebase_ids:
|
||||
new_bom_lines = dict(((line.product_id), line) for line in self.new_bom_id.bom_line_ids)
|
||||
if self._is_conflict(new_bom_lines, self.bom_rebase_ids):
|
||||
return self.write({'state': 'conflict'})
|
||||
else:
|
||||
old_bom_lines = dict(((line.product_id), line) for line in self.bom_id.bom_line_ids)
|
||||
self.rebase(old_bom_lines, new_bom_lines, self.bom_rebase_ids)
|
||||
# Remove all rebase line of current eco.
|
||||
self.bom_rebase_ids.unlink()
|
||||
if self.previous_change_ids:
|
||||
new_bom_lines = dict(((line.product_id), line) for line in self.new_bom_id.bom_line_ids)
|
||||
if self._is_conflict(new_bom_lines, self.previous_change_ids):
|
||||
return self.write({'state': 'conflict'})
|
||||
else:
|
||||
new_activated_bom_lines = dict(((line.product_id), line) for line in self.current_bom_id.bom_line_ids)
|
||||
self.rebase(new_activated_bom_lines, new_bom_lines, self.previous_change_ids)
|
||||
# Remove all rebase line of current eco.
|
||||
self.previous_change_ids.unlink()
|
||||
if self.current_bom_id:
|
||||
self.new_bom_id.write({'version': self.current_bom_id.version + 1, 'previous_bom_id': self.current_bom_id.id})
|
||||
vals.update({'bom_id': self.current_bom_id.id, 'current_bom_id': False})
|
||||
self.message_post(body=_('Successfully Rebased !'))
|
||||
return self.write(vals)
|
||||
|
||||
@api.depends('bom_id.bom_line_ids', 'new_bom_id.bom_line_ids', 'new_bom_id.bom_line_ids.product_qty', 'new_bom_id.bom_line_ids.product_uom_id', 'new_bom_id.bom_line_ids.operation_id')
|
||||
def _compute_bom_change_ids(self):
|
||||
# Compute difference between old bom and new bom revision.
|
||||
for eco in self:
|
||||
eco.bom_change_ids = eco._get_difference_bom_lines(eco.bom_id, eco.new_bom_id)
|
||||
|
||||
@api.depends('bom_id.bom_line_ids', 'current_bom_id.bom_line_ids', 'current_bom_id.bom_line_ids.product_qty', 'current_bom_id.bom_line_ids.product_uom_id', 'current_bom_id.bom_line_ids.operation_id')
|
||||
def _compute_previous_bom_change(self):
|
||||
for eco in self:
|
||||
if eco.current_bom_id:
|
||||
# Compute difference between old bom and newly activated bom.
|
||||
eco.previous_change_ids = eco._get_difference_bom_lines(eco.bom_id, eco.current_bom_id)
|
||||
else:
|
||||
eco.previous_change_ids = False
|
||||
|
||||
@api.depends('bom_id.operation_ids', 'bom_id.operation_ids.active', 'new_bom_id.operation_ids', 'new_bom_id.operation_ids.active')
|
||||
def _compute_routing_change_ids(self):
|
||||
for rec in self:
|
||||
if rec.state == 'confirmed' or rec.type == 'product':
|
||||
continue
|
||||
new_routing_commands = [Command.clear()]
|
||||
old_routing_lines = defaultdict(lambda: self.env['mrp.routing.workcenter'])
|
||||
# Two operations could have the same values so we save them with the same key
|
||||
for op in rec.bom_id.operation_ids:
|
||||
old_routing_lines[op._get_comparison_values()] |= op
|
||||
if rec.new_bom_id and rec.bom_id:
|
||||
for operation in rec.new_bom_id.operation_ids:
|
||||
key = (operation._get_comparison_values())
|
||||
old_op = old_routing_lines[key][:1]
|
||||
if old_op:
|
||||
old_routing_lines[key] -= old_op
|
||||
if tools.float_compare(old_op.time_cycle_manual, operation.time_cycle_manual, 2) != 0:
|
||||
new_routing_commands += [Command.create({
|
||||
'change_type': 'update',
|
||||
'workcenter_id': operation.workcenter_id.id,
|
||||
'new_time_cycle_manual': operation.time_cycle_manual,
|
||||
'old_time_cycle_manual': old_op.time_cycle_manual,
|
||||
'operation_id': operation.id,
|
||||
})]
|
||||
new_routing_commands += self._prepare_detailed_change_commands(operation, old_op)
|
||||
else:
|
||||
new_routing_commands += [Command.create({
|
||||
'change_type': 'add',
|
||||
'workcenter_id': operation.workcenter_id.id,
|
||||
'new_time_cycle_manual': operation.time_cycle_manual,
|
||||
'operation_id': operation.id,
|
||||
})]
|
||||
new_routing_commands += self._prepare_detailed_change_commands(operation, None)
|
||||
for old_ops in old_routing_lines.values():
|
||||
for old_op in old_ops:
|
||||
new_routing_commands += [(0, 0, {
|
||||
'change_type': 'remove',
|
||||
'workcenter_id': old_op.workcenter_id.id,
|
||||
'old_time_cycle_manual': old_op.time_cycle_manual,
|
||||
'operation_id': old_op.id,
|
||||
})]
|
||||
rec.routing_change_ids = new_routing_commands
|
||||
|
||||
def _prepare_detailed_change_commands(self, new, old):
|
||||
"""Necessary for overrides to track change of quality checks"""
|
||||
return []
|
||||
|
||||
def _compute_user_approval(self):
|
||||
for eco in self:
|
||||
is_required_approval = eco.stage_id.approval_template_ids.filtered(lambda x: x.approval_type in ('mandatory', 'optional') and self.env.user in x.user_ids)
|
||||
user_approvals = eco.approval_ids.filtered(lambda x: x.template_stage_id == eco.stage_id and x.user_id == self.env.user and not x.is_closed)
|
||||
last_approval = user_approvals.sorted(lambda a : a.create_date, reverse=True)[:1]
|
||||
eco.user_can_approve = is_required_approval and not last_approval.is_approved
|
||||
eco.user_can_reject = is_required_approval and not last_approval.is_rejected
|
||||
|
||||
@api.depends('stage_id', 'approval_ids.is_approved', 'approval_ids.is_rejected')
|
||||
def _compute_kanban_state(self):
|
||||
""" State of ECO is based on the state of approvals for the current stage. """
|
||||
for rec in self:
|
||||
approvals = rec.approval_ids.filtered(lambda app:
|
||||
app.template_stage_id == rec.stage_id and not app.is_closed)
|
||||
if not approvals:
|
||||
rec.kanban_state = 'normal'
|
||||
elif all(approval.is_approved for approval in approvals):
|
||||
rec.kanban_state = 'done'
|
||||
elif any(approval.is_rejected for approval in approvals):
|
||||
rec.kanban_state = 'blocked'
|
||||
else:
|
||||
rec.kanban_state = 'normal'
|
||||
|
||||
@api.depends('kanban_state', 'stage_id', 'approval_ids')
|
||||
def _compute_allow_change_stage(self):
|
||||
for rec in self:
|
||||
approvals = rec.approval_ids.filtered(lambda app: app.template_stage_id == rec.stage_id)
|
||||
if approvals:
|
||||
rec.allow_change_stage = rec.kanban_state == 'done'
|
||||
else:
|
||||
rec.allow_change_stage = rec.kanban_state in ['normal', 'done']
|
||||
|
||||
@api.depends('state', 'stage_id.allow_apply_change')
|
||||
def _compute_allow_apply_change(self):
|
||||
for rec in self:
|
||||
rec.allow_apply_change = rec.stage_id.allow_apply_change and rec.state in ('confirmed', 'progress')
|
||||
|
||||
@api.depends('stage_id.approval_template_ids')
|
||||
def _compute_allow_change_kanban_state(self):
|
||||
for rec in self:
|
||||
rec.allow_change_kanban_state = False if rec.stage_id.approval_template_ids else True
|
||||
|
||||
@api.depends('stage_id', 'kanban_state')
|
||||
def _compute_kanban_state_label(self):
|
||||
for eco in self:
|
||||
if eco.kanban_state == 'normal':
|
||||
eco.kanban_state_label = eco.legend_normal
|
||||
elif eco.kanban_state == 'blocked':
|
||||
eco.kanban_state_label = eco.legend_blocked
|
||||
else:
|
||||
eco.kanban_state_label = eco.legend_done
|
||||
|
||||
@api.onchange('product_tmpl_id')
|
||||
def onchange_product_tmpl_id(self):
|
||||
if self.product_tmpl_id.bom_ids:
|
||||
self.bom_id = self.product_tmpl_id.bom_ids.ids[0]
|
||||
|
||||
@api.onchange('type_id')
|
||||
def onchange_type_id(self):
|
||||
self.stage_id = self.env['mrp.eco.stage'].search([('type_ids', 'in', self.type_id.id)], limit=1).id
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
prefix = self.env['ir.sequence'].next_by_code('mrp.eco') or ''
|
||||
vals['name'] = '%s%s' % (prefix and '%s: ' % prefix or '', vals.get('name', ''))
|
||||
ecos = super().create(vals_list)
|
||||
ecos._create_approvals()
|
||||
return ecos
|
||||
|
||||
def write(self, vals):
|
||||
if vals.get('stage_id'):
|
||||
newstage = self.env['mrp.eco.stage'].browse(vals['stage_id'])
|
||||
# raise exception only if we increase the stage, not on decrease
|
||||
for eco in self:
|
||||
if eco.stage_id and ((newstage.sequence, newstage.id) > (eco.stage_id.sequence, eco.stage_id.id)):
|
||||
if not eco.allow_change_stage:
|
||||
raise UserError(_('You cannot change the stage, as approvals are still required.'))
|
||||
has_blocking_stages = self.env['mrp.eco.stage'].search_count([
|
||||
('sequence', '>=', eco.stage_id.sequence),
|
||||
('sequence', '<=', newstage.sequence),
|
||||
('type_ids', 'in', eco.type_id.id),
|
||||
('id', 'not in', [eco.stage_id.id] + [vals['stage_id']]),
|
||||
('is_blocking', '=', True)])
|
||||
if has_blocking_stages:
|
||||
raise UserError(_('You cannot change the stage, as approvals are required in the process.'))
|
||||
if eco.stage_id != newstage:
|
||||
eco.approval_ids.filtered(lambda x: x.status != 'none').write({'is_closed': True})
|
||||
eco.approval_ids.filtered(lambda x: x.status == 'none').unlink()
|
||||
if 'displayed_image_attachment_id' in vals:
|
||||
doc = False
|
||||
if vals['displayed_image_attachment_id']:
|
||||
doc = self.env['mrp.document'].search([('ir_attachment_id', '=', vals['displayed_image_attachment_id'])])
|
||||
if not doc:
|
||||
doc = self.env['mrp.document'].create([{'ir_attachment_id': vals['displayed_image_attachment_id']}])
|
||||
vals.pop('displayed_image_attachment_id')
|
||||
vals['displayed_image_id'] = doc
|
||||
res = super(MrpEco, self).write(vals)
|
||||
if vals.get('stage_id'):
|
||||
self._create_approvals()
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _read_group_stage_ids(self, stages, domain, order):
|
||||
""" Read group customization in order to display all the stages of the ECO type
|
||||
in the Kanban view, even if there is no ECO in that stage
|
||||
"""
|
||||
search_domain = []
|
||||
if self._context.get('default_type_ids'):
|
||||
search_domain = [('type_ids', 'in', self._context['default_type_ids'])]
|
||||
|
||||
stage_ids = stages._search(search_domain, order=order, access_rights_uid=SUPERUSER_ID)
|
||||
return stages.browse(stage_ids)
|
||||
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, **kwargs):
|
||||
message = super(MrpEco, self).message_post(**kwargs)
|
||||
if message.message_type == 'comment' and message.author_id == self.env.user.partner_id: # should use message_values to avoid a read
|
||||
for eco in self:
|
||||
for approval in eco.approval_ids.filtered(lambda app: app.template_stage_id == eco.stage_id and app.status == 'none' and app.approval_template_id.approval_type == 'comment'):
|
||||
if self.env.user in approval.approval_template_id.user_ids:
|
||||
approval.write({
|
||||
'status': 'comment',
|
||||
'user_id': self.env.uid
|
||||
})
|
||||
return message
|
||||
|
||||
def _create_approvals(self):
|
||||
approval_vals = []
|
||||
activity_vals = []
|
||||
for eco in self:
|
||||
for approval_template in eco.stage_id.approval_template_ids:
|
||||
approval = eco.approval_ids.filtered(lambda app: app.approval_template_id == approval_template and not app.is_closed)
|
||||
if not approval:
|
||||
approval_vals.append({
|
||||
'eco_id': eco.id,
|
||||
'approval_template_id': approval_template.id,
|
||||
})
|
||||
for user in approval_template.user_ids:
|
||||
activity_vals.append({
|
||||
'activity_type_id': self.env.ref('mrp_plm.mail_activity_eco_approval').id,
|
||||
'user_id': user.id,
|
||||
'res_id': eco.id,
|
||||
'res_model_id': self.env.ref('mrp_plm.model_mrp_eco').id,
|
||||
})
|
||||
self.env['mrp.eco.approval'].create(approval_vals)
|
||||
self.env['mail.activity'].create(activity_vals)
|
||||
|
||||
def _create_or_update_approval(self, status):
|
||||
for eco in self:
|
||||
for approval_template in eco.stage_id.approval_template_ids.filtered(lambda a: self.env.user in a.user_ids):
|
||||
approvals = eco.approval_ids.filtered(lambda x: x.approval_template_id == approval_template and not x.is_closed)
|
||||
none_approvals = approvals.filtered(lambda a: a.status =='none')
|
||||
confirmed_approvals = approvals - none_approvals
|
||||
if none_approvals:
|
||||
none_approvals.write({'status': status, 'user_id': self.env.uid, 'approval_date': fields.Datetime.now()})
|
||||
confirmed_approvals.write({'is_closed': True})
|
||||
approval = none_approvals[:1]
|
||||
else:
|
||||
approvals.write({'is_closed': True})
|
||||
approval = self.env['mrp.eco.approval'].create({
|
||||
'eco_id': eco.id,
|
||||
'approval_template_id': approval_template.id,
|
||||
'status': status,
|
||||
'user_id': self.env.uid,
|
||||
'approval_date': fields.Datetime.now(),
|
||||
})
|
||||
|
||||
message = escape(_("%(approval_name)s %(approver_name)s %(approval_status)s this ECO")) % {
|
||||
'approval_name': approval.name,
|
||||
'approver_name': approval.user_id.name,
|
||||
'approval_status': approval.status,
|
||||
}
|
||||
eco.message_post(body=message, subtype_xmlid='mail.mt_comment')
|
||||
|
||||
def approve(self):
|
||||
self._create_or_update_approval(status='approved')
|
||||
|
||||
def reject(self):
|
||||
self._create_or_update_approval(status='rejected')
|
||||
|
||||
def conflict_resolve(self):
|
||||
self.ensure_one()
|
||||
vals = {'state': 'progress'}
|
||||
if self.current_bom_id:
|
||||
vals.update({'bom_id': self.current_bom_id.id, 'current_bom_id': False})
|
||||
self.write(vals)
|
||||
# Set previous BoM on new revision and change version of BoM.
|
||||
self.new_bom_id.write({'version': self.bom_id.version + 1, 'previous_bom_id': self.bom_id.id})
|
||||
# Remove all rebase lines.
|
||||
rebase_lines = self.bom_rebase_ids + self.previous_change_ids
|
||||
rebase_lines.unlink()
|
||||
return True
|
||||
|
||||
def action_new_revision(self):
|
||||
IrAttachment = self.env['ir.attachment']
|
||||
for eco in self:
|
||||
if eco.type == 'bom':
|
||||
eco.new_bom_id = eco.bom_id.sudo().copy(default={
|
||||
'version': eco.bom_id.version + 1,
|
||||
'active': False,
|
||||
'previous_bom_id': eco.bom_id.id,
|
||||
})
|
||||
attachments = IrAttachment.search([('res_model', '=', 'mrp.bom'),
|
||||
('res_id', '=', eco.bom_id.id)])
|
||||
else:
|
||||
attachments = IrAttachment.search([('res_model', '=', 'product.template'),
|
||||
('res_id', '=', eco.product_tmpl_id.id)])
|
||||
for attach in attachments:
|
||||
new_attach = attach.copy({'res_model': 'mrp.eco', 'res_id': eco.id})
|
||||
self.env['mrp.document'].create({'ir_attachment_id': new_attach.id, 'origin_attachment_id': attach.id})
|
||||
self.write({'state': 'progress'})
|
||||
|
||||
def action_apply(self):
|
||||
self._check_company()
|
||||
eco_need_action = self.env['mrp.eco']
|
||||
for eco in self:
|
||||
if eco.state == 'done':
|
||||
continue
|
||||
if eco.state == 'rebase':
|
||||
eco.apply_rebase()
|
||||
if eco.allow_apply_change:
|
||||
if eco.type == 'product':
|
||||
for attach in eco.with_context(active_test=False).mrp_document_ids:
|
||||
origin = attach.origin_attachment_id
|
||||
if not attach.active:
|
||||
origin.unlink()
|
||||
continue
|
||||
if origin._compute_checksum(origin.raw) == origin._compute_checksum(attach.raw):
|
||||
if attach.origin_attachment_id.name != attach.name:
|
||||
attach.origin_attachment_id.name = attach.name
|
||||
if attach.origin_attachment_id.company_id != attach.company_id:
|
||||
attach.origin_attachment_id.company_id = attach.company_id
|
||||
continue
|
||||
attach.ir_attachment_id.copy({
|
||||
'res_model': 'product.template',
|
||||
'res_id': eco.product_tmpl_id.id,
|
||||
})
|
||||
eco.product_tmpl_id.version = eco.product_tmpl_id.version + 1
|
||||
else:
|
||||
eco.mapped('new_bom_id').apply_new_version()
|
||||
for attach in eco.mrp_document_ids:
|
||||
attach.ir_attachment_id.copy({
|
||||
'res_model': 'mrp.bom',
|
||||
'res_id': eco.new_bom_id.id,
|
||||
})
|
||||
vals = {'state': 'done'}
|
||||
stage_id = eco.env['mrp.eco.stage'].search([
|
||||
('final_stage', '=', True),
|
||||
('type_ids', 'in', eco.type_id.id)], limit=1).id
|
||||
if stage_id:
|
||||
vals['stage_id'] = stage_id
|
||||
eco.write(vals)
|
||||
else:
|
||||
eco_need_action |= eco
|
||||
if eco_need_action:
|
||||
return {
|
||||
'name': _('Eco'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'tree, form',
|
||||
'views': [[False, 'tree'], [False, 'form']],
|
||||
'res_model': 'mrp.eco',
|
||||
'target': 'current',
|
||||
'domain': [('id', 'in', eco_need_action.ids)],
|
||||
'context': {'search_default_changetoapply': False},
|
||||
}
|
||||
|
||||
def action_see_attachments(self):
|
||||
self.ensure_one()
|
||||
domain = ['&', ('res_model', '=', self._name), ('res_id', '=', self.id)]
|
||||
attachment_view = self.env.ref('mrp_plm.view_document_file_kanban_mrp_plm')
|
||||
context = {
|
||||
'default_res_model': self._name,
|
||||
'default_res_id': self.id,
|
||||
'default_company_id': self.company_id.id,
|
||||
'search_default_all': 1,
|
||||
'create': self.state != 'done',
|
||||
'edit': self.state != 'done',
|
||||
'delete': self.state != 'done',
|
||||
}
|
||||
return {
|
||||
'name': _('Attachments'),
|
||||
'domain': domain,
|
||||
'res_model': 'mrp.document',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_id': attachment_view.id,
|
||||
'views': [(attachment_view.id, 'kanban'), (False, 'form')],
|
||||
'view_mode': 'kanban,tree,form',
|
||||
'help': _('''<p class="o_view_nocontent_smiling_face">
|
||||
Upload files to your ECO, that will be applied to the product later
|
||||
</p><p>
|
||||
Use this feature to store any files, like drawings or specifications.
|
||||
</p>'''),
|
||||
'limit': 80,
|
||||
'context': context,
|
||||
}
|
||||
|
||||
def open_new_bom(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Eco BoM'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mrp.bom',
|
||||
'target': 'current',
|
||||
'res_id': self.new_bom_id.id,
|
||||
'context': {
|
||||
'default_product_tmpl_id': self.product_tmpl_id.id,
|
||||
'default_product_id': self.product_tmpl_id.product_variant_id.id,
|
||||
'create': self.state != 'done',
|
||||
'edit': self.state != 'done',
|
||||
'delete': self.state != 'done',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class MrpEcoBomChange(models.Model):
|
||||
_name = 'mrp.eco.bom.change'
|
||||
_description = 'ECO BoM changes'
|
||||
|
||||
eco_id = fields.Many2one('mrp.eco', 'Engineering Change', ondelete='cascade')
|
||||
eco_rebase_id = fields.Many2one('mrp.eco', 'ECO Rebase', ondelete='cascade')
|
||||
rebase_id = fields.Many2one('mrp.eco', 'Rebase', ondelete='cascade')
|
||||
change_type = fields.Selection([('add', 'Add'), ('remove', 'Remove'), ('update', 'Update')], string='Type', required=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', required=True)
|
||||
old_uom_id = fields.Many2one('uom.uom', 'Previous Product UoM')
|
||||
new_uom_id = fields.Many2one('uom.uom', 'New Product UoM')
|
||||
old_product_qty = fields.Float('Previous revision quantity', default=0)
|
||||
new_product_qty = fields.Float('New revision quantity', default=0)
|
||||
old_operation_id = fields.Many2one('mrp.routing.workcenter', 'Previous Consumed in Operation')
|
||||
new_operation_id = fields.Many2one('mrp.routing.workcenter', 'New Consumed in Operation')
|
||||
upd_product_qty = fields.Float('Quantity', compute='_compute_change', store=True)
|
||||
uom_change = fields.Char('Unit of Measure', compute='_compute_change', compute_sudo=True)
|
||||
operation_change = fields.Char(compute='_compute_change', string='Consumed in Operation', compute_sudo=True)
|
||||
conflict = fields.Boolean()
|
||||
|
||||
@api.depends('new_product_qty', 'old_product_qty', 'old_operation_id', 'new_operation_id', 'old_uom_id', 'new_uom_id')
|
||||
def _compute_change(self):
|
||||
for rec in self:
|
||||
rec.upd_product_qty = rec.new_product_qty - rec.old_product_qty
|
||||
rec.operation_change = rec.new_operation_id.name if rec.change_type == 'add' else rec.old_operation_id.name
|
||||
rec.uom_change = False
|
||||
if (rec.old_uom_id and rec.new_uom_id) and rec.old_uom_id != rec.new_uom_id:
|
||||
rec.uom_change = rec.old_uom_id.name + ' -> ' + rec.new_uom_id.name
|
||||
if (rec.old_operation_id._get_comparison_values() != rec.new_operation_id._get_comparison_values()) and rec.change_type == 'update':
|
||||
rec.operation_change = (rec.old_operation_id.name or '') + ' -> ' + (rec.new_operation_id.name or '')
|
||||
|
||||
|
||||
class MrpEcoRoutingChange(models.Model):
|
||||
_name = 'mrp.eco.routing.change'
|
||||
_description = 'Eco Routing changes'
|
||||
|
||||
eco_id = fields.Many2one('mrp.eco', 'Engineering Change', ondelete='cascade', required=True)
|
||||
change_type = fields.Selection([('add', 'Add'), ('remove', 'Remove'), ('update', 'Update')], string='Type', required=True)
|
||||
workcenter_id = fields.Many2one('mrp.workcenter', 'Work Center')
|
||||
old_time_cycle_manual = fields.Float('Old manual duration', default=0)
|
||||
new_time_cycle_manual = fields.Float('New manual duration', default=0)
|
||||
upd_time_cycle_manual = fields.Float('Manual Duration Change', compute='_compute_upd_time_cycle_manual', store=True)
|
||||
operation_id = fields.Many2one('mrp.routing.workcenter', 'New or Previous Operation')
|
||||
operation_name = fields.Char(related='operation_id.name', string='Operation')
|
||||
|
||||
@api.depends('new_time_cycle_manual', 'old_time_cycle_manual')
|
||||
def _compute_upd_time_cycle_manual(self):
|
||||
for rec in self:
|
||||
rec.upd_time_cycle_manual = rec.new_time_cycle_manual - rec.old_time_cycle_manual
|
||||
|
||||
|
||||
class MrpEcoTag(models.Model):
|
||||
_name = "mrp.eco.tag"
|
||||
_description = "ECO Tags"
|
||||
|
||||
def _get_default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
name = fields.Char('Tag Name', required=True)
|
||||
color = fields.Integer('Color Index', default=_get_default_color)
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
||||
]
|
||||
33
mrp_plm/models/mrp_production.py
Normal file
33
mrp_plm/models/mrp_production.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
latest_bom_id = fields.Many2one('mrp.bom', compute="_compute_latest_bom_id")
|
||||
|
||||
@api.depends('bom_id', 'bom_id.active')
|
||||
def _compute_latest_bom_id(self):
|
||||
self.latest_bom_id = False
|
||||
# check if the bom has a new version
|
||||
for mo in self:
|
||||
if mo.bom_id and not mo.bom_id.active:
|
||||
mo.latest_bom_id = mo.bom_id._get_active_version()
|
||||
# check if the components have a new version
|
||||
mo_to_update = self.search([
|
||||
('id', 'in', self.filtered(lambda p: not p.latest_bom_id).ids),
|
||||
('move_raw_ids.bom_line_id.bom_id.active', '=', False)
|
||||
])
|
||||
for mo in mo_to_update:
|
||||
mo.latest_bom_id = mo.bom_id
|
||||
|
||||
def action_update_bom(self):
|
||||
for production in self:
|
||||
if production.state != 'draft' or not production.latest_bom_id:
|
||||
continue
|
||||
latest_bom = production.latest_bom_id
|
||||
(production.move_finished_ids | production.move_raw_ids).unlink()
|
||||
production.workorder_ids.unlink()
|
||||
production.write({'bom_id': latest_bom.id})
|
||||
15
mrp_plm/models/product.py
Normal file
15
mrp_plm/models/product.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
version = fields.Integer('Version', default=1, readonly=True, copy=False, help="The current version of the product.")
|
||||
eco_count = fields.Integer('# ECOs',compute='_compute_eco_count')
|
||||
eco_ids = fields.One2many('mrp.eco', 'product_tmpl_id', 'ECOs')
|
||||
|
||||
def _compute_eco_count(self):
|
||||
for p in self:
|
||||
p.eco_count = len(p.eco_ids)
|
||||
4
mrp_plm/report/__init__.py
Normal file
4
mrp_plm/report/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mrp_report_bom_structure
|
||||
46
mrp_plm/report/mrp_report_bom_structure.py
Normal file
46
mrp_plm/report/mrp_report_bom_structure.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ReportBomStructure(models.AbstractModel):
|
||||
_inherit = 'report.mrp.report_bom_structure'
|
||||
|
||||
def _get_pdf_doc(self, bom_id, data, quantity, product_variant_id=None):
|
||||
doc = super()._get_pdf_doc(bom_id, data, quantity, product_variant_id)
|
||||
doc['show_ecos'] = True if data and data.get('show_ecos') == 'true' and self.env.user.user_has_groups('mrp_plm.group_plm_user') else False
|
||||
return doc
|
||||
|
||||
def _get_report_data(self, bom_id, searchQty=0, searchVariant=False):
|
||||
res = super()._get_report_data(bom_id, searchQty, searchVariant)
|
||||
res['is_eco_applied'] = self.env.user.user_has_groups('mrp_plm.group_plm_user')
|
||||
return res
|
||||
|
||||
def _get_bom_data(self, bom, warehouse, product=False, line_qty=False, bom_line=False, level=0, parent_bom=False, index=0, product_info=False, ignore_stock=False):
|
||||
res = super()._get_bom_data(bom, warehouse, product, line_qty, bom_line, level, parent_bom, index, product_info, ignore_stock)
|
||||
if self.env.user.user_has_groups('mrp_plm.group_plm_user'):
|
||||
res['version'] = res['bom'] and res['bom'].version or ''
|
||||
product_tmpl_id = (res['product'] and res['product'].product_tmpl_id.id) or (res['bom'] and res['product'].product_tmpl_id.id)
|
||||
res['ecos'] = self.env['mrp.eco'].search_count([('product_tmpl_id', '=', product_tmpl_id), ('state', '!=', 'done')]) or ''
|
||||
return res
|
||||
|
||||
def _get_component_data(self, bom, warehouse, bom_line, line_quantity, level, index, product_info, ignore_stock=False):
|
||||
res = super()._get_component_data(bom, warehouse, bom_line, line_quantity, level, index, product_info, ignore_stock)
|
||||
if self.env.user.user_has_groups('mrp_plm.group_plm_user'):
|
||||
res['version'] = False
|
||||
res['ecos'] = self.env['mrp.eco'].search_count([('product_tmpl_id', '=', res['product'].product_tmpl_id.id), ('state', '!=', 'done')]) or ''
|
||||
return res
|
||||
|
||||
def _get_bom_array_lines(self, data, level, unfolded_ids, unfolded, parent_unfolded):
|
||||
lines = super()._get_bom_array_lines(data, level, unfolded_ids, unfolded, parent_unfolded)
|
||||
if not self.env.user.user_has_groups('mrp_plm.group_plm_user'):
|
||||
return lines
|
||||
for component in data.get('components', []):
|
||||
if not component['bom_id']:
|
||||
continue
|
||||
bom_line = next(filter(lambda l: l.get('bom_id', None) == component['bom_id'], lines))
|
||||
if bom_line:
|
||||
bom_line['version'] = component['version']
|
||||
bom_line['ecos'] = component['ecos']
|
||||
|
||||
return lines
|
||||
38
mrp_plm/report/mrp_report_bom_structure.xml
Normal file
38
mrp_plm/report/mrp_report_bom_structure.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<odoo>
|
||||
<template id="report_mrp_bom_inherit_mrp_plm" inherit_id="mrp.report_mrp_bom">
|
||||
<xpath expr="//th[@name='th_mrp_bom_h']" position="after">
|
||||
<th t-if="data['show_ecos']">BoM Version</th>
|
||||
<th t-if="data['show_ecos']">ECOs</th>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//td[@name='td_mrp_bom']" position="after">
|
||||
<td t-if="data['show_ecos']">
|
||||
<span t-esc="data['version']"/>
|
||||
</td>
|
||||
<td t-if="data['show_ecos']">
|
||||
<span t-esc="data['ecos']"/>
|
||||
</td>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//td[@name='td_mrp_bom_f']" position="after">
|
||||
<td t-if="data['show_ecos']"/>
|
||||
<td t-if="data['show_ecos']"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//td[@name='td_mrp_bom_byproducts_f']" position="after">
|
||||
<td t-if="data['show_ecos']"/>
|
||||
<td t-if="data['show_ecos']"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="report_mrp_bom_pdf_line_inherit_mrp_plm" inherit_id="mrp.report_mrp_bom_pdf_line">
|
||||
<xpath expr="//td[@name='td_mrp_code']" position="after">
|
||||
<td t-if="data['show_ecos']">
|
||||
<span t-if="l.get('version')" t-esc="l['version']"/>
|
||||
</td>
|
||||
<td t-if="data['show_ecos']">
|
||||
<span t-if="l.get('ecos')" t-esc="l['ecos']"/>
|
||||
</td>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
14
mrp_plm/security/ir.model.access.csv
Normal file
14
mrp_plm/security/ir.model.access.csv
Normal file
@@ -0,0 +1,14 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_model_mrp_eco_approval_template_manager,mrp.eco.approval.template,mrp_plm.model_mrp_eco_approval_template,mrp_plm.group_plm_manager,1,1,1,1
|
||||
access_model_mrp_eco_approval_template_user,mrp.eco.approval.template,mrp_plm.model_mrp_eco_approval_template,mrp_plm.group_plm_user,1,0,0,0
|
||||
access_model_mrp_eco_approval_user,mrp.eco.approval,mrp_plm.model_mrp_eco_approval,mrp_plm.group_plm_user,1,1,1,1
|
||||
access_model_mrp_eco_stage_manager,mrp.eco.stage,mrp_plm.model_mrp_eco_stage,mrp_plm.group_plm_manager,1,1,1,1
|
||||
access_model_mrp_eco_stage_user,mrp.eco.stage,mrp_plm.model_mrp_eco_stage,mrp_plm.group_plm_user,1,0,0,0
|
||||
access_model_mrp_eco_bom_change_user,mrp.eco.bom.line,mrp_plm.model_mrp_eco_bom_change,mrp_plm.group_plm_user,1,1,1,1
|
||||
access_model_mrp_eco_routing_change_user,mrp.eco.routing.line,mrp_plm.model_mrp_eco_routing_change,mrp_plm.group_plm_user,1,1,1,1
|
||||
access_mrp_eco_tag_user,mrp.eco.tag,mrp_plm.model_mrp_eco_tag,mrp_plm.group_plm_user,1,1,1,1
|
||||
access_mrp_eco_tag_manager,mrp.eco.tag,mrp_plm.model_mrp_eco_tag,mrp_plm.group_plm_manager,1,1,1,1
|
||||
access_mrp_eco_type_user,mrp.eco.type,mrp_plm.model_mrp_eco_type,mrp_plm.group_plm_user,1,0,0,0
|
||||
access_mrp_eco_type_manager,mrp.eco.type,mrp_plm.model_mrp_eco_type,mrp_plm.group_plm_manager,1,1,1,1
|
||||
access_mrp_eco_user,mrp.eco,mrp_plm.model_mrp_eco,mrp_plm.group_plm_user,1,1,1,1
|
||||
access_product_template_plm_manager,product.template plm_manager,product.model_product_template,mrp_plm.group_plm_manager,1,1,1,1
|
||||
|
45
mrp_plm/security/mrp_plm.xml
Normal file
45
mrp_plm/security/mrp_plm.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- This group is meant to manage PLM stages -->
|
||||
<record model="ir.module.category" id="module_category_manufacturing_product_lifecycle_management_(plm)">
|
||||
<field name="name">PLM</field>
|
||||
<field name="description">Helps you manage your product's lifecycles.</field>
|
||||
<field name="sequence">5</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_plm.group_plm_user" model="res.groups">
|
||||
<field name="name">User</field>
|
||||
<field name="category_id" ref="base.module_category_manufacturing_product_lifecycle_management_(plm)"/>
|
||||
<field name="implied_ids" eval="[(4, ref('mrp.group_mrp_user'))]"/>
|
||||
<field name="comment">The PLM user uses products lifecycle management</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_plm.group_plm_manager" model="res.groups">
|
||||
<field name="name">Administrator</field>
|
||||
<field name="category_id" ref="base.module_category_manufacturing_product_lifecycle_management_(plm)"/>
|
||||
<field name="implied_ids" eval="[(4, ref('mrp_plm.group_plm_user'))]"/>
|
||||
<field name="comment">The PLM manager manages products lifecycle management</field>
|
||||
</record>
|
||||
|
||||
<record id="base.default_user" model="res.users">
|
||||
<field name="groups_id" eval="[(4, ref('mrp_plm.group_plm_manager'))]"/>
|
||||
</record>
|
||||
|
||||
<record model="res.users" id="base.user_root">
|
||||
<field eval="[(4,ref('mrp_plm.group_plm_manager'))]" name="groups_id"/>
|
||||
</record>
|
||||
|
||||
<record model="res.users" id="base.user_admin">
|
||||
<field eval="[(4,ref('mrp_plm.group_plm_manager'))]" name="groups_id"/>
|
||||
</record>
|
||||
|
||||
<data noupdate="1">
|
||||
|
||||
<record model="ir.rule" id="mrp_eco_comp_rule">
|
||||
<field name="name">Manufacturing ECO company rule</field>
|
||||
<field name="model_id" ref="model_mrp_eco"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
BIN
mrp_plm/static/description/icon.png
Normal file
BIN
mrp_plm/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
1
mrp_plm/static/description/icon.svg
Normal file
1
mrp_plm/static/description/icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#7CC098"/><stop offset="100%" stop-color="#5F8A71"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M42.127 69H4c-2 0-4-1-4-4V35l15.895-17.19C25.228 8.478 38 6.334 50 15c8.415 6.077 12.076 13.466 10.984 22.165-.035.285-.1.714-.194 1.29a24.755 24.755 0 0 1-.304 1.352C60.099 41.274 58.936 44.338 57 49L42.127 69z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#000" d="M35.722 27L47 30.846v10.385L38.056 47 26 41.23V30.847L35.722 27zm-.381 1L28 31.111 38.045 35 45 31.5 35.34 28zM27 32v8.944L38 46v-9.722L27 32zm-.615-24l-5.9 8.058 9.51 3.036-1.28-3.953a22.208 22.208 0 0 1 7.2-1.203c11.135 0 20.316 8.182 21.904 18.866l3.228-2.94c-2.799-11.282-12.995-19.668-25.132-19.668a25.8 25.8 0 0 0-8.365 1.394L26.385 8zm-8.44 9.452C13.046 22.167 10 28.769 10 36.088c0 7.763 3.434 14.745 8.861 19.496l-2.54 2.807 9.93 1.07-2.12-9.739-2.768 3.074a22.075 22.075 0 0 1-7.6-16.708 22.073 22.073 0 0 1 8.326-17.319l-2.922-.935-1.223-.382zM60.97 32.766l-7.39 6.702 4.068.879c-1.98 10.21-10.933 17.891-21.733 17.891-2.858 0-5.591-.528-8.097-1.508l.916 4.258A25.918 25.918 0 0 0 35.914 62c12.565 0 23.05-8.988 25.4-20.87l3.686.782-4.03-9.146z" opacity=".3"/><path fill="#FFF" d="M35.722 25L47 28.846v10.385L38.056 45 26 39.23V28.847L35.722 25zm-.381 1L28 29.111 38.045 33 45 29.5 35.34 26zM27 30v8.944L38 44v-9.722L27 30zm-.615-24l-5.9 8.058 9.51 3.036-1.28-3.953a22.208 22.208 0 0 1 7.2-1.203c11.135 0 20.316 8.182 21.904 18.866l3.228-2.94C58.248 16.582 48.052 8.196 35.915 8.196A25.8 25.8 0 0 0 27.55 9.59L26.385 6zm-8.44 9.452C13.046 20.167 10 26.769 10 34.088c0 7.763 3.434 14.745 8.861 19.496l-2.54 2.807 9.93 1.07-2.12-9.739-2.768 3.074a22.075 22.075 0 0 1-7.6-16.708 22.073 22.073 0 0 1 8.326-17.319l-2.922-.935-1.223-.382zM60.97 30.766l-7.39 6.702 4.068.879c-1.98 10.21-10.933 17.891-21.733 17.891-2.858 0-5.591-.528-8.097-1.508l.916 4.258A25.918 25.918 0 0 0 35.914 60c12.565 0 23.05-8.988 25.4-20.87l3.686.782-4.03-9.146z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,22 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { BomOverviewComponent } from "@mrp/components/bom_overview/mrp_bom_overview";
|
||||
|
||||
patch(BomOverviewComponent.prototype, "mrp_plm", {
|
||||
setup() {
|
||||
this._super.apply();
|
||||
this.state.showOptions.ecos = false;
|
||||
this.state.showOptions.ecoAllowed = false;
|
||||
},
|
||||
|
||||
async getBomData() {
|
||||
const bomData = await this._super.apply();
|
||||
this.state.showOptions.ecoAllowed = bomData['is_eco_applied'];
|
||||
return bomData;
|
||||
},
|
||||
|
||||
getReportName(printAll) {
|
||||
return this._super.apply(this, arguments) + "&show_ecos=" + (this.state.showOptions.ecoAllowed && this.state.showOptions.ecos);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { BomOverviewDisplayFilter } from "@mrp/components/bom_overview_display_filter/mrp_bom_overview_display_filter";
|
||||
|
||||
patch(BomOverviewDisplayFilter.prototype, "mrp_plm", {
|
||||
setup() {
|
||||
this._super.apply();
|
||||
if (this.props.showOptions.ecoAllowed) {
|
||||
this.displayOptions.ecos = this.env._t('ECOs');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
patch(BomOverviewDisplayFilter, "mrp_plm", {
|
||||
props: {
|
||||
...BomOverviewDisplayFilter.props,
|
||||
showOptions: {
|
||||
...BomOverviewDisplayFilter.showOptions,
|
||||
ecos: Boolean,
|
||||
ecoAllowed: Boolean,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { BomOverviewLine } from "@mrp/components/bom_overview_line/mrp_bom_overview_line";
|
||||
|
||||
patch(BomOverviewLine.prototype, "mrp_plm", {
|
||||
//---- Handlers ----
|
||||
|
||||
async goToEco() {
|
||||
return this.actionService.doAction({
|
||||
name: this.env._t("ECOs"),
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "mrp.eco",
|
||||
domain: [["product_tmpl_id.product_variant_ids", "in", [this.data.product_id]]],
|
||||
views: [[false, "kanban"], [false, "list"], [false, "form"]],
|
||||
target: "current",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
patch(BomOverviewLine, "mrp_plm", {
|
||||
props: {
|
||||
...BomOverviewLine.props,
|
||||
showOptions: {
|
||||
...BomOverviewLine.showOptions,
|
||||
ecos: Boolean,
|
||||
ecoAllowed: Boolean,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mrp_plm.BomOverviewLine" t-inherit="mrp.BomOverviewLine" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//td[@name='td_mrp_bom']" position="after">
|
||||
<td t-if="props.showOptions.ecos" class="text-end">
|
||||
<span t-if="data.version" t-esc="data.version"/>
|
||||
</td>
|
||||
<td t-if="props.showOptions.ecos" class="text-end">
|
||||
<a href="#" t-on-click.prevent="goToEco" t-esc="data.ecos"/>
|
||||
</td>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user