Kaynağa Gözat

Merge pull request #23 from M22TechConsulting/import_layaout_prod

[ADD] custom_import_layout: add module
FernizaM22 1 yıl önce
ebeveyn
işleme
cf06e8880d

+ 3 - 0
custom_import_layout/__init__.py

@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import models

+ 21 - 0
custom_import_layout/__manifest__.py

@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+{
+    'name': "Importación de layouts",
+    'summary': """
+        Importación de layouts por medio de excel.
+    """,
+    'description': """
+        Importación de layouts por medio de excel en los diferentes modelos.
+    """,
+    'author': "M22",
+    'website': "https://www.m22.mx",
+    'category': 'Import',
+    'version': '16.0.1',
+    'depends': ['base','sale','purchase'],
+    'data': [
+        'security/ir.model.access.csv',
+        'views/import_layout_rule.xml',
+        'views/import_layout.xml'
+    ],
+    'license': 'AGPL-3'
+}

BIN
custom_import_layout/__pycache__/__init__.cpython-37.pyc


+ 3 - 0
custom_import_layout/controllers/__init__.py

@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import controllers

BIN
custom_import_layout/controllers/__pycache__/__init__.cpython-37.pyc


BIN
custom_import_layout/controllers/__pycache__/controllers.cpython-37.pyc


+ 21 - 0
custom_import_layout/controllers/controllers.py

@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# from odoo import http
+
+
+# class CustomImportLayout(http.Controller):
+#     @http.route('/custom_import_layout/custom_import_layout', auth='public')
+#     def index(self, **kw):
+#         return "Hello, world"
+
+#     @http.route('/custom_import_layout/custom_import_layout/objects', auth='public')
+#     def list(self, **kw):
+#         return http.request.render('custom_import_layout.listing', {
+#             'root': '/custom_import_layout/custom_import_layout',
+#             'objects': http.request.env['custom_import_layout.custom_import_layout'].search([]),
+#         })
+
+#     @http.route('/custom_import_layout/custom_import_layout/objects/<model("custom_import_layout.custom_import_layout"):obj>', auth='public')
+#     def object(self, obj, **kw):
+#         return http.request.render('custom_import_layout.object', {
+#             'object': obj
+#         })

+ 30 - 0
custom_import_layout/demo/demo.xml

@@ -0,0 +1,30 @@
+<odoo>
+    <data>
+<!--
+          <record id="object0" model="custom_import_layout.custom_import_layout">
+            <field name="name">Object 0</field>
+            <field name="value">0</field>
+          </record>
+
+          <record id="object1" model="custom_import_layout.custom_import_layout">
+            <field name="name">Object 1</field>
+            <field name="value">10</field>
+          </record>
+
+          <record id="object2" model="custom_import_layout.custom_import_layout">
+            <field name="name">Object 2</field>
+            <field name="value">20</field>
+          </record>
+
+          <record id="object3" model="custom_import_layout.custom_import_layout">
+            <field name="name">Object 3</field>
+            <field name="value">30</field>
+          </record>
+
+          <record id="object4" model="custom_import_layout.custom_import_layout">
+            <field name="name">Object 4</field>
+            <field name="value">40</field>
+          </record>
+-->
+    </data>
+</odoo>

+ 5 - 0
custom_import_layout/models/__init__.py

@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from . import import_layout_rule
+from . import import_layout_rule_line
+from . import import_layout

BIN
custom_import_layout/models/__pycache__/__init__.cpython-37.pyc


BIN
custom_import_layout/models/__pycache__/import_layout.cpython-37.pyc


BIN
custom_import_layout/models/__pycache__/import_layout_rule.cpython-37.pyc


BIN
custom_import_layout/models/__pycache__/import_layout_rule_line.cpython-37.pyc


+ 129 - 0
custom_import_layout/models/import_layout.py

@@ -0,0 +1,129 @@
+from odoo import api, fields, models
+from odoo.exceptions import ValidationError
+import pandas as pd
+import os
+import base64
+
+
+class ImportLayput(models.TransientModel):
+    _name = 'import.layout'
+    _description = 'Importación de layout'
+    _rec_name = "rule_id"
+
+    rule_id = fields.Many2one(comodel_name="import.layout.rule", string="Plantilla")
+    file_data = fields.Binary(string="Archivo")
+    file_name = fields.Char(string="Nombre del archivo")
+    company_id = fields.Many2one(comodel_name="res.company", string="Empresa", default=lambda self: self.env.company)
+
+    def read_excel(self):
+        # Validar la extensión
+        self.extension_validator(self.file_name)
+        # Validar que se cargue el modelo correspondiente al menú
+        self.validate_model_id()
+        # Lectura del excel
+        data_decode = base64.b64decode(self.file_data)
+        df = pd.read_excel(data_decode)
+        group_line_id = self.rule_id.main_columns_ids.filtered(lambda line: line.group_by)
+        df_group_by = df.groupby(group_line_id.name)
+        move_ids = self.get_data(df_group_by)
+        if move_ids:
+            return {
+                'type': 'ir.actions.client',
+                'tag': 'display_notification',
+                'params': {
+                    'type': 'success',
+                    'message': (f'Se crearon los siguientes movimientos {move_ids}.'),
+                    'next': {'type': 'ir.actions.act_window_close'},
+                }
+            }
+
+    def validate_model_id(self):
+        if self.env.context.get("sale_import") and self.sudo().rule_id.main_model_id.model != "sale.order":
+            raise ValidationError("Es necesario asignar una regla relacionada a las ordenes de venta.")
+        elif self.env.context.get("purchase_import") and self.sudo().rule_id.main_model_id.model != "purchase.order":
+            raise ValidationError("Es necesario asignar una regla relacionada a las ordenes de compra.")
+
+    def extension_validator(self, file_name):
+        name, extension = os.path.splitext(file_name)
+        valid = True if str(extension).upper() == '.XLSX' or str(extension).upper() == '.XLS' else False
+        if not valid:
+            raise ValidationError(
+                "La extensión del archivo no es valida con el formato de excel, esta debe ser .xlsx o xls")
+
+    def get_data(self, df):
+        try:
+            main_name = []
+            for ciudad, grupo in df:
+                main_data = dict()
+                data_list = []
+                # Obtencion de los datos del cabecero
+                for column in self.rule_id.main_columns_ids:
+                    value = self.get_field_info(column.field_id, grupo.loc[grupo.index[0], column.name])
+                    main_data[f"{column.field_id.name}"] = value
+                # Creación del cabecero
+                if main_data:
+                    main_id = self.env[self.rule_id.main_model_id.model].sudo().create(main_data)
+                    main_name.append(main_id.name)
+                    # Obtenciónd de los datos de las lineas de la orden
+                    for line_index in range(grupo.shape[0]):
+                        data = {}
+                        for column in self.rule_id.column_ids:
+                            value = self.get_field_info(column.field_id,
+                                                        grupo.loc[grupo.index[line_index], column.name])
+                            data[f"{column.field_id.name}"] = value
+                            main_field = self.env[self.rule_id.model_id.model].fields_get()
+                            related_field = next((campo for campo, datos in main_field.items() if
+                                                  datos.get('relation') == f'{self.rule_id.main_model_id.model}'), None)
+                            # Relación de las lineas con su llave primaria
+                            data[f"{related_field}"] = main_id.id
+                        if data:
+                            data_list.append(data)
+                    # Creación de las lineas de las ordenes
+                    if data_list:
+                        line_ids = self.env[self.rule_id.model_id.model].sudo().create(data_list)
+            return main_name if main_name else False
+        except Exception as e:
+            raise ValidationError(
+                f"Hay algun problema con la configuración de la regla, posiblemente el nombre de las columnas no coincidan con el archivo, como por ejemplo: {e}")
+
+    # Obtener los valores de los campos dependiendo del tipo de campo
+    def get_field_info(self, field_id, value):
+        if field_id.ttype in ["many2one", "many2one_reference"]:
+            if str(value).isnumeric():
+                if len(str(value)) <= 9:
+                    field_value = self.env[field_id.relation].sudo().search([("id", "=", int(value))], limit=1)
+                else:
+                    field_value = False
+                if not field_value and field_id.relation in ['product.product', 'product.template']:
+                    field_value = self.env[field_id.relation].sudo().search(["|",("default_code", "=", value),("barcode","=",value)], limit=1)
+                    if not field_value:
+                        raise ValidationError(
+                            f"No se encontro un registro en el modelo de {field_id.relation} con el id {value}")
+                elif not field_value:
+                    raise ValidationError(
+                        f"No se encontro un registro en el modelo de {field_id.relation} con el código {value}")
+                value = field_value.id
+            elif field_id.relation in ['product.product', 'product.template']:
+                field_value = self.env[field_id.relation].sudo().search(["|",("default_code", "=", value),("barcode","=",value)], limit=1)
+                if not field_value:
+                    field_value = self.env[field_id.relation].sudo().search([("name", "=", value)], limit=1)
+                    if not field_value:
+                        raise ValidationError(
+                            f"No se encontro un registro en el modelo de {field_id.relation} con el código {value}")
+                value = field_value.id
+            else:
+                field_value = self.env[field_id.relation].sudo().search([("name", "=", value)], limit=1)
+                if not field_value:
+                    raise ValidationError(
+                        f"No se encontro un registro en el modelo de {field_id.relation} con el nombre {value}")
+                value = field_value.id
+        return value
+
+
+
+
+
+
+
+
+

+ 21 - 0
custom_import_layout/models/import_layout_rule.py

@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+from odoo.exceptions import ValidationError
+
+
+class ImportLayoutRule(models.Model):
+    _name = "import.layout.rule"
+    _description = "Reglas de importación"
+
+    name = fields.Char(string="Nombre")
+    model_id = fields.Many2one(comodel_name="ir.model", string="Modelo de lineas")
+    main_model_id = fields.Many2one(comodel_name="ir.model", string="Modelo del cabero")
+    column_ids = fields.One2many("import.layout.rule.line", "rule_id", string="Regla de columnas")
+    main_columns_ids = fields.One2many("import.layout.rule.main.line", "rule_id", string="Reglas del cabecero")
+
+    @api.constrains("main_columns_ids")
+    def _check_main_group_by(self):
+        for rec in self:
+            if len(rec.main_columns_ids.filtered(lambda line: line.group_by)) != 1:
+                raise ValidationError("Es necesario indicar solamente un agrupador en las lineas del cabecero.")

+ 27 - 0
custom_import_layout/models/import_layout_rule_line.py

@@ -0,0 +1,27 @@
+from odoo import api, fields, models
+
+class ImportLayoutRuleMainLine(models.Model):
+    _name = 'import.layout.rule.main.line'
+    _description = 'Linea de reglas de importación del cabecero'
+
+    name = fields.Char(string="Nombre")
+    rule_id = fields.Many2one(comodel_name="import.layout.rule", string="Regla")
+    field_id = fields.Many2one(comodel_name="ir.model.fields", string="Campo")
+    sequence = fields.Integer(string="Secuencia")
+    group_by = fields.Boolean(string="Agrupar por")
+
+class ImportLayoutRuleLine(models.Model):
+    _name = 'import.layout.rule.line'
+    _description = 'Linea de reglas de importación'
+
+    name = fields.Char(string="Nombre")
+    rule_id = fields.Many2one(comodel_name="import.layout.rule", string="Regla")
+    field_id = fields.Many2one(comodel_name="ir.model.fields", string="Campo")
+    sequence = fields.Integer(string="Secuencia")
+
+
+
+
+
+
+

+ 5 - 0
custom_import_layout/security/ir.model.access.csv

@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_import_layout_rule,import_layout_rule,model_import_layout_rule,base.group_erp_manager,1,1,1,1
+access_import_layout_rule_line,import_layout_rule_line,model_import_layout_rule_line,base.group_erp_manager,1,1,1,1
+access_import_layout_rule_main_line,import_layout_rule_main_line,model_import_layout_rule_main_line,base.group_erp_manager,1,1,1,1
+access_import_layout,import_layout,model_import_layout,base.group_user,1,1,1,1

+ 51 - 0
custom_import_layout/views/import_layout.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <data>
+
+        <record id="import_layout_view_form" model="ir.ui.view">
+            <field name="name">import_layout_view_form</field>
+            <field name="model">import.layout</field>
+            <field name="arch" type="xml">
+                <form string="Importación de layout">
+                    <header>
+                        <button name="read_excel" string="Importar" type="object" class="btn btn-prumary"/>
+                    </header>
+                    <sheet>
+                        <group>
+                            <group>
+                                <field name="rule_id"/>
+                                <field name="file_name" invisible="1"/>
+                                <field name="file_data" filename="file_name"/>
+                            </group>
+                            <group>
+                                <field name="company_id" readonly="1"/>
+                            </group>
+                        </group>
+                    </sheet>
+                </form>
+            </field>
+        </record>
+
+        <record id="import_layout_sale_action" model="ir.actions.act_window">
+            <field name="name">Importación de layout</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">import.layout</field>
+            <field name="view_mode">form</field>
+            <field name="context">{'sale_import':True}</field>
+        </record>
+
+        <record id="import_layout_purchase_action" model="ir.actions.act_window">
+            <field name="name">Importación de layout</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">import.layout</field>
+            <field name="view_mode">form</field>
+            <field name="context">{'purchase_import':True}</field>
+        </record>
+
+        <menuitem id="import_layout_sale_menu" name="Importación de layout" parent="sale.sale_order_menu"
+                  action="import_layout_sale_action" sequence="200"/>
+
+        <menuitem id="import_layout_purchase_menu" name="Importación de layout" parent="purchase.menu_procurement_management"
+                  action="import_layout_purchase_action" sequence="200"/>
+    </data>
+</odoo>

+ 76 - 0
custom_import_layout/views/import_layout_rule.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <data>
+
+        <record id="import_layout_rule_view_tree" model="ir.ui.view">
+            <field name="name">import_layout_rule_view_tree</field>
+            <field name="model">import.layout.rule</field>
+            <field name="arch" type="xml">
+                <tree string="Reglas de importación">
+                    <field name="name"/>
+                    <field name="model_id"/>
+                </tree>
+            </field>
+        </record>
+
+        <record id="import_layout_rule_view_form" model="ir.ui.view">
+            <field name="name">import_layout_rule_view_form</field>
+            <field name="model">import.layout.rule</field>
+            <field name="arch" type="xml">
+                <form string="Reglas de importación">
+                    <sheet>
+                        <group>
+                            <group>
+                                <field name="name" placeholder="Nombre de plantilla"/>
+                            </group>
+
+                        </group>
+                        <notebook>
+                            <page string="Cabecero">
+                                <group>
+                                    <field name="main_model_id" domain="[('model','in',['sale.order','purchase.order'])]" options="{'no_create':True, 'no_open': True}"/>
+                                </group>
+                                <field name="main_columns_ids">
+                                    <tree editable="bottom">
+                                        <field name="sequence" widget="handle"/>
+                                        <field name="name"/>
+                                        <field name="field_id" domain="[('model_id','=',parent.main_model_id)]"
+                                               options="{'no_create':True, 'no_open': True}"/>
+                                        <field name="group_by"/>
+                                    </tree>
+                                </field>
+                            </page>
+                            <page string="Lineas">
+                                <group>
+                                    <field name="model_id" domain="[('model','in',['sale.order.line','purchase.order.line'])]" options="{'no_create':True, 'no_open': True}"/>
+                                </group>
+                                <field name="column_ids">
+                                    <tree editable="bottom">
+                                        <field name="sequence" widget="handle"/>
+                                        <field name="name"/>
+                                        <field name="field_id" domain="[('model_id','=',parent.model_id)]"
+                                               options="{'no_create':True, 'no_open': True}"/>
+                                    </tree>
+                                </field>
+                            </page>
+                        </notebook>
+
+                    </sheet>
+                </form>
+            </field>
+        </record>
+
+        <record id="import_layout_rule_action" model="ir.actions.act_window">
+            <field name="name">Reglas de importación</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">import.layout.rule</field>
+            <field name="view_mode">tree,form</field>
+        </record>
+
+        <menuitem id="import_layout_rule_categ_menu" name="Importación" parent="base.menu_administration"
+                  sequence="200"/>
+        <menuitem id="import_layout_rule_action_menu" name="Reglas de importación"
+                  parent="import_layout_rule_categ_menu" action="import_layout_rule_action" sequence="200"/>
+
+    </data>
+</odoo>