mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
1
mrp_lot_number_propagation/__init__.py
Normal file
1
mrp_lot_number_propagation/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
20
mrp_lot_number_propagation/__manifest__.py
Normal file
20
mrp_lot_number_propagation/__manifest__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
{
|
||||
"name": "MRP Serial Number Propagation",
|
||||
"version": "14.0.1.0.0",
|
||||
"development_status": "Beta",
|
||||
"license": "AGPL-3",
|
||||
"author": "Camptocamp, Odoo Community Association (OCA)",
|
||||
"maintainers": ["sebalix"],
|
||||
"summary": "Propagate a serial number from a component to a finished product",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"category": "Manufacturing",
|
||||
"depends": ["mrp"],
|
||||
"data": [
|
||||
"views/mrp_bom.xml",
|
||||
"views/mrp_production.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"application": False,
|
||||
}
|
||||
192
mrp_lot_number_propagation/i18n/es.po
Normal file
192
mrp_lot_number_propagation/i18n/es.po
Normal file
@@ -0,0 +1,192 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mrp_lot_number_propagation
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2023-10-29 08:29+0000\n"
|
||||
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: es\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"
|
||||
"X-Generator: Weblate 4.17\n"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/product_template.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"A BoM propagating serial numbers requires this product to be tracked as "
|
||||
"such."
|
||||
msgstr ""
|
||||
"Una BoM que propague números de serie requiere que este producto sea "
|
||||
"rastreado como tal."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_bom__lot_number_propagation
|
||||
msgid ""
|
||||
"Allow to propagate the lot/serial number from a component to the finished "
|
||||
"product."
|
||||
msgstr ""
|
||||
"Permite propagar el número de lote/serie de un componente al producto "
|
||||
"acabado."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_bom
|
||||
msgid "Bill of Material"
|
||||
msgstr "Lista de Material"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_bom_line
|
||||
msgid "Bill of Material Line"
|
||||
msgstr "Línea de la factura de materiales"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Bill of material is marked for lot number propagation, but there are "
|
||||
"multiple components propagating lot number. Please check BOM configuration."
|
||||
msgstr ""
|
||||
"La lista de materiales está marcada para la propagación del número de lote, "
|
||||
"pero hay varios componentes que propagan el número de lote. Compruebe la "
|
||||
"configuración de la lista de materiales."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Bill of material is marked for lot number propagation, but there are no "
|
||||
"components propagating lot number. Please check BOM configuration."
|
||||
msgstr ""
|
||||
"La lista de materiales está marcada para la propagación del número de lote, "
|
||||
"pero no hay componentes que propaguen el número de lote. Compruebe la "
|
||||
"configuración de la lista de materiales."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom__display_lot_number_propagation
|
||||
msgid "Display Lot Number Propagation"
|
||||
msgstr "Mostrar Número de Lote Propagación"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom_line__display_propagate_lot_number
|
||||
msgid "Display Propagate Lot Number"
|
||||
msgstr "Mostrar Propagación Número de Lote"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_production__is_lot_number_propagated
|
||||
msgid "Is Lot Number Propagated"
|
||||
msgstr "Es el Número de Lote Propagado"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom__lot_number_propagation
|
||||
msgid "Lot Number Propagation"
|
||||
msgstr "Propagación del Número de Lote"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model_terms:ir.ui.view,arch_db:mrp_lot_number_propagation.mrp_production_form_view
|
||||
msgid "Lot/Serial Number"
|
||||
msgstr "Lote/Número de serie"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Lot/Serial number %s already exists and has been used. Unable to propagate "
|
||||
"it."
|
||||
msgstr ""
|
||||
"El lote/número de serie %s ya existe y ha sido utilizado. No se ha podido "
|
||||
"propagar."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Lot/Serial number is propagated from a component, you are not allowed to "
|
||||
"change it."
|
||||
msgstr ""
|
||||
"El número de lote/serie se propaga desde un componente, usted no está "
|
||||
"autorizado a modificarlo."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_production__is_lot_number_propagated
|
||||
msgid ""
|
||||
"Lot/serial number is propagated from a component to the finished product."
|
||||
msgstr ""
|
||||
"El número de lote/serie se propaga de un componente al producto acabado."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_bom_line.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only components tracked by serial number can propagate its lot/serial number"
|
||||
" to the finished product."
|
||||
msgstr ""
|
||||
"Sólo los componentes rastreados por número de serie pueden propagar su "
|
||||
"número de lote/serie al producto acabado."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_product_product
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Plantilla del Producto"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_production
|
||||
msgid "Production Order"
|
||||
msgstr "Orden de Producción"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom_line__propagate_lot_number
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_stock_move__propagate_lot_number
|
||||
msgid "Propagate Lot Number"
|
||||
msgstr "Propagar el Número de Lote"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_production__propagated_lot_producing
|
||||
msgid "Propagated Lot Producing"
|
||||
msgstr "Producción de Lotes Propagados"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_stock_move
|
||||
msgid "Stock Move"
|
||||
msgstr "Movimiento de Existencias"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_production__propagated_lot_producing
|
||||
msgid ""
|
||||
"The BoM used on this manufacturing order is set to propagate lot number from"
|
||||
" one of its components. The value will be computed once the corresponding "
|
||||
"component is selected."
|
||||
msgstr ""
|
||||
"La lista de materiales utilizada en esta orden de producción está "
|
||||
"configurada para propagar el número de lote desde uno de sus componentes. El "
|
||||
"valor se calculará una vez seleccionado el componente correspondiente."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/product_template.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This component is configured to propagate its serial number in the following"
|
||||
" Bill of Materials:{boms}'"
|
||||
msgstr ""
|
||||
"Este componente está configurado para propagar su número de serie en la "
|
||||
"siguiente lista de materiales:{boms}'"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_bom.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"With 'Lot Number Propagation' enabled, a line has to be configured with the "
|
||||
"'Propagate Lot Number' option."
|
||||
msgstr ""
|
||||
"Con la \"Propagación del número de lote\" activada, debe configurarse una "
|
||||
"línea con la opción \"Propagar número de lote\"."
|
||||
198
mrp_lot_number_propagation/i18n/it.po
Normal file
198
mrp_lot_number_propagation/i18n/it.po
Normal file
@@ -0,0 +1,198 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mrp_lot_number_propagation
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2023-11-21 16:35+0000\n"
|
||||
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: it\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"
|
||||
"X-Generator: Weblate 4.17\n"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/product_template.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"A BoM propagating serial numbers requires this product to be tracked as such."
|
||||
msgstr ""
|
||||
"Una distinta base che propaga i numeri di serie richiede che questo prodotto "
|
||||
"sia tracciato come tale."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_bom__lot_number_propagation
|
||||
msgid ""
|
||||
"Allow to propagate the lot/serial number from a component to the finished "
|
||||
"product."
|
||||
msgstr ""
|
||||
"Consente di propagare il numero di lotto/matricola da un componente al "
|
||||
"prodotto finito."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_bom
|
||||
msgid "Bill of Material"
|
||||
msgstr "Distinta base"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_bom_line
|
||||
msgid "Bill of Material Line"
|
||||
msgstr "Riga distinta base"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Bill of material is marked for lot number propagation, but there are "
|
||||
"multiple components propagating lot number. Please check BOM configuration."
|
||||
msgstr ""
|
||||
"La distinta base è impostata per la propagazione del numero di lotto, ma ci "
|
||||
"sono più componenti che propagano il numero di lotto. Verificare la "
|
||||
"configurazione della DB."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Bill of material is marked for lot number propagation, but there are no "
|
||||
"components propagating lot number. Please check BOM configuration."
|
||||
msgstr ""
|
||||
"La distinta base è impostata per la propagazione del numero di lotto, ma non "
|
||||
"ci sono componenti che propagano il numero di lotto. Verificare la "
|
||||
"configurazione della DB."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom__display_lot_number_propagation
|
||||
msgid "Display Lot Number Propagation"
|
||||
msgstr "Visualizza propagazione numero di lotto"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom_line__display_propagate_lot_number
|
||||
msgid "Display Propagate Lot Number"
|
||||
msgstr "Visualizza il numero di lotto propagato"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_production__is_lot_number_propagated
|
||||
msgid "Is Lot Number Propagated"
|
||||
msgstr "Il numero di lotto è propagato"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom__lot_number_propagation
|
||||
msgid "Lot Number Propagation"
|
||||
msgstr "Propagazione numero di lotto"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model_terms:ir.ui.view,arch_db:mrp_lot_number_propagation.mrp_production_form_view
|
||||
msgid "Lot/Serial Number"
|
||||
msgstr "Numero di lotto/serie"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Lot/Serial number %s already exists and has been used. Unable to propagate "
|
||||
"it."
|
||||
msgstr ""
|
||||
"Il lotto/seriale %s esiste ed è stato usato. Non è possibile propagarlo."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Lot/Serial number is propagated from a component, you are not allowed to "
|
||||
"change it."
|
||||
msgstr ""
|
||||
"Il numero di lotto/matricola è propagato da un componente, non è consentito "
|
||||
"modificarlo."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_production__is_lot_number_propagated
|
||||
msgid ""
|
||||
"Lot/serial number is propagated from a component to the finished product."
|
||||
msgstr ""
|
||||
"Il numero di lotto/matricola è propagato da un componente al prodotto finito."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_bom_line.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only components tracked by serial number can propagate its lot/serial number "
|
||||
"to the finished product."
|
||||
msgstr ""
|
||||
"Solo i componenti tracciati da una matricola possono propagare il loro "
|
||||
"numero di lotto/matricola al prodotto finito."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_product_product
|
||||
msgid "Product"
|
||||
msgstr "Prodotto"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "Modello prodotto"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_production
|
||||
msgid "Production Order"
|
||||
msgstr "Ordine di produzione"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom_line__propagate_lot_number
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_stock_move__propagate_lot_number
|
||||
msgid "Propagate Lot Number"
|
||||
msgstr "Propaga numero di lotto"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_production__propagated_lot_producing
|
||||
msgid "Propagated Lot Producing"
|
||||
msgstr "Produzione lotto propagato"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_stock_move
|
||||
msgid "Stock Move"
|
||||
msgstr "Movimento di magazzino"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_production__propagated_lot_producing
|
||||
msgid ""
|
||||
"The BoM used on this manufacturing order is set to propagate lot number from "
|
||||
"one of its components. The value will be computed once the corresponding "
|
||||
"component is selected."
|
||||
msgstr ""
|
||||
"La DiBa utilizzata in questo ordine di produzione è impostata per propagare "
|
||||
"il nomero di lotto da uno dei suoi componenti. Il valore verrà calcolato una "
|
||||
"volta che il componente corrispondente sarà selezionato."
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/product_template.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This component is configured to propagate its serial number in the following "
|
||||
"Bill of Materials:{boms}'"
|
||||
msgstr ""
|
||||
"Questo componente è configurato per propagare la sua matricola nella "
|
||||
"seguente DiBa: {boms}'"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_bom.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"With 'Lot Number Propagation' enabled, a line has to be configured with the "
|
||||
"'Propagate Lot Number' option."
|
||||
msgstr ""
|
||||
"Con 'Propagazione numero di lotto' abilitata, una riga deve essere "
|
||||
"configurata con l'opzione 'Propaga numero di lotto'."
|
||||
|
||||
#, python-format
|
||||
#~ msgid ""
|
||||
#~ "Only one BoM line can propagate its lot/serial number to the finished "
|
||||
#~ "product."
|
||||
#~ msgstr ""
|
||||
#~ "Solo una riga di DiBa può propagare il suo numero di lotto/matricola al "
|
||||
#~ "prodotto finito."
|
||||
165
mrp_lot_number_propagation/i18n/mrp_lot_number_propagation.pot
Normal file
165
mrp_lot_number_propagation/i18n/mrp_lot_number_propagation.pot
Normal file
@@ -0,0 +1,165 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mrp_lot_number_propagation
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/product_template.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"A BoM propagating serial numbers requires this product to be tracked as "
|
||||
"such."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_bom__lot_number_propagation
|
||||
msgid ""
|
||||
"Allow to propagate the lot/serial number from a component to the finished "
|
||||
"product."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_bom
|
||||
msgid "Bill of Material"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_bom_line
|
||||
msgid "Bill of Material Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Bill of material is marked for lot number propagation, but there are "
|
||||
"multiple components propagating lot number. Please check BOM configuration."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Bill of material is marked for lot number propagation, but there are no "
|
||||
"components propagating lot number. Please check BOM configuration."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom__display_lot_number_propagation
|
||||
msgid "Display Lot Number Propagation"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom_line__display_propagate_lot_number
|
||||
msgid "Display Propagate Lot Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_production__is_lot_number_propagated
|
||||
msgid "Is Lot Number Propagated"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom__lot_number_propagation
|
||||
msgid "Lot Number Propagation"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model_terms:ir.ui.view,arch_db:mrp_lot_number_propagation.mrp_production_form_view
|
||||
msgid "Lot/Serial Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Lot/Serial number %s already exists and has been used. Unable to propagate "
|
||||
"it."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_production.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Lot/Serial number is propagated from a component, you are not allowed to "
|
||||
"change it."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_production__is_lot_number_propagated
|
||||
msgid ""
|
||||
"Lot/serial number is propagated from a component to the finished product."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_bom_line.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only components tracked by serial number can propagate its lot/serial number"
|
||||
" to the finished product."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_product_product
|
||||
msgid "Product"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_mrp_production
|
||||
msgid "Production Order"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_bom_line__propagate_lot_number
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_stock_move__propagate_lot_number
|
||||
msgid "Propagate Lot Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,field_description:mrp_lot_number_propagation.field_mrp_production__propagated_lot_producing
|
||||
msgid "Propagated Lot Producing"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model,name:mrp_lot_number_propagation.model_stock_move
|
||||
msgid "Stock Move"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: model:ir.model.fields,help:mrp_lot_number_propagation.field_mrp_production__propagated_lot_producing
|
||||
msgid ""
|
||||
"The BoM used on this manufacturing order is set to propagate lot number from"
|
||||
" one of its components. The value will be computed once the corresponding "
|
||||
"component is selected."
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/product_template.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This component is configured to propagate its serial number in the following"
|
||||
" Bill of Materials:{boms}'"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_lot_number_propagation
|
||||
#: code:addons/mrp_lot_number_propagation/models/mrp_bom.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"With 'Lot Number Propagation' enabled, a line has to be configured with the "
|
||||
"'Propagate Lot Number' option."
|
||||
msgstr ""
|
||||
6
mrp_lot_number_propagation/models/__init__.py
Normal file
6
mrp_lot_number_propagation/models/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from . import mrp_bom
|
||||
from . import mrp_bom_line
|
||||
from . import mrp_production
|
||||
from . import product_product
|
||||
from . import product_template
|
||||
from . import stock_move
|
||||
88
mrp_lot_number_propagation/models/mrp_bom.py
Normal file
88
mrp_lot_number_propagation/models/mrp_bom.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class MrpBom(models.Model):
|
||||
_inherit = "mrp.bom"
|
||||
|
||||
lot_number_propagation = fields.Boolean(
|
||||
default=False,
|
||||
help=(
|
||||
"Allow to propagate the lot/serial number "
|
||||
"from a component to the finished product."
|
||||
),
|
||||
)
|
||||
display_lot_number_propagation = fields.Boolean(
|
||||
compute="_compute_display_lot_number_propagation"
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"type",
|
||||
"product_tmpl_id.tracking",
|
||||
"product_qty",
|
||||
"product_uom_id",
|
||||
"bom_line_ids.product_id.tracking",
|
||||
"bom_line_ids.product_qty",
|
||||
"bom_line_ids.product_uom_id",
|
||||
)
|
||||
def _compute_display_lot_number_propagation(self):
|
||||
"""Check if a lot number can be propagated.
|
||||
|
||||
A lot number can be propagated from a component to the finished product if:
|
||||
- the type of the BoM is normal (Manufacture this product)
|
||||
- the finished product is tracked by serial number
|
||||
- the quantity of the finished product is 1 and its UoM is unit
|
||||
- there is at least one bom line, with a component tracked by serial,
|
||||
having a quantity of 1 and its UoM is unit
|
||||
"""
|
||||
uom_unit = self.env.ref("uom.product_uom_unit")
|
||||
for bom in self:
|
||||
bom.display_lot_number_propagation = (
|
||||
bom.type in self._get_lot_number_propagation_bom_types()
|
||||
and bom.product_tmpl_id.tracking == "serial"
|
||||
and tools.float_compare(
|
||||
bom.product_qty, 1, precision_rounding=bom.product_uom_id.rounding
|
||||
)
|
||||
== 0
|
||||
and bom.product_uom_id == uom_unit
|
||||
and bom._has_tracked_product_to_propagate()
|
||||
)
|
||||
|
||||
def _get_lot_number_propagation_bom_types(self):
|
||||
return ["normal"]
|
||||
|
||||
def _has_tracked_product_to_propagate(self):
|
||||
self.ensure_one()
|
||||
uom_unit = self.env.ref("uom.product_uom_unit")
|
||||
for line in self.bom_line_ids:
|
||||
if (
|
||||
line.product_id.tracking == "serial"
|
||||
and line.product_uom_id == uom_unit
|
||||
and tools.float_compare(
|
||||
line.product_qty, 1, precision_rounding=line.product_uom_id.rounding
|
||||
)
|
||||
== 0
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@api.onchange("display_lot_number_propagation")
|
||||
def onchange_display_lot_number_propagation(self):
|
||||
if not self.display_lot_number_propagation:
|
||||
self.lot_number_propagation = False
|
||||
|
||||
@api.constrains("lot_number_propagation")
|
||||
def _check_propagate_lot_number(self):
|
||||
for bom in self:
|
||||
if not bom.lot_number_propagation:
|
||||
continue
|
||||
if not bom.bom_line_ids.filtered("propagate_lot_number"):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"With 'Lot Number Propagation' enabled, a line has "
|
||||
"to be configured with the 'Propagate Lot Number' option."
|
||||
)
|
||||
)
|
||||
48
mrp_lot_number_propagation/models/mrp_bom_line.py
Normal file
48
mrp_lot_number_propagation/models/mrp_bom_line.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class MrpBomLine(models.Model):
|
||||
_inherit = "mrp.bom.line"
|
||||
|
||||
propagate_lot_number = fields.Boolean(
|
||||
default=False,
|
||||
)
|
||||
display_propagate_lot_number = fields.Boolean(
|
||||
compute="_compute_display_propagate_lot_number"
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"bom_id.display_lot_number_propagation",
|
||||
"bom_id.lot_number_propagation",
|
||||
)
|
||||
def _compute_display_propagate_lot_number(self):
|
||||
for line in self:
|
||||
line.display_propagate_lot_number = (
|
||||
line.bom_id.display_lot_number_propagation
|
||||
and line.bom_id.lot_number_propagation
|
||||
)
|
||||
|
||||
@api.constrains("propagate_lot_number")
|
||||
def _check_propagate_lot_number(self):
|
||||
"""
|
||||
This function should check:
|
||||
|
||||
- if the bom has lot_number_propagation marked, there is one and
|
||||
only one line of this bom with propagate_lot_number marked.
|
||||
- the bom line being marked with lot_number_propagation is of the same
|
||||
tracking type as the finished product
|
||||
"""
|
||||
for line in self:
|
||||
if not line.bom_id.lot_number_propagation:
|
||||
continue
|
||||
if line.propagate_lot_number and line.product_id.tracking != "serial":
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Only components tracked by serial number can propagate "
|
||||
"its lot/serial number to the finished product."
|
||||
)
|
||||
)
|
||||
193
mrp_lot_number_propagation/models/mrp_production.py
Normal file
193
mrp_lot_number_propagation/models/mrp_production.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from odoo.addons.base.models.ir_ui_view import (
|
||||
transfer_modifiers_to_node,
|
||||
transfer_node_to_modifiers,
|
||||
)
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = "mrp.production"
|
||||
|
||||
is_lot_number_propagated = fields.Boolean(
|
||||
default=False,
|
||||
readonly=True,
|
||||
help=(
|
||||
"Lot/serial number is propagated "
|
||||
"from a component to the finished product."
|
||||
),
|
||||
)
|
||||
propagated_lot_producing = fields.Char(
|
||||
compute="_compute_propagated_lot_producing",
|
||||
help=(
|
||||
"The BoM used on this manufacturing order is set to propagate "
|
||||
"lot number from one of its components. The value will be "
|
||||
"computed once the corresponding component is selected."
|
||||
),
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"move_raw_ids.propagate_lot_number",
|
||||
"move_raw_ids.move_line_ids.qty_done",
|
||||
"move_raw_ids.move_line_ids.lot_id",
|
||||
)
|
||||
def _compute_propagated_lot_producing(self):
|
||||
for order in self:
|
||||
order.propagated_lot_producing = False
|
||||
move_with_lot = order._get_propagating_component_move()
|
||||
line_with_sn = move_with_lot.move_line_ids.filtered(
|
||||
lambda l: (
|
||||
l.lot_id
|
||||
and l.product_id.tracking == "serial"
|
||||
and tools.float_compare(
|
||||
l.qty_done, 1, precision_rounding=l.product_uom_id.rounding
|
||||
)
|
||||
== 0
|
||||
)
|
||||
)
|
||||
if len(line_with_sn) == 1:
|
||||
order.propagated_lot_producing = line_with_sn.lot_id.name
|
||||
|
||||
@api.onchange("bom_id")
|
||||
def _onchange_bom_id_lot_number_propagation(self):
|
||||
self.is_lot_number_propagated = self.bom_id.lot_number_propagation
|
||||
|
||||
def action_confirm(self):
|
||||
res = super().action_confirm()
|
||||
self._set_lot_number_propagation_data_from_bom()
|
||||
return res
|
||||
|
||||
def _get_propagating_component_move(self):
|
||||
self.ensure_one()
|
||||
return self.move_raw_ids.filtered(lambda o: o.propagate_lot_number)
|
||||
|
||||
def _set_lot_number_propagation_data_from_bom(self):
|
||||
"""Copy information from BoM to the manufacturing order."""
|
||||
for order in self:
|
||||
propagate_lot = order.bom_id.lot_number_propagation
|
||||
if not propagate_lot:
|
||||
continue
|
||||
order.is_lot_number_propagated = propagate_lot
|
||||
propagate_move = order.move_raw_ids.filtered(
|
||||
lambda m: m.bom_line_id.propagate_lot_number
|
||||
)
|
||||
if not propagate_move:
|
||||
raise UserError(
|
||||
_(
|
||||
"Bill of material is marked for lot number propagation, but "
|
||||
"there are no components propagating lot number. "
|
||||
"Please check BOM configuration."
|
||||
)
|
||||
)
|
||||
elif len(propagate_move) > 1:
|
||||
raise UserError(
|
||||
_(
|
||||
"Bill of material is marked for lot number propagation, but "
|
||||
"there are multiple components propagating lot number. "
|
||||
"Please check BOM configuration."
|
||||
)
|
||||
)
|
||||
else:
|
||||
propagate_move.propagate_lot_number = True
|
||||
|
||||
def _post_inventory(self, cancel_backorder=False):
|
||||
self._create_and_assign_propagated_lot_number()
|
||||
return super()._post_inventory(cancel_backorder=cancel_backorder)
|
||||
|
||||
def _create_and_assign_propagated_lot_number(self):
|
||||
for order in self:
|
||||
if not order.is_lot_number_propagated or order.lot_producing_id:
|
||||
continue
|
||||
finish_moves = order.move_finished_ids.filtered(
|
||||
lambda m: m.product_id == order.product_id
|
||||
and m.state not in ("done", "cancel")
|
||||
)
|
||||
if finish_moves and not finish_moves.quantity_done:
|
||||
lot_model = self.env["stock.production.lot"]
|
||||
lot = lot_model.search(
|
||||
[
|
||||
("product_id", "=", order.product_id.id),
|
||||
("company_id", "=", order.company_id.id),
|
||||
("name", "=", order.propagated_lot_producing),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
if lot.quant_ids:
|
||||
raise UserError(
|
||||
_(
|
||||
"Lot/Serial number %s already exists and has been used. "
|
||||
"Unable to propagate it."
|
||||
)
|
||||
)
|
||||
if not lot:
|
||||
lot = self.env["stock.production.lot"].create(
|
||||
{
|
||||
"product_id": order.product_id.id,
|
||||
"company_id": order.company_id.id,
|
||||
"name": order.propagated_lot_producing,
|
||||
}
|
||||
)
|
||||
order.with_context(lot_propagation=True).lot_producing_id = lot
|
||||
|
||||
def write(self, vals):
|
||||
for order in self:
|
||||
if (
|
||||
order.is_lot_number_propagated
|
||||
and "lot_producing_id" in vals
|
||||
and not self.env.context.get("lot_propagation")
|
||||
):
|
||||
raise UserError(
|
||||
_(
|
||||
"Lot/Serial number is propagated from a component, "
|
||||
"you are not allowed to change it."
|
||||
)
|
||||
)
|
||||
return super().write(vals)
|
||||
|
||||
def fields_view_get(
|
||||
self, view_id=None, view_type="form", toolbar=False, submenu=False
|
||||
):
|
||||
# Override to hide the "lot_producing_id" field + "action_generate_serial"
|
||||
# button if the MO is configured to propagate a serial number
|
||||
result = super().fields_view_get(
|
||||
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
|
||||
)
|
||||
if result.get("name") in self._views_to_adapt():
|
||||
result["arch"] = self._fields_view_get_adapt_lot_tags_attrs(result)
|
||||
return result
|
||||
|
||||
def _views_to_adapt(self):
|
||||
"""Return the form view names bound to 'mrp.production' to adapt."""
|
||||
return ["mrp.production.form"]
|
||||
|
||||
def _fields_view_get_adapt_lot_tags_attrs(self, view):
|
||||
"""Hide elements related to lot if it is automatically propagated."""
|
||||
doc = etree.XML(view["arch"])
|
||||
tags = (
|
||||
"//label[@for='lot_producing_id']",
|
||||
"//field[@name='lot_producing_id']/..", # parent <div>
|
||||
)
|
||||
for xpath_expr in tags:
|
||||
attrs_key = "invisible"
|
||||
nodes = doc.xpath(xpath_expr)
|
||||
for field in nodes:
|
||||
attrs = safe_eval(field.attrib.get("attrs", "{}"))
|
||||
if not attrs[attrs_key]:
|
||||
continue
|
||||
invisible_domain = expression.OR(
|
||||
[attrs[attrs_key], [("is_lot_number_propagated", "=", True)]]
|
||||
)
|
||||
attrs[attrs_key] = invisible_domain
|
||||
field.set("attrs", str(attrs))
|
||||
modifiers = {}
|
||||
transfer_node_to_modifiers(field, modifiers, self.env.context)
|
||||
transfer_modifiers_to_node(modifiers, field)
|
||||
return etree.tostring(doc, encoding="unicode")
|
||||
13
mrp_lot_number_propagation/models/product_product.py
Normal file
13
mrp_lot_number_propagation/models/product_product.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
@api.constrains("tracking")
|
||||
def _check_bom_propagate_lot_number(self):
|
||||
for product in self:
|
||||
product.product_tmpl_id._check_bom_propagate_lot_number()
|
||||
42
mrp_lot_number_propagation/models/product_template.py
Normal file
42
mrp_lot_number_propagation/models/product_template.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, api, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
@api.constrains("tracking")
|
||||
def _check_bom_propagate_lot_number(self):
|
||||
"""Block tracking type updates if the product is used by a BoM."""
|
||||
for product in self:
|
||||
if product.tracking == "serial":
|
||||
continue
|
||||
# Check BoMs
|
||||
for bom in product.bom_ids:
|
||||
if bom.lot_number_propagation:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"A BoM propagating serial numbers requires "
|
||||
"this product to be tracked as such."
|
||||
)
|
||||
)
|
||||
# Check lines of BoMs
|
||||
bom_lines = self.env["mrp.bom.line"].search(
|
||||
[
|
||||
("product_id", "in", product.product_variant_ids.ids),
|
||||
("propagate_lot_number", "=", True),
|
||||
("bom_id.lot_number_propagation", "=", True),
|
||||
]
|
||||
)
|
||||
if bom_lines:
|
||||
boms = "\n- ".join(bom_lines.mapped("bom_id.display_name"))
|
||||
boms = "\n- " + boms
|
||||
raise ValidationError(
|
||||
_(
|
||||
"This component is configured to propagate its "
|
||||
"serial number in the following Bill of Materials:{boms}'"
|
||||
).format(boms=boms)
|
||||
)
|
||||
13
mrp_lot_number_propagation/models/stock_move.py
Normal file
13
mrp_lot_number_propagation/models/stock_move.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
propagate_lot_number = fields.Boolean(
|
||||
default=False,
|
||||
readonly=True,
|
||||
)
|
||||
2
mrp_lot_number_propagation/readme/CONTRIBUTORS.rst
Normal file
2
mrp_lot_number_propagation/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,2 @@
|
||||
* Akim Juillerat <akim.juillerat@camptocamp.com>
|
||||
* Sébastien Alix <sebastien.alix@camptocamp.com>
|
||||
1
mrp_lot_number_propagation/readme/DESCRIPTION.rst
Normal file
1
mrp_lot_number_propagation/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1 @@
|
||||
Allow to propagate a lot number from a component to a finished product.
|
||||
1
mrp_lot_number_propagation/readme/ROADMAP.rst
Normal file
1
mrp_lot_number_propagation/readme/ROADMAP.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Add compatibility with lot number (in addition to serial number)
|
||||
1
mrp_lot_number_propagation/tests/__init__.py
Normal file
1
mrp_lot_number_propagation/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_mrp_bom
|
||||
159
mrp_lot_number_propagation/tests/common.py
Normal file
159
mrp_lot_number_propagation/tests/common.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests import Form, common
|
||||
|
||||
|
||||
class Common(common.SavepointCase):
|
||||
|
||||
LOT_NAME = "PROPAGATED-LOT"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
||||
cls.bom = cls.env.ref("mrp.mrp_bom_desk")
|
||||
cls.bom_product_template = cls.env.ref(
|
||||
"mrp.product_product_computer_desk_product_template"
|
||||
)
|
||||
cls.bom_product_product = cls.env.ref("mrp.product_product_computer_desk")
|
||||
cls.product_tracked_by_lot = cls.env.ref(
|
||||
"mrp.product_product_computer_desk_leg"
|
||||
)
|
||||
cls.product_tracked_by_sn = cls.env.ref(
|
||||
"mrp.product_product_computer_desk_head"
|
||||
)
|
||||
cls.product_template_tracked_by_sn = cls.env.ref(
|
||||
"mrp.product_product_computer_desk_head_product_template"
|
||||
)
|
||||
cls.line_tracked_by_lot = cls.bom.bom_line_ids.filtered(
|
||||
lambda o: o.product_id == cls.product_tracked_by_lot
|
||||
)
|
||||
cls.line_tracked_by_sn = cls.bom.bom_line_ids.filtered(
|
||||
lambda o: o.product_id == cls.product_tracked_by_sn
|
||||
)
|
||||
cls.line_no_tracking = fields.first(
|
||||
cls.bom.bom_line_ids.filtered(lambda o: o.product_id.tracking == "none")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _update_qty_in_location(
|
||||
cls, location, product, quantity, package=None, lot=None, in_date=None
|
||||
):
|
||||
quants = cls.env["stock.quant"]._gather(
|
||||
product, location, lot_id=lot, package_id=package, strict=True
|
||||
)
|
||||
# this method adds the quantity to the current quantity, so remove it
|
||||
quantity -= sum(quants.mapped("quantity"))
|
||||
cls.env["stock.quant"]._update_available_quantity(
|
||||
product,
|
||||
location,
|
||||
quantity,
|
||||
package_id=package,
|
||||
lot_id=lot,
|
||||
in_date=in_date,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _update_stock_component_qty(cls, order=None, bom=None, location=None):
|
||||
if not order and not bom:
|
||||
return
|
||||
if order:
|
||||
bom = order.bom_id
|
||||
if not location:
|
||||
location = cls.env.ref("stock.stock_location_stock")
|
||||
for line in bom.bom_line_ids:
|
||||
if line.product_id.type != "product":
|
||||
continue
|
||||
lot = None
|
||||
if line.product_id.tracking != "none":
|
||||
lot_name = "".join(
|
||||
random.choice(string.ascii_lowercase) for i in range(10)
|
||||
)
|
||||
if line.propagate_lot_number:
|
||||
lot_name = cls.LOT_NAME
|
||||
vals = {
|
||||
"product_id": line.product_id.id,
|
||||
"company_id": line.company_id.id,
|
||||
"name": lot_name,
|
||||
}
|
||||
lot = cls.env["stock.production.lot"].create(vals)
|
||||
cls._update_qty_in_location(
|
||||
location,
|
||||
line.product_id,
|
||||
line.product_qty,
|
||||
lot=lot,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_lot_quants(cls, lot, location=None):
|
||||
quants = lot.quant_ids.filtered(lambda q: q.quantity > 0)
|
||||
if location:
|
||||
quants = quants.filtered(
|
||||
lambda q: q.location_id.parent_path in location.parent_path
|
||||
)
|
||||
return quants
|
||||
|
||||
@classmethod
|
||||
def _add_color_and_legs_variants(cls, product_template):
|
||||
color_attribute = cls.env.ref("product.product_attribute_2")
|
||||
color_att_value_white = cls.env.ref("product.product_attribute_value_3")
|
||||
color_att_value_black = cls.env.ref("product.product_attribute_value_4")
|
||||
legs_attribute = cls.env.ref("product.product_attribute_1")
|
||||
legs_att_value_steel = cls.env.ref("product.product_attribute_value_1")
|
||||
legs_att_value_alu = cls.env.ref("product.product_attribute_value_2")
|
||||
cls._add_variants(
|
||||
product_template,
|
||||
{
|
||||
color_attribute: [color_att_value_white, color_att_value_black],
|
||||
legs_attribute: [legs_att_value_steel, legs_att_value_alu],
|
||||
},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _add_variants(cls, product_template, attribute_values_dict):
|
||||
for attribute, att_values_list in attribute_values_dict.items():
|
||||
cls.env["product.template.attribute.line"].create(
|
||||
{
|
||||
"product_tmpl_id": product_template.id,
|
||||
"attribute_id": attribute.id,
|
||||
"value_ids": [(6, 0, [att_val.id for att_val in att_values_list])],
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_bom_with_variants(cls):
|
||||
attribute_values_dict = {
|
||||
att_val.product_attribute_value_id.name: att_val.id
|
||||
for att_val in cls.env["product.template.attribute.value"].search(
|
||||
[("product_tmpl_id", "=", cls.bom_product_template.id)]
|
||||
)
|
||||
}
|
||||
new_bom_form = Form(cls.env["mrp.bom"])
|
||||
new_bom_form.product_tmpl_id = cls.bom_product_template
|
||||
new_bom = new_bom_form.save()
|
||||
bom_line_create_values = []
|
||||
for product in cls.product_template_tracked_by_sn.product_variant_ids:
|
||||
create_values = {"bom_id": new_bom.id}
|
||||
create_values["product_id"] = product.id
|
||||
att_values_commands = []
|
||||
for att_value in product.product_template_attribute_value_ids:
|
||||
att_values_commands.append(
|
||||
(4, attribute_values_dict[att_value.name], 0)
|
||||
)
|
||||
create_values[
|
||||
"bom_product_template_attribute_value_ids"
|
||||
] = att_values_commands
|
||||
bom_line_create_values.append(create_values)
|
||||
cls.env["mrp.bom.line"].create(bom_line_create_values)
|
||||
new_bom_form = Form(new_bom)
|
||||
new_bom_form.lot_number_propagation = True
|
||||
for line_position, _bom_line in enumerate(new_bom.bom_line_ids):
|
||||
new_bom_line_form = new_bom_form.bom_line_ids.edit(line_position)
|
||||
new_bom_line_form.propagate_lot_number = True
|
||||
new_bom_line_form.save()
|
||||
return new_bom_form.save()
|
||||
72
mrp_lot_number_propagation/tests/test_mrp_bom.py
Normal file
72
mrp_lot_number_propagation/tests/test_mrp_bom.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import Form
|
||||
|
||||
from .common import Common
|
||||
|
||||
|
||||
class TestMrpBom(Common):
|
||||
def test_bom_display_lot_number_propagation(self):
|
||||
self.assertTrue(self.bom.display_lot_number_propagation)
|
||||
self.bom.product_tmpl_id.tracking = "none"
|
||||
self.assertFalse(self.bom.display_lot_number_propagation)
|
||||
|
||||
def test_bom_line_check_propagate_lot_number_not_tracked(self):
|
||||
form = Form(self.bom)
|
||||
form.lot_number_propagation = True
|
||||
# Flag a line that can't be propagated
|
||||
line_form = form.bom_line_ids.edit(2) # line without tracking
|
||||
line_form.propagate_lot_number = True
|
||||
line_form.save()
|
||||
with self.assertRaisesRegex(ValidationError, "Only components tracked"):
|
||||
form.save()
|
||||
|
||||
def test_bom_line_check_propagate_lot_number_tracked_by_lot(self):
|
||||
form = Form(self.bom)
|
||||
form.lot_number_propagation = True
|
||||
# Flag a line tracked by lot (not SN) which is not supported
|
||||
line_form = form.bom_line_ids.edit(1)
|
||||
line_form.propagate_lot_number = True
|
||||
line_form.save()
|
||||
with self.assertRaisesRegex(ValidationError, "Only components tracked"):
|
||||
form.save()
|
||||
|
||||
def test_bom_line_check_propagate_lot_number_same_tracking(self):
|
||||
form = Form(self.bom)
|
||||
form.lot_number_propagation = True
|
||||
# Flag a line whose tracking type is the same than the finished product
|
||||
line_form = form.bom_line_ids.edit(0)
|
||||
line_form.propagate_lot_number = True
|
||||
line_form.save()
|
||||
form.save()
|
||||
|
||||
def test_bom_check_propagate_lot_number(self):
|
||||
# Configure the BoM to propagate the lot/SN without enabling any line
|
||||
with self.assertRaisesRegex(ValidationError, "a line has to be configured"):
|
||||
self.bom.lot_number_propagation = True
|
||||
|
||||
def test_reset_tracking_on_bom_product(self):
|
||||
# Configure the BoM to propagate the lot/SN
|
||||
with Form(self.bom) as form:
|
||||
form.lot_number_propagation = True
|
||||
line_form = form.bom_line_ids.edit(0) # Line tracked by SN
|
||||
line_form.propagate_lot_number = True
|
||||
line_form.save()
|
||||
form.save()
|
||||
# Reset the tracking on the finished product
|
||||
with self.assertRaisesRegex(ValidationError, "A BoM propagating"):
|
||||
self.bom.product_tmpl_id.tracking = "none"
|
||||
|
||||
def test_reset_tracking_on_bom_component(self):
|
||||
# Configure the BoM to propagate the lot/SN
|
||||
with Form(self.bom) as form:
|
||||
form.lot_number_propagation = True
|
||||
line_form = form.bom_line_ids.edit(0) # Line tracked by SN
|
||||
line_form.propagate_lot_number = True
|
||||
line_form.save()
|
||||
form.save()
|
||||
# Reset the tracking on the component which propagates the SN
|
||||
with self.assertRaisesRegex(ValidationError, "This component is"):
|
||||
self.line_tracked_by_sn.product_id.tracking = "none"
|
||||
147
mrp_lot_number_propagation/tests/test_mrp_production.py
Normal file
147
mrp_lot_number_propagation/tests/test_mrp_production.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import Form
|
||||
|
||||
from .common import Common
|
||||
|
||||
|
||||
class TestMrpProduction(Common):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# Configure the BoM to propagate lot number
|
||||
cls._configure_bom()
|
||||
cls.order = cls._create_order(cls.bom_product_product, cls.bom)
|
||||
|
||||
@classmethod
|
||||
def _configure_bom(cls):
|
||||
with Form(cls.bom) as form:
|
||||
form.lot_number_propagation = True
|
||||
line_form = form.bom_line_ids.edit(0) # Line tracked by SN
|
||||
line_form.propagate_lot_number = True
|
||||
line_form.save()
|
||||
form.save()
|
||||
|
||||
@classmethod
|
||||
def _create_order(cls, product, bom):
|
||||
with Form(cls.env["mrp.production"]) as form:
|
||||
form.product_id = product
|
||||
form.bom_id = bom
|
||||
return form.save()
|
||||
|
||||
def _set_qty_done(self, order):
|
||||
for line in order.move_raw_ids.move_line_ids:
|
||||
line.qty_done = line.product_uom_qty
|
||||
order.qty_producing = order.product_qty
|
||||
|
||||
def test_order_propagated_lot_producing(self):
|
||||
self.assertTrue(self.order.is_lot_number_propagated) # set by onchange
|
||||
self._update_stock_component_qty(self.order)
|
||||
self.order.action_confirm()
|
||||
self.order.action_assign()
|
||||
self.assertTrue(self.order.is_lot_number_propagated) # set by action_confirm
|
||||
self.assertTrue(any(self.order.move_raw_ids.mapped("propagate_lot_number")))
|
||||
self._set_qty_done(self.order)
|
||||
self.assertEqual(self.order.propagated_lot_producing, self.LOT_NAME)
|
||||
|
||||
def test_order_write_lot_producing_id_not_allowed(self):
|
||||
with self.assertRaisesRegex(UserError, "not allowed"):
|
||||
self.order.write({"lot_producing_id": False})
|
||||
|
||||
def test_order_post_inventory(self):
|
||||
self._update_stock_component_qty(self.order)
|
||||
self.order.action_confirm()
|
||||
self.order.action_assign()
|
||||
self._set_qty_done(self.order)
|
||||
self.assertTrue(self.order.is_lot_number_propagated) # set by action_confirm
|
||||
self.order.button_mark_done()
|
||||
self.assertEqual(self.order.lot_producing_id.name, self.LOT_NAME)
|
||||
|
||||
def test_order_post_inventory_lot_already_exists_but_not_used(self):
|
||||
self._update_stock_component_qty(self.order)
|
||||
self.order.action_confirm()
|
||||
self.order.action_assign()
|
||||
self._set_qty_done(self.order)
|
||||
self.assertEqual(self.order.propagated_lot_producing, self.LOT_NAME)
|
||||
# Create a lot with the same number for the finished product
|
||||
# without any stock/quants (so not used at all) before validating the MO
|
||||
existing_lot = self.env["stock.production.lot"].create(
|
||||
{
|
||||
"product_id": self.order.product_id.id,
|
||||
"company_id": self.order.company_id.id,
|
||||
"name": self.order.propagated_lot_producing,
|
||||
}
|
||||
)
|
||||
self.order.button_mark_done()
|
||||
self.assertEqual(self.order.lot_producing_id, existing_lot)
|
||||
|
||||
def test_order_post_inventory_lot_already_exists_and_used(self):
|
||||
self._update_stock_component_qty(self.order)
|
||||
self.order.action_confirm()
|
||||
self.order.action_assign()
|
||||
self._set_qty_done(self.order)
|
||||
self.assertEqual(self.order.propagated_lot_producing, self.LOT_NAME)
|
||||
# Create a lot with the same number for the finished product
|
||||
# with some stock/quants (so it is considered as used) before
|
||||
# validating the MO
|
||||
existing_lot = self.env["stock.production.lot"].create(
|
||||
{
|
||||
"product_id": self.order.product_id.id,
|
||||
"company_id": self.order.company_id.id,
|
||||
"name": self.order.propagated_lot_producing,
|
||||
}
|
||||
)
|
||||
self._update_qty_in_location(
|
||||
self.env.ref("stock.stock_location_stock"),
|
||||
self.order.product_id,
|
||||
1,
|
||||
lot=existing_lot,
|
||||
)
|
||||
with self.assertRaisesRegex(UserError, "already exists and has been used"):
|
||||
self.order.button_mark_done()
|
||||
|
||||
def test_confirm_with_variant_ok(self):
|
||||
self._add_color_and_legs_variants(self.bom_product_template)
|
||||
self._add_color_and_legs_variants(self.product_template_tracked_by_sn)
|
||||
new_bom = self._create_bom_with_variants()
|
||||
self.assertTrue(new_bom.lot_number_propagation)
|
||||
# As all variants must have a single component
|
||||
# where lot must be propagated, there should not be any error
|
||||
for product in self.bom_product_template.product_variant_ids:
|
||||
new_order = self._create_order(product, new_bom)
|
||||
new_order.action_confirm()
|
||||
|
||||
def test_confirm_with_variant_multiple(self):
|
||||
self._add_color_and_legs_variants(self.bom_product_template)
|
||||
self._add_color_and_legs_variants(self.product_template_tracked_by_sn)
|
||||
new_bom = self._create_bom_with_variants()
|
||||
# Remove application on variant for first bom line
|
||||
# with this only the first variant of the product template
|
||||
# will have a single component where lot must be propagated
|
||||
new_bom.bom_line_ids[0].bom_product_template_attribute_value_ids = [(5, 0, 0)]
|
||||
for cnt, product in enumerate(self.bom_product_template.product_variant_ids):
|
||||
new_order = self._create_order(product, new_bom)
|
||||
if cnt == 0:
|
||||
new_order.action_confirm()
|
||||
else:
|
||||
with self.assertRaisesRegex(UserError, "multiple components"):
|
||||
new_order.action_confirm()
|
||||
|
||||
def test_confirm_with_variant_no(self):
|
||||
self._add_color_and_legs_variants(self.bom_product_template)
|
||||
self._add_color_and_legs_variants(self.product_template_tracked_by_sn)
|
||||
new_bom = self._create_bom_with_variants()
|
||||
for cnt, product in enumerate(self.bom_product_template.product_variant_ids):
|
||||
new_order = self._create_order(product, new_bom)
|
||||
if cnt == 0:
|
||||
# Fake the case with no component by removing the flag
|
||||
new_order.move_raw_ids.bom_line_id.write(
|
||||
{"propagate_lot_number": False}
|
||||
)
|
||||
with self.assertRaisesRegex(UserError, "no component"):
|
||||
new_order.action_confirm()
|
||||
new_order.action_assign()
|
||||
else:
|
||||
new_order.action_confirm()
|
||||
28
mrp_lot_number_propagation/views/mrp_bom.xml
Normal file
28
mrp_lot_number_propagation/views/mrp_bom.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2022 Camptocamp SA
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
|
||||
<record id="mrp_bom_form_view" model="ir.ui.view">
|
||||
<field name="name">mrp.bom.form.inherit</field>
|
||||
<field name="model">mrp.bom</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="after">
|
||||
<field name="display_lot_number_propagation" invisible="1" />
|
||||
<field
|
||||
name="lot_number_propagation"
|
||||
attrs="{'invisible': [('display_lot_number_propagation', '=', False)]}"
|
||||
/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='bom_line_ids']/tree" position="inside">
|
||||
<field name="display_propagate_lot_number" invisible="1" />
|
||||
<field
|
||||
name="propagate_lot_number"
|
||||
attrs="{'column_invisible': ['|', ('parent.display_lot_number_propagation', '=', False), ('parent.lot_number_propagation', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
25
mrp_lot_number_propagation/views/mrp_production.xml
Normal file
25
mrp_lot_number_propagation/views/mrp_production.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2022 Camptocamp SA
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
|
||||
<record id="mrp_production_form_view" model="ir.ui.view">
|
||||
<field name="name">mrp.production.form.inherit</field>
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<!-- Place new fields in the first group while being compatible with OE -->
|
||||
<xpath expr="//field[@name='id']/.." position="inside">
|
||||
<field name="is_lot_number_propagated" force_save="1" />
|
||||
</xpath>
|
||||
<label for="lot_producing_id" position="before">
|
||||
<field
|
||||
name="propagated_lot_producing"
|
||||
string="Lot/Serial Number"
|
||||
attrs="{'invisible': [('is_lot_number_propagated', '=', False)]}"
|
||||
/>
|
||||
</label>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
../../../../mrp_lot_number_propagation
|
||||
6
setup/mrp_lot_number_propagation/setup.py
Normal file
6
setup/mrp_lot_number_propagation/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
Reference in New Issue
Block a user