Accept Merge Request #2269: (feature/新增plm模块 -> develop)
Merge Request: 删除plm模块代码 Created By: @胡尧 Accepted By: @胡尧 URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2269?initial=true
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import report
|
||||
@@ -1,41 +0,0 @@
|
||||
# -*- 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',
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,44 +0,0 @@
|
||||
<?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
1828
mrp_plm/i18n/af.po
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/am.po
1833
mrp_plm/i18n/am.po
File diff suppressed because it is too large
Load Diff
1897
mrp_plm/i18n/ar.po
1897
mrp_plm/i18n/ar.po
File diff suppressed because it is too large
Load Diff
1877
mrp_plm/i18n/az.po
1877
mrp_plm/i18n/az.po
File diff suppressed because it is too large
Load Diff
1888
mrp_plm/i18n/bg.po
1888
mrp_plm/i18n/bg.po
File diff suppressed because it is too large
Load Diff
1764
mrp_plm/i18n/bs.po
1764
mrp_plm/i18n/bs.po
File diff suppressed because it is too large
Load Diff
1924
mrp_plm/i18n/ca.po
1924
mrp_plm/i18n/ca.po
File diff suppressed because it is too large
Load Diff
1889
mrp_plm/i18n/cs.po
1889
mrp_plm/i18n/cs.po
File diff suppressed because it is too large
Load Diff
1895
mrp_plm/i18n/da.po
1895
mrp_plm/i18n/da.po
File diff suppressed because it is too large
Load Diff
1926
mrp_plm/i18n/de.po
1926
mrp_plm/i18n/de.po
File diff suppressed because it is too large
Load Diff
1832
mrp_plm/i18n/el.po
1832
mrp_plm/i18n/el.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1921
mrp_plm/i18n/es.po
1921
mrp_plm/i18n/es.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1915
mrp_plm/i18n/et.po
1915
mrp_plm/i18n/et.po
File diff suppressed because it is too large
Load Diff
1456
mrp_plm/i18n/eu.po
1456
mrp_plm/i18n/eu.po
File diff suppressed because it is too large
Load Diff
1908
mrp_plm/i18n/fa.po
1908
mrp_plm/i18n/fa.po
File diff suppressed because it is too large
Load Diff
1932
mrp_plm/i18n/fi.po
1932
mrp_plm/i18n/fi.po
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/fo.po
1454
mrp_plm/i18n/fo.po
File diff suppressed because it is too large
Load Diff
1917
mrp_plm/i18n/fr.po
1917
mrp_plm/i18n/fr.po
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
||||
# 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
1454
mrp_plm/i18n/gl.po
File diff suppressed because it is too large
Load Diff
1841
mrp_plm/i18n/gu.po
1841
mrp_plm/i18n/gu.po
File diff suppressed because it is too large
Load Diff
1889
mrp_plm/i18n/he.po
1889
mrp_plm/i18n/he.po
File diff suppressed because it is too large
Load Diff
1837
mrp_plm/i18n/hi.po
1837
mrp_plm/i18n/hi.po
File diff suppressed because it is too large
Load Diff
1887
mrp_plm/i18n/hr.po
1887
mrp_plm/i18n/hr.po
File diff suppressed because it is too large
Load Diff
1895
mrp_plm/i18n/hu.po
1895
mrp_plm/i18n/hu.po
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/hy.po
1833
mrp_plm/i18n/hy.po
File diff suppressed because it is too large
Load Diff
1905
mrp_plm/i18n/id.po
1905
mrp_plm/i18n/id.po
File diff suppressed because it is too large
Load Diff
1824
mrp_plm/i18n/is.po
1824
mrp_plm/i18n/is.po
File diff suppressed because it is too large
Load Diff
1907
mrp_plm/i18n/it.po
1907
mrp_plm/i18n/it.po
File diff suppressed because it is too large
Load Diff
1863
mrp_plm/i18n/ja.po
1863
mrp_plm/i18n/ja.po
File diff suppressed because it is too large
Load Diff
1462
mrp_plm/i18n/ka.po
1462
mrp_plm/i18n/ka.po
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/kab.po
1454
mrp_plm/i18n/kab.po
File diff suppressed because it is too large
Load Diff
1886
mrp_plm/i18n/km.po
1886
mrp_plm/i18n/km.po
File diff suppressed because it is too large
Load Diff
1869
mrp_plm/i18n/ko.po
1869
mrp_plm/i18n/ko.po
File diff suppressed because it is too large
Load Diff
1802
mrp_plm/i18n/lb.po
1802
mrp_plm/i18n/lb.po
File diff suppressed because it is too large
Load Diff
1863
mrp_plm/i18n/lo.po
1863
mrp_plm/i18n/lo.po
File diff suppressed because it is too large
Load Diff
1886
mrp_plm/i18n/lt.po
1886
mrp_plm/i18n/lt.po
File diff suppressed because it is too large
Load Diff
1874
mrp_plm/i18n/lv.po
1874
mrp_plm/i18n/lv.po
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/mk.po
1454
mrp_plm/i18n/mk.po
File diff suppressed because it is too large
Load Diff
1843
mrp_plm/i18n/ml.po
1843
mrp_plm/i18n/ml.po
File diff suppressed because it is too large
Load Diff
1885
mrp_plm/i18n/mn.po
1885
mrp_plm/i18n/mn.po
File diff suppressed because it is too large
Load Diff
1838
mrp_plm/i18n/ms.po
1838
mrp_plm/i18n/ms.po
File diff suppressed because it is too large
Load Diff
1274
mrp_plm/i18n/my.po
1274
mrp_plm/i18n/my.po
File diff suppressed because it is too large
Load Diff
1851
mrp_plm/i18n/nb.po
1851
mrp_plm/i18n/nb.po
File diff suppressed because it is too large
Load Diff
1456
mrp_plm/i18n/ne.po
1456
mrp_plm/i18n/ne.po
File diff suppressed because it is too large
Load Diff
1908
mrp_plm/i18n/nl.po
1908
mrp_plm/i18n/nl.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/no.po
1833
mrp_plm/i18n/no.po
File diff suppressed because it is too large
Load Diff
1927
mrp_plm/i18n/pl.po
1927
mrp_plm/i18n/pl.po
File diff suppressed because it is too large
Load Diff
1877
mrp_plm/i18n/pt.po
1877
mrp_plm/i18n/pt.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1903
mrp_plm/i18n/ro.po
1903
mrp_plm/i18n/ro.po
File diff suppressed because it is too large
Load Diff
1916
mrp_plm/i18n/ru.po
1916
mrp_plm/i18n/ru.po
File diff suppressed because it is too large
Load Diff
1877
mrp_plm/i18n/sk.po
1877
mrp_plm/i18n/sk.po
File diff suppressed because it is too large
Load Diff
1870
mrp_plm/i18n/sl.po
1870
mrp_plm/i18n/sl.po
File diff suppressed because it is too large
Load Diff
1454
mrp_plm/i18n/sq.po
1454
mrp_plm/i18n/sq.po
File diff suppressed because it is too large
Load Diff
1876
mrp_plm/i18n/sr.po
1876
mrp_plm/i18n/sr.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1878
mrp_plm/i18n/sv.po
1878
mrp_plm/i18n/sv.po
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/sw.po
1833
mrp_plm/i18n/sw.po
File diff suppressed because it is too large
Load Diff
1833
mrp_plm/i18n/ta.po
1833
mrp_plm/i18n/ta.po
File diff suppressed because it is too large
Load Diff
1897
mrp_plm/i18n/th.po
1897
mrp_plm/i18n/th.po
File diff suppressed because it is too large
Load Diff
1920
mrp_plm/i18n/tr.po
1920
mrp_plm/i18n/tr.po
File diff suppressed because it is too large
Load Diff
1902
mrp_plm/i18n/uk.po
1902
mrp_plm/i18n/uk.po
File diff suppressed because it is too large
Load Diff
1899
mrp_plm/i18n/vi.po
1899
mrp_plm/i18n/vi.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,179 +0,0 @@
|
||||
# -*- 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()
|
||||
@@ -1,10 +0,0 @@
|
||||
# -*- 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')
|
||||
@@ -1,834 +0,0 @@
|
||||
# -*- 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 !"),
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
# -*- 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})
|
||||
@@ -1,15 +0,0 @@
|
||||
# -*- 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)
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mrp_report_bom_structure
|
||||
@@ -1,46 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,38 +0,0 @@
|
||||
<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>
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
|
@@ -1,45 +0,0 @@
|
||||
<?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>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.7 KiB |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,22 +0,0 @@
|
||||
/** @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);
|
||||
}
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
/** @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,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
/** @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,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
<?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