diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000000..542d351c7a2a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# generated from manifests external_dependencies +xlrd diff --git a/setup/stock_picking_import_serial_number/odoo/addons/stock_picking_import_serial_number b/setup/stock_picking_import_serial_number/odoo/addons/stock_picking_import_serial_number new file mode 120000 index 000000000000..8b4f10995402 --- /dev/null +++ b/setup/stock_picking_import_serial_number/odoo/addons/stock_picking_import_serial_number @@ -0,0 +1 @@ +../../../../stock_picking_import_serial_number \ No newline at end of file diff --git a/setup/stock_picking_import_serial_number/setup.py b/setup/stock_picking_import_serial_number/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_picking_import_serial_number/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_picking_import_serial_number/README.rst b/stock_picking_import_serial_number/README.rst new file mode 100644 index 000000000000..73d022696fff --- /dev/null +++ b/stock_picking_import_serial_number/README.rst @@ -0,0 +1,108 @@ +=================================== +Stock Picking Import Serial Numbers +=================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e087f27ef36a7e27f0c283c593ce9a2ab863373ae51d6badda44db2f02e8c9b8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-workflow/tree/14.0/stock_picking_import_serial_number + :alt: OCA/stock-logistics-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-workflow-14-0/stock-logistics-workflow-14-0-stock_picking_import_serial_number + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-workflow&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of stock module to allow import serial numbers +from an excel file. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +#. Go to a *Inventory > Configuration > Settings*. +#. Choose the product field which yo want to search products. +#. Choose the file column index where product reference is stored. +#. Choose the file column index where serial number reference is stored. + +Usage +===== + +To use this module you need to: + +#. Go to *Inventory > Incoming* and create one. +#. Click on button "Import S/N". +#. Select an excel file. +#. Click on import button. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * Carlos Dauden + * Sergio Teruel + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-sergio-teruel| image:: https://github.com/sergio-teruel.png?size=40px + :target: https://github.com/sergio-teruel + :alt: sergio-teruel + +Current `maintainer `__: + +|maintainer-sergio-teruel| + +This module is part of the `OCA/stock-logistics-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_picking_import_serial_number/__init__.py b/stock_picking_import_serial_number/__init__.py new file mode 100644 index 000000000000..127685b7ddc0 --- /dev/null +++ b/stock_picking_import_serial_number/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models +from . import wizard diff --git a/stock_picking_import_serial_number/__manifest__.py b/stock_picking_import_serial_number/__manifest__.py new file mode 100644 index 000000000000..268fb935648f --- /dev/null +++ b/stock_picking_import_serial_number/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2022 Tecnativa - Sergio Teruel +# Copyright 2023 Tecnativa - Carolina Fernandez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Stock Picking Import Serial Numbers", + "summary": "Import S/N from excel file for incoming pickings", + "version": "15.0.1.0.0", + "development_status": "Production/Stable", + "category": "stock", + "website": "https://github.com/OCA/stock-logistics-workflow", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "depends": ["stock"], + "data": [ + "wizard/import_serial_number_view.xml", + "views/res_config_settings_views.xml", + "views/stock_picking.xml", + "security/ir.model.access.csv", + ], + "external_dependencies": {"python": ["xlrd"]}, + "maintainers": ["sergio-teruel"], +} diff --git a/stock_picking_import_serial_number/i18n/es.po b/stock_picking_import_serial_number/i18n/es.po new file mode 100644 index 000000000000..36370c035a7e --- /dev/null +++ b/stock_picking_import_serial_number/i18n/es.po @@ -0,0 +1,188 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_picking_import_serial_number +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-05-04 10:48+0000\n" +"PO-Revision-Date: 2022-05-04 12:49+0200\n" +"Last-Translator: Sergio Teruel \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.3\n" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Field to search products" +msgstr "Campo por el que buscar productos" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "File column index for products" +msgstr "" +"Índice de la columna que contiene el producto" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "File column index for serial number" +msgstr "" +"Índice de la columna que contiene el número de " +"serie" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__res_config_settings__default_sn_search_product_by_field__barcode +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__stock_picking_import_serial_number_wiz__sn_search_product_by_field__barcode +msgid "Barcode" +msgstr "Código de barras" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.stock_picking_import_serial_number_wiz +msgid "Choose a file to import..." +msgstr "Seleccione un archivo a importar..." + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__default_sn_serial_column_index +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__sn_serial_column_index +msgid "Column index for S/N" +msgstr "Índice de la columna para S/N" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__default_sn_product_column_index +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__sn_product_column_index +msgid "Column index for product" +msgstr "Índice de la columna para el producto" + +#. module: stock_picking_import_serial_number +#: model:ir.model,name:stock_picking_import_serial_number.model_res_config_settings +msgid "Config Settings" +msgstr "Opciones de Configuración" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__create_date +msgid "Created on" +msgstr "Creado en" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__data_file +msgid "File to import" +msgstr "Fichero a importar" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__filename +msgid "Filename" +msgstr "Nombre de archivo" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__id +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__id +msgid "ID" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.actions.act_window,name:stock_picking_import_serial_number.action_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.stock_picking_import_serial_number_wiz +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.view_picking_form +msgid "Import S/N" +msgstr "Importar S/N" + +#. module: stock_picking_import_serial_number +#: model:ir.model,name:stock_picking_import_serial_number.model_stock_picking_import_serial_number_wiz +msgid "Import S/N wizard" +msgstr "Asistente de importación S/N" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__overwrite_serial +msgid "Overwrite Serial" +msgstr "Sobreescribir S/N" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__picking_ids +msgid "Picking" +msgstr "Albarán" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__res_config_settings__default_sn_search_product_by_field__default_code +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__stock_picking_import_serial_number_wiz__sn_search_product_by_field__default_code +msgid "Reference" +msgstr "Referencia" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__default_sn_search_product_by_field +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__sn_search_product_by_field +msgid "Search product by field" +msgstr "Buscar producto por campo" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Select the field to search products" +msgstr "Seleccione el campo para buscar productos" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Select the index file column which contains the product info" +msgstr "" +"Selecciones el índice de la columna que contiene información del producto" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Select the index file column which contains the serial numbers info" +msgstr "" +"Selecciones el índice de la columna que contiene información del número se " +"serie" + +#. module: stock_picking_import_serial_number +#: code:addons/stock_picking_import_serial_number/wizard/import_serial_number_wizard.py:0 +#, python-format +msgid "You must upload file to import records" +msgstr "Debe de subir un fichero para importar registros" + +#. module: stock_picking_import_serial_number +#: code:addons/stock_picking_import_serial_number/wizard/import_serial_number_wizard.py:0 +#, python-format +msgid "" +"You only can import S/N for picking operations with creation lots checked" +msgstr "Solo puede importar S/N para operaciones con creación de lotes marcado" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.stock_picking_import_serial_number_wiz +msgid "_Cancel" +msgstr "_Cancelar" + +#, python-format +#~ msgid "You only can import S/N for incoming moves" +#~ msgstr "Solo puede importar S/N para albaranes de entrada" diff --git a/stock_picking_import_serial_number/i18n/stock_picking_import_serial_number.pot b/stock_picking_import_serial_number/i18n/stock_picking_import_serial_number.pot new file mode 100644 index 000000000000..3179d1d62d54 --- /dev/null +++ b/stock_picking_import_serial_number/i18n/stock_picking_import_serial_number.pot @@ -0,0 +1,173 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_picking_import_serial_number +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.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: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Field to search products" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "File column index for products" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "File column index for serial number" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__res_config_settings__default_sn_search_product_by_field__barcode +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__stock_picking_import_serial_number_wiz__sn_search_product_by_field__barcode +msgid "Barcode" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.stock_picking_import_serial_number_wiz +msgid "Choose a file to import..." +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__default_sn_serial_column_index +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__sn_serial_column_index +msgid "Column index for S/N" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__default_sn_product_column_index +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__sn_product_column_index +msgid "Column index for product" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model,name:stock_picking_import_serial_number.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__create_uid +msgid "Created by" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__create_date +msgid "Created on" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__data_file +msgid "File to import" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__filename +msgid "Filename" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__id +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__id +msgid "ID" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.actions.act_window,name:stock_picking_import_serial_number.action_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.stock_picking_import_serial_number_wiz +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.view_picking_form +msgid "Import S/N" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model,name:stock_picking_import_serial_number.model_stock_picking_import_serial_number_wiz +msgid "Import S/N wizard" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz____last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__write_date +msgid "Last Updated on" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__overwrite_serial +msgid "Overwrite Serial" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__picking_ids +msgid "Picking" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__res_config_settings__default_sn_search_product_by_field__default_code +#: model:ir.model.fields.selection,name:stock_picking_import_serial_number.selection__stock_picking_import_serial_number_wiz__sn_search_product_by_field__default_code +msgid "Reference" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_res_config_settings__default_sn_search_product_by_field +#: model:ir.model.fields,field_description:stock_picking_import_serial_number.field_stock_picking_import_serial_number_wiz__sn_search_product_by_field +msgid "Search product by field" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Select the field to search products" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Select the index file column which contains the product info" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.res_config_settings_view_form +msgid "Select the index file column which contains the serial numbers info" +msgstr "" + +#. module: stock_picking_import_serial_number +#: code:addons/stock_picking_import_serial_number/wizard/import_serial_number_wizard.py:0 +#, python-format +msgid "You must upload file to import records" +msgstr "" + +#. module: stock_picking_import_serial_number +#: code:addons/stock_picking_import_serial_number/wizard/import_serial_number_wizard.py:0 +#, python-format +msgid "" +"You only can import S/N for picking operations with creation lots checked" +msgstr "" + +#. module: stock_picking_import_serial_number +#: model_terms:ir.ui.view,arch_db:stock_picking_import_serial_number.stock_picking_import_serial_number_wiz +msgid "_Cancel" +msgstr "" diff --git a/stock_picking_import_serial_number/models/__init__.py b/stock_picking_import_serial_number/models/__init__.py new file mode 100644 index 000000000000..c541e765140a --- /dev/null +++ b/stock_picking_import_serial_number/models/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2022 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import res_config_settings diff --git a/stock_picking_import_serial_number/models/res_config_settings.py b/stock_picking_import_serial_number/models/res_config_settings.py new file mode 100644 index 000000000000..ef45b2489de6 --- /dev/null +++ b/stock_picking_import_serial_number/models/res_config_settings.py @@ -0,0 +1,27 @@ +# Copyright 2022 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + default_sn_search_product_by_field = fields.Selection( + [("default_code", "Reference"), ("barcode", "Barcode")], + string="Search product by field", + default="default_code", + default_model="stock.picking.import.serial.number.wiz", + required=True, + ) + + default_sn_product_column_index = fields.Integer( + string="Column index for product", + default_model="stock.picking.import.serial.number.wiz", + default=0, + ) + default_sn_serial_column_index = fields.Integer( + string="Column index for S/N", + default_model="stock.picking.import.serial.number.wiz", + default="1", + ) diff --git a/stock_picking_import_serial_number/readme/CONFIGURE.rst b/stock_picking_import_serial_number/readme/CONFIGURE.rst new file mode 100644 index 000000000000..ad72b3ccb84e --- /dev/null +++ b/stock_picking_import_serial_number/readme/CONFIGURE.rst @@ -0,0 +1,6 @@ +To configure this module, you need to: + +#. Go to a *Inventory > Configuration > Settings*. +#. Choose the product field which yo want to search products. +#. Choose the file column index where product reference is stored. +#. Choose the file column index where serial number reference is stored. diff --git a/stock_picking_import_serial_number/readme/CONTRIBUTORS.rst b/stock_picking_import_serial_number/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..1fa35e49f9b6 --- /dev/null +++ b/stock_picking_import_serial_number/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +* `Tecnativa `_: + + * Carlos Dauden + * Sergio Teruel + * Carolina Fernandez diff --git a/stock_picking_import_serial_number/readme/DESCRIPTION.rst b/stock_picking_import_serial_number/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..32df165082a9 --- /dev/null +++ b/stock_picking_import_serial_number/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module extends the functionality of stock module to allow import serial numbers +from an excel file. diff --git a/stock_picking_import_serial_number/readme/USAGE.rst b/stock_picking_import_serial_number/readme/USAGE.rst new file mode 100644 index 000000000000..e3087bcbb3e8 --- /dev/null +++ b/stock_picking_import_serial_number/readme/USAGE.rst @@ -0,0 +1,6 @@ +To use this module you need to: + +#. Go to *Inventory > Incoming* and create one. +#. Click on button "Import S/N". +#. Select an excel file. +#. Click on import button. diff --git a/stock_picking_import_serial_number/security/ir.model.access.csv b/stock_picking_import_serial_number/security/ir.model.access.csv new file mode 100644 index 000000000000..b20bb59b752c --- /dev/null +++ b/stock_picking_import_serial_number/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_stock_picking_import_serial_number_wiz,access_stock_picking_import_serial_number_wiz,model_stock_picking_import_serial_number_wiz,base.group_user,1,1,1,1 diff --git a/stock_picking_import_serial_number/static/description/icon.png b/stock_picking_import_serial_number/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/stock_picking_import_serial_number/static/description/icon.png differ diff --git a/stock_picking_import_serial_number/static/description/index.html b/stock_picking_import_serial_number/static/description/index.html new file mode 100644 index 000000000000..7c17c7566667 --- /dev/null +++ b/stock_picking_import_serial_number/static/description/index.html @@ -0,0 +1,454 @@ + + + + + + +Stock Picking Import Serial Numbers + + + +
+

Stock Picking Import Serial Numbers

+ + +

Beta License: AGPL-3 OCA/stock-logistics-workflow Translate me on Weblate Try me on Runboat

+

This module extends the functionality of stock module to allow import serial numbers +from an excel file.

+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Go to a Inventory > Configuration > Settings.
  2. +
  3. Choose the product field which yo want to search products.
  4. +
  5. Choose the file column index where product reference is stored.
  6. +
  7. Choose the file column index where serial number reference is stored.
  8. +
+
+
+

Usage

+

To use this module you need to:

+
    +
  1. Go to Inventory > Incoming and create one.
  2. +
  3. Click on button “Import S/N”.
  4. +
  5. Select an excel file.
  6. +
  7. Click on import button.
  8. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

sergio-teruel

+

This module is part of the OCA/stock-logistics-workflow project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_picking_import_serial_number/tests/__init__.py b/stock_picking_import_serial_number/tests/__init__.py new file mode 100644 index 000000000000..0a577e140384 --- /dev/null +++ b/stock_picking_import_serial_number/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import common +from . import test_stock_picking_import_serial_number diff --git a/stock_picking_import_serial_number/tests/common.py b/stock_picking_import_serial_number/tests/common.py new file mode 100644 index 000000000000..4faa5c1a4547 --- /dev/null +++ b/stock_picking_import_serial_number/tests/common.py @@ -0,0 +1,67 @@ +# Copyright 2022 Tecnativa - Sergio Teruel +# Copyright 2022 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +class CommonStockPickingImportSerial(object): + def assertUniqueIn(self, element_list): + elements = [] + for element in element_list: + if element in elements: + raise Exception("Element %s is not unique in list" % element) + elements.append(element) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.lot_obj = cls.env["stock.production.lot"] + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.picking_type_in = cls.env.ref("stock.picking_type_in") + cls.picking_type_in.use_create_lots = True + cls.picking_type_in.show_reserved = True + cls.supplier_location = cls.env.ref("stock.stock_location_suppliers") + cls.supplier = cls.env["res.partner"].create({"name": "Supplier - test"}) + cls.file = b"0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAOwADAP7/CQAGAAAAAAAAAAAAAAABAAAACQAAAAAAAAAAEAAAAgAAAAEAAAD+////AAAAAAAAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9//////////7///8EAAAABQAAAAYAAAAHAAAACAAAAP7///8KAAAA/v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////1IAbwBvAHQAIABFAG4AdAByAHkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAUA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v///wAAAAAAAAAAAQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAAFgAAABcAAAAYAAAAGQAAABoAAAAbAAAAHAAAAB0AAAAeAAAAHwAAACAAAAAhAAAAIgAAACMAAAAkAAAAJQAAACYAAAAnAAAA/v///ykAAAD+/////v///ywAAAAtAAAA/v///y8AAAD+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8JCBAAAAYFALsNzAcAAAAABgAAAOEAAgCwBMEAAgAAAOIAAABcAHAABAAAQ2FsYyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEIAAgCwBGEBAgAAAMABAAA9AQIAAQCcAAIADgCvAQIAAAC8AQIAAAA9ABIAAAAAAABAACA4AAAAAAABAPQBQAACAAAAjQACAAAAIgACAAAADgACAAEAtwECAAAA2gACAAAAMQAaAMgAAAD/f5ABAAAAAgAABQFBAHIAaQBhAGwAMQAaAMgAAAD/f5ABAAAAAAAABQFBAHIAaQBhAGwAMQAaAMgAAAD/f5ABAAAAAAAABQFBAHIAaQBhAGwAMQAaAMgAAAD/f5ABAAAAAAAABQFBAHIAaQBhAGwAMQAuAMgAAAD/f5ABAAAAAQAADwFUAGkAbQBlAHMAIABOAGUAdwAgAFIAbwBtAGEAbgAeBAwApAAHAABHZW5lcmFs4AAUAAAApAD1/yAAAAAAAAAAAAAAAMAg4AAUAAEAAAD1/yAAAPQAAAAAAAAAAMAg4AAUAAEAAAD1/yAAAPQAAAAAAAAAAMAg4AAUAAIAAAD1/yAAAPQAAAAAAAAAAMAg4AAUAAIAAAD1/yAAAPQAAAAAAAAAAMAg4AAUAAAAAAD1/yAAAPQAAAAAAAAAAMAg4AAUAAAAAAD1/yAAAPQAAAAAAAAAAMAg4AAUAAAAAAD1/yAAAPQAAAAAAAAAAMAg4AAUAAAAAAD1/yAAAPQAAAAAAAAAAMAg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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAgADAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAASQAAAAAAAAABAE8AbABlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgACAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAUAAAAAAAAAAUAUwB1AG0AbQBhAHIAeQBJAG4AZgBvAHIAbQBhAHQAaQBvAG4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAIA/////wUAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKwAAAKwAAAAAAAAABQBEAG8AYwB1AG0AZQBuAHQAUwB1AG0AbQBhAHIAeQBJAG4AZgBvAHIAbQBhAHQAaQBvAG4AAAAAAAAAAAAAADgAAgD///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuAAAAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v///wAAAAAAAAAA" # noqa: B950 + + @classmethod + def _create_product(cls, tracking="lot", reference=None): + name = "{tracking}".format(tracking=tracking) + vals = { + "name": name, + "type": "product", + "tracking": tracking, + } + if reference: + vals["default_code"] = reference + return cls.env["product.product"].create(vals) + + @classmethod + def _create_picking(cls): + return ( + cls.env["stock.picking"] + .with_context(default_picking_type_id=cls.picking_type_in.id) + .create( + { + "partner_id": cls.supplier.id, + "picking_type_id": cls.picking_type_in.id, + "location_id": cls.supplier_location.id, + } + ) + ) + + @classmethod + def _create_move(cls, picking, product=None, qty=1.0): + location_dest = picking.picking_type_id.default_location_dest_id + cls.move = cls.env["stock.move"].create( + { + "name": "test-{product}".format(product=product.name), + "product_id": product.id, + "picking_id": picking.id, + "picking_type_id": picking.picking_type_id.id, + "product_uom_qty": qty, + "product_uom": product.uom_id.id, + "location_id": cls.supplier_location.id, + "location_dest_id": location_dest.id, + } + ) diff --git a/stock_picking_import_serial_number/tests/test_stock_picking_import_serial_number.py b/stock_picking_import_serial_number/tests/test_stock_picking_import_serial_number.py new file mode 100644 index 000000000000..a333c190944b --- /dev/null +++ b/stock_picking_import_serial_number/tests/test_stock_picking_import_serial_number.py @@ -0,0 +1,75 @@ +# Copyright 2022 Tecnativa - Sergio Teruel +# Copyright 2023 Tecnativa - Carolina Fernandez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.exceptions import UserError +from odoo.tests import TransactionCase + +from .common import CommonStockPickingImportSerial + + +class TestStockPickingImportSN(CommonStockPickingImportSerial, TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Create 3 products with lot/serial and auto_create True/False + cls.product_lot = cls._create_product() + cls.product_serial = cls._create_product(tracking="serial", reference="1234") + cls.product_no_tracking = cls._create_product(tracking="none") + + cls.picking_in_01 = cls._create_picking() + cls._create_move(picking=cls.picking_in_01, product=cls.product_lot, qty=2.0) + cls._create_move(picking=cls.picking_in_01, product=cls.product_serial, qty=3.0) + cls._create_move( + picking=cls.picking_in_01, product=cls.product_no_tracking, qty=4.0 + ) + + cls.picking_in_01.action_assign() + + cls.picking_in_02 = cls._create_picking() + cls._create_move(picking=cls.picking_in_02, product=cls.product_lot, qty=2.0) + cls._create_move(picking=cls.picking_in_02, product=cls.product_serial, qty=3.0) + cls._create_move( + picking=cls.picking_in_02, product=cls.product_no_tracking, qty=4.0 + ) + cls.picking_in_02.action_assign() + + def _create_wizard(self, pickings=False): + if not pickings: + pickings = self.picking_in_01 | self.picking_in_02 + return self.env["stock.picking.import.serial.number.wiz"].create( + { + "picking_ids": [(6, 0, pickings.ids)], + "data_file": self.file, + "filename": "SNImport.xls", + } + ) + + def test_import_serial_number_no_file(self): + wiz = self._create_wizard() + wiz.data_file = False + with self.assertRaises(UserError): + wiz.action_import() + + def test_import_serial_number_no_create_lot(self): + self.picking_type_in.use_create_lots = False + wiz = self._create_wizard() + with self.assertRaises(UserError): + wiz.action_import() + + def test_import_serial_number(self): + wiz = self._create_wizard() + wiz.action_import() + smls = self.picking_in_01.move_line_ids.filtered("lot_name") + self.assertEqual(len(smls), 3) + smls = self.picking_in_02.move_line_ids.filtered("lot_name") + self.assertEqual(len(smls), 3) + + def test_import_serial_number_no_show_reserved(self): + self.picking_in_01.picking_type_id.show_reserved = False + picking = self.picking_in_01.copy() + picking.action_confirm() + picking.action_assign() + wiz = self._create_wizard(pickings=picking) + wiz.action_import() + smls = picking.move_line_nosuggest_ids.filtered("lot_name") + self.assertEqual(len(smls), 6) diff --git a/stock_picking_import_serial_number/views/res_config_settings_views.xml b/stock_picking_import_serial_number/views/res_config_settings_views.xml new file mode 100644 index 000000000000..f22220cc02a8 --- /dev/null +++ b/stock_picking_import_serial_number/views/res_config_settings_views.xml @@ -0,0 +1,54 @@ + + + + res.config.settings + + +
+

Import S/N

+
+
+
+
+ Field to search products +
+ Select the field to search products +
+
+ +
+
+
+
+
+
+
+ File column index for products +
+ Select the index file column which contains the product info +
+
+ +
+
+
+
+
+ File column index for serial number +
+ Select the index file column which contains the serial numbers info +
+
+ +
+
+
+
+
+ + + diff --git a/stock_picking_import_serial_number/views/stock_picking.xml b/stock_picking_import_serial_number/views/stock_picking.xml new file mode 100644 index 000000000000..e27bba922bfa --- /dev/null +++ b/stock_picking_import_serial_number/views/stock_picking.xml @@ -0,0 +1,17 @@ + + + stock.picking + + + + + + diff --git a/stock_picking_import_serial_number/wizard/__init__.py b/stock_picking_import_serial_number/wizard/__init__.py new file mode 100644 index 000000000000..d073155b791c --- /dev/null +++ b/stock_picking_import_serial_number/wizard/__init__.py @@ -0,0 +1 @@ +from . import import_serial_number_wizard diff --git a/stock_picking_import_serial_number/wizard/import_serial_number_view.xml b/stock_picking_import_serial_number/wizard/import_serial_number_view.xml new file mode 100644 index 000000000000..295a4f559b37 --- /dev/null +++ b/stock_picking_import_serial_number/wizard/import_serial_number_view.xml @@ -0,0 +1,40 @@ + + + + stock.picking.import.serial.number.wiz.form + stock.picking.import.serial.number.wiz + +
+ + + + + + + + +
+
+
+
+
+ + Import S/N + ir.actions.act_window + stock.picking.import.serial.number.wiz + form + + new + +
diff --git a/stock_picking_import_serial_number/wizard/import_serial_number_wizard.py b/stock_picking_import_serial_number/wizard/import_serial_number_wizard.py new file mode 100644 index 000000000000..e6d19faa5fcb --- /dev/null +++ b/stock_picking_import_serial_number/wizard/import_serial_number_wizard.py @@ -0,0 +1,108 @@ +# Copyright 2022 Tecnativa - Sergio Teruel +# Copyright 2023 Tecnativa - Carolina Fernandez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import base64 + +import xlrd + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class StockPickingImportSerialNumber(models.TransientModel): + _name = "stock.picking.import.serial.number.wiz" + _description = "Import S/N wizard" + + @api.model + def default_get(self, fields): + res = super().default_get(fields) + if ( + self.env.context.get("active_ids") + and self.env.context.get("active_model") == "stock.picking" + ): + pickings = self.env["stock.picking"].browse( + self.env.context.get("active_ids") + ) + if pickings.exists(): + res.update({"picking_ids": [(6, 0, pickings.ids)]}) + return res + + picking_ids = fields.Many2many("stock.picking") + data_file = fields.Binary(string="File to import") + filename = fields.Char() + overwrite_serial = fields.Boolean() + # Fields filled by settings. This names are special + sn_search_product_by_field = fields.Selection( + [("default_code", "Reference"), ("barcode", "Barcode")], + string="Search product by field", + default="default_code", + ) + sn_product_column_index = fields.Integer( + string="Column index for product", default=0 + ) + sn_serial_column_index = fields.Integer(string="Column index for S/N", default="1") + + def action_import(self): + if self.picking_ids.filtered(lambda p: not p.picking_type_id.use_create_lots): + raise UserError( + _( + "You only can import S/N for picking operations with" + " creation lots checked" + ) + ) + if not self.data_file: + raise UserError(_("You must upload file to import records")) + xl_workbook = False + xl_sheet = False + if self.filename.split(".")[1] in ["xls", "xlsx"]: + xl_workbook = xlrd.open_workbook( + file_contents=base64.b64decode(self.data_file) + ) + xl_sheet = xl_workbook.sheet_by_index(0) + for picking in self.picking_ids: + move_lines = picking.mapped("move_line_ids").filtered( + lambda ln: ln.product_id.tracking == "serial" + and ln.picking_id.picking_type_id.use_create_lots + ) + self._import_serial_number(xl_sheet, move_lines, picking) + self.data_file = False + + def _import_serial_number(self, xl_sheet, stock_move_lines, picking): + product_file_set = set() + serial_list = [] + for row_idx in range(1, xl_sheet.nrows): + product = str(xl_sheet.cell(row_idx, self.sn_product_column_index).value) + serial = str(xl_sheet.cell(row_idx, self.sn_serial_column_index).value) + product_file_set.add(product) + serial_list.append((product, serial)) + + products = self.env["product.product"].search( + [(self.sn_search_product_by_field, "in", list(product_file_set))] + ) + for item in serial_list: + product = products.filtered( + lambda p: p[self.sn_search_product_by_field] == item[0] + ) + if picking.picking_type_id.show_reserved: + smls = stock_move_lines.filtered(lambda ln: ln.product_id == product) + for sml in smls: + if not sml.lot_name or self.overwrite_serial: + sml.lot_name = item[1] + sml.qty_done = 1.0 + # Only assign one serial + break + # TODO: Check if product is present on initial demand?? + # elif product and picking.move_lines.filtered(lambda ln: ln.product_id == product) + elif product: + self.env["stock.move.line"].create( + { + "picking_id": picking.id, + "location_id": picking.location_id.id, + "location_dest_id": picking.location_dest_id.id, + "product_id": product.id, + "product_uom_id": product.uom_id.id, + "lot_name": item[1], + "product_uom_qty": 0.0, + "qty_done": 1.0, + } + )