Update for production backup
This commit is contained in:
@@ -355,3 +355,5 @@ Ejemplo: `HRS-POL-CLUB-M-BLK-L` = Polo Club, Hombre, Negro, Talla L
|
||||
- NUNCA crees scripts temporales de Python (o bash) directamente en la carpeta raíz del proyecto.
|
||||
- Si necesitas crear un script (`.py`, `.sh`, etc.) para una tarea, colócalo SIEMPRE dentro del directorio `scripts/`. Crea este directorio si no existe.
|
||||
|
||||
## Ejecución y Cambios de Código
|
||||
- NUNCA apliques cambios en el código ni ejecutes comandos que modifiquen el entorno sin antes consultar y recibir la autorización explícita ("Adelante", "Ok", "Dale", etc.) del usuario. Debes plantear la solución primero y esperar aprobación.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
'name': 'Hazard Reports & Dashboards',
|
||||
'version': '1.0',
|
||||
'summary': 'KPIs and Pivot reports for Sales and Operations',
|
||||
'description': 'Adds custom Dashboards and Pivot views for sale.order to analyze Retail vs B2B performance.',
|
||||
'category': 'Sales/Sales',
|
||||
'author': 'TESS',
|
||||
'depends': ['sale', 'sale_management'],
|
||||
'data': [
|
||||
'views/sale_order_report_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Pivot View -->
|
||||
<record id="view_sale_order_kpi_pivot" model="ir.ui.view">
|
||||
<field name="name">sale.order.kpi.pivot</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Análisis de Ventas (KPIs)" sample="1">
|
||||
<field name="date_order" type="row" interval="month"/>
|
||||
<field name="partner_id" type="row"/>
|
||||
<field name="amount_total" type="measure"/>
|
||||
<field name="amount_untaxed" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Graph View -->
|
||||
<record id="view_sale_order_kpi_graph" model="ir.ui.view">
|
||||
<field name="name">sale.order.kpi.graph</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Ventas por Canal" type="bar" sample="1">
|
||||
<field name="date_order" interval="month"/>
|
||||
<field name="amount_total" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Search View Extension to add Shopify vs B2B filters -->
|
||||
<record id="view_sale_order_kpi_search" model="ir.ui.view">
|
||||
<field name="name">sale.order.kpi.search</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_sales_order_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='my_sale_orders_filter']" position="before">
|
||||
<filter string="Canal: Retail (Shopify)" name="channel_shopify" domain="[('shopify_order_id', '!=', False)]"/>
|
||||
<filter string="Canal: Custom (B2B)" name="channel_b2b" domain="[('shopify_order_id', '=', False)]"/>
|
||||
<separator/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_hazard_kpi_reports" model="ir.actions.act_window">
|
||||
<field name="name">Análisis y KPIs (B2B / Retail)</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
<field name="view_mode">pivot,graph,tree,form</field>
|
||||
<field name="domain">[('state', 'in', ['sale', 'done'])]</field>
|
||||
<field name="search_view_id" ref="sale.view_sales_order_filter"/>
|
||||
</record>
|
||||
|
||||
<!-- Let's map specific view modes to the action -->
|
||||
<record id="action_hazard_kpi_reports_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="view_id" ref="view_sale_order_kpi_pivot"/>
|
||||
<field name="act_window_id" ref="action_hazard_kpi_reports"/>
|
||||
</record>
|
||||
<record id="action_hazard_kpi_reports_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="view_sale_order_kpi_graph"/>
|
||||
<field name="act_window_id" ref="action_hazard_kpi_reports"/>
|
||||
</record>
|
||||
|
||||
<!-- Menu Item -->
|
||||
<menuitem id="menu_hazard_kpi_reports"
|
||||
name="Análisis KPIs (Retail / B2B)"
|
||||
parent="sale.menu_sale_report"
|
||||
action="action_hazard_kpi_reports"
|
||||
sequence="5"/>
|
||||
</odoo>
|
||||
@@ -23,6 +23,11 @@ class ProductTemplate(models.Model):
|
||||
string='ID de Shopify (Template)',
|
||||
copy=False, index=True, readonly=True,
|
||||
)
|
||||
shopify_compare_at_price = fields.Float(
|
||||
string='Precio de Comparación (Shopify)',
|
||||
copy=False,
|
||||
help="Precio original (tachado) en Shopify. El precio de venta será el precio público de Odoo.",
|
||||
)
|
||||
|
||||
# ── Campos nuevos: control de publicación ─────────────────
|
||||
shopify_published = fields.Boolean(
|
||||
@@ -146,15 +151,21 @@ class ProductTemplate(models.Model):
|
||||
}
|
||||
api_base = f"https://{shop_domain}/admin/api/2024-01"
|
||||
|
||||
# ── Respaldo de Barcodes desde Shopify ──────────────────
|
||||
# Si ya existe en Shopify, consultamos el producto actual para recuperar barcodes.
|
||||
# Esto evita que Odoo borre barcodes en Shopify si localmente estaban vacíos.
|
||||
# ── Consulta preventiva a Shopify (si el producto ya existe) ──────────────
|
||||
# Se hace UNA sola consulta GET al inicio para:
|
||||
# A) Recuperar barcodes de Shopify (evita borrarlos si Odoo los tiene vacíos).
|
||||
# B) Auto-enlazar IDs de variante por SKU ANTES de construir el payload.
|
||||
# Esto es CRÍTICO: si Odoo no tiene el shopify_variant_id de una variante,
|
||||
# la trataría como nueva y mandaría POST a crearla. Shopify rechaza eso
|
||||
# si la opción está vinculada a un metacampo de categoría.
|
||||
if self.shopify_id:
|
||||
try:
|
||||
check_url = f"{api_base}/products/{self.shopify_id}.json"
|
||||
check_resp = requests.get(check_url, headers=headers, timeout=15)
|
||||
if check_resp.status_code == 200:
|
||||
prod_data = check_resp.json().get('product', {})
|
||||
|
||||
# A) Respaldo de barcodes
|
||||
shopify_barcodes = {}
|
||||
for var in prod_data.get('variants', []):
|
||||
v_id = str(var.get('id'))
|
||||
@@ -162,13 +173,62 @@ class ProductTemplate(models.Model):
|
||||
if barcode:
|
||||
shopify_barcodes[v_id] = barcode
|
||||
|
||||
# Actualizar variantes de Odoo con el barcode de Shopify si en Odoo está vacío
|
||||
for v in self.product_variant_ids:
|
||||
s_var_id = v.shopify_variant_id
|
||||
if s_var_id and s_var_id in shopify_barcodes and not v.barcode:
|
||||
v.sudo().write({'barcode': shopify_barcodes[s_var_id]})
|
||||
|
||||
# B) Auto-enlazado preventivo de IDs de variante por SKU
|
||||
# Construimos dos estructuras desde la respuesta de Shopify:
|
||||
# sku_to_ids: SKU → {variant_id, inventory_item_id}
|
||||
# existing_variant_ids: conjunto de IDs activos en Shopify
|
||||
sku_to_ids = {}
|
||||
existing_variant_ids = set()
|
||||
for var in prod_data.get('variants', []):
|
||||
sku = (var.get('sku') or '').strip()
|
||||
v_id_str = str(var.get('id', ''))
|
||||
existing_variant_ids.add(v_id_str)
|
||||
if sku:
|
||||
sku_to_ids[sku] = {
|
||||
'variant_id': v_id_str,
|
||||
'inventory_item_id': str(var.get('inventory_item_id', '')),
|
||||
}
|
||||
|
||||
for v in self.product_variant_ids:
|
||||
sku = (v.default_code or '').strip()
|
||||
|
||||
# C) Limpiar IDs obsoletos: si Odoo tiene un variant_id que ya no
|
||||
# existe en Shopify (la variante fue borrada allá), limpiamos el
|
||||
# campo para que Odoo la trate como nueva y la intente crear.
|
||||
if v.shopify_variant_id and v.shopify_variant_id not in existing_variant_ids:
|
||||
_logger.info(
|
||||
"[Shopify Push] Variante obsoleta detectada: SKU '%s' tenía variant_id=%s "
|
||||
"que ya no existe en Shopify. Se limpia para intentar recrearla.",
|
||||
sku, v.shopify_variant_id
|
||||
)
|
||||
v.sudo().write({
|
||||
'shopify_variant_id': False,
|
||||
'shopify_inventory_item_id': False,
|
||||
})
|
||||
|
||||
# B) Para variantes sin ID de Shopify, buscamos enlace por SKU
|
||||
if not v.shopify_variant_id and sku:
|
||||
if sku in sku_to_ids:
|
||||
ids_data = sku_to_ids[sku]
|
||||
write_vals = {}
|
||||
if ids_data.get('variant_id'):
|
||||
write_vals['shopify_variant_id'] = ids_data['variant_id']
|
||||
if ids_data.get('inventory_item_id'):
|
||||
write_vals['shopify_inventory_item_id'] = ids_data['inventory_item_id']
|
||||
if write_vals:
|
||||
v.sudo().write(write_vals)
|
||||
_logger.info(
|
||||
"[Shopify Push] Auto-enlazado preventivo: SKU '%s' → variant_id=%s",
|
||||
sku, ids_data['variant_id']
|
||||
)
|
||||
|
||||
except Exception as e_bc:
|
||||
_logger.warning("[Shopify Push] No se pudieron resguardar los barcodes de Shopify: %s", str(e_bc))
|
||||
_logger.warning("[Shopify Push] Error en consulta preventiva a Shopify: %s", str(e_bc))
|
||||
|
||||
# ── Construir payload ─────────────────────────────────
|
||||
is_create = not bool(self.shopify_id)
|
||||
@@ -196,7 +256,7 @@ class ProductTemplate(models.Model):
|
||||
_logger.warning("[Shopify Push] Error actualizando variante %s: %s", v_id, v_resp.text[:200])
|
||||
warning_messages.append(f"Variante {v_id} no actualizada.")
|
||||
else:
|
||||
# Si no tiene ID, es una nueva variante, la agregamos al producto
|
||||
# Si no tiene ID, es una variante realmente nueva → intentar crearla
|
||||
v_url = f"{api_base}/products/{self.shopify_id}/variants.json"
|
||||
v_resp = requests.post(v_url, headers=headers, json={'variant': var_data}, timeout=15)
|
||||
if v_resp.status_code not in (200, 201):
|
||||
@@ -205,7 +265,21 @@ class ProductTemplate(models.Model):
|
||||
except Exception:
|
||||
actual_error = v_resp.text[:200]
|
||||
_logger.warning("[Shopify Push] Error creando variante: %s", actual_error)
|
||||
warning_messages.append(f"No se pudo crear la variante (SKU: {var_data.get('sku')}). Shopify respondió: {actual_error}")
|
||||
|
||||
# Detectar el error específico de metacampos vinculados y dar un tip claro al usuario
|
||||
error_str = str(actual_error)
|
||||
if 'linked to a metafield' in error_str or 'Cannot set name' in error_str:
|
||||
tip_msg = (
|
||||
f"No se pudo crear la variante (SKU: {var_data.get('sku')}) porque "
|
||||
f"la opción de talla/color en Shopify está vinculada a un Metacampo.\n"
|
||||
f"💡 TIP: En Shopify → Producto → Variantes → clic en 'Talla' (o 'Color') "
|
||||
f"→ selecciona 'Desconectar'. Luego intenta de nuevo desde Odoo."
|
||||
)
|
||||
warning_messages.append(tip_msg)
|
||||
else:
|
||||
warning_messages.append(
|
||||
f"No se pudo crear la variante (SKU: {var_data.get('sku')}). Shopify respondió: {actual_error}"
|
||||
)
|
||||
else:
|
||||
has_new_variants = True
|
||||
|
||||
@@ -339,6 +413,9 @@ class ProductTemplate(models.Model):
|
||||
'inventory_management': 'shopify',
|
||||
'inventory_policy': 'deny',
|
||||
}
|
||||
if self.shopify_compare_at_price > 0:
|
||||
v_payload['compare_at_price'] = str(self.shopify_compare_at_price)
|
||||
|
||||
if v.barcode: # Solo enviar barcode si Odoo tiene uno
|
||||
v_payload['barcode'] = v.barcode
|
||||
if v.shopify_variant_id:
|
||||
@@ -372,6 +449,9 @@ class ProductTemplate(models.Model):
|
||||
'inventory_management': 'shopify',
|
||||
'inventory_policy': 'deny',
|
||||
}
|
||||
if self.shopify_compare_at_price > 0:
|
||||
variant_data['compare_at_price'] = str(self.shopify_compare_at_price)
|
||||
|
||||
if v.barcode: # Solo enviar barcode si Odoo tiene uno
|
||||
variant_data['barcode'] = v.barcode
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
string="ID Producto Shopify"
|
||||
readonly="1"
|
||||
attrs="{'invisible': [('shopify_id', '=', False)]}"/>
|
||||
<field name="shopify_compare_at_price"
|
||||
string="Precio de Comparación"
|
||||
widget="monetary"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Manual Operativo Definitivo: Flujo Custom y Maquila (B2B)
|
||||
|
||||
Este manual detalla paso a paso cómo debe operar el personal de la empresa (Ventas, Compras y Almacén) para vender productos a la medida maquilados por terceros, garantizando que **nada de esto interfiera con Shopify**.
|
||||
|
||||
---
|
||||
|
||||
## FASE 0: El Blindaje contra Shopify (Para tu tranquilidad)
|
||||
**¿Cómo evitamos que se mezcle con Shopify?**
|
||||
Implementamos un "candado" a nivel de código en tu Odoo. Creamos dos categorías de inventario intocables por Shopify:
|
||||
1. `Insumos / Materia Prima`
|
||||
2. `Producto Final Custom`
|
||||
|
||||
Si alguien por error intenta presionar el botón "Publicar en Shopify" en cualquier producto de estas categorías, **Odoo lanzará un error bloqueando la acción**. La plataforma de Retail (Shopify) está tecnológicamente aislada de este flujo B2B.
|
||||
|
||||
---
|
||||
|
||||
## FASE 1: Creación de Insumos y Productos (Responsable: Compras / Almacén)
|
||||
Antes de vender, hay que decirle a Odoo qué materiales tenemos, cómo se enviarán a la fábrica y qué vamos a vender.
|
||||
|
||||
### 1.1 Crear el Insumo (Ej. La Tela o Botones)
|
||||
1. Ir al módulo de **Inventario > Productos > Crear**.
|
||||
2. **Nombre:** `Tela Azul Premium` o `Botones`
|
||||
3. **Tipo de Producto:** `Almacenable` (para poder contar las existencias).
|
||||
4. **Categoría de Producto:** `Insumos / Materia Prima` *(¡CRÍTICO para el blindaje!)*.
|
||||
5. **Configuración de Rutas y Proveedores (¡Muy Importante!):**
|
||||
Aquí debes elegir cómo llegará este insumo a la fábrica maquiladora. Tienes dos escenarios:
|
||||
|
||||
* **Escenario A: El insumo sale de nuestro almacén (Flujo Normal)**
|
||||
* Ve a la pestaña **Inventario** y en la sección de Rutas marca: **"Reabastecer al subcontratista al ordenar"**.
|
||||
* *(Esto le dirá a Odoo que nos pida sacar la mercancía de nuestra bodega).*
|
||||
|
||||
* **Escenario B: El proveedor de tela la envía directo a la fábrica (Triangulación / Dropshipping)**
|
||||
* Ve a la pestaña **Inventario** y en Rutas marca: **"Enviar al subcontratista al ordenar"** (Dropship Subcontractor).
|
||||
* Ve a la pestaña **Compras** y agrega al proveedor que te vende esta tela.
|
||||
* *(Esto le dirá a Odoo que genere una compra automática a ese proveedor enviando la tela directo a la maquila).*
|
||||
|
||||
6. Guardar.
|
||||
|
||||
### 1.2 Crear el Producto Final (Ej. La Camisa)
|
||||
1. Ir a **Inventario > Productos > Crear**.
|
||||
2. **Nombre:** `Camisa Custom Club de Golf`
|
||||
3. **Tipo de Producto:** `Almacenable`.
|
||||
4. **Categoría de Producto:** `Producto Final Custom` *(¡CRÍTICO!)*.
|
||||
5. Ir a la pestaña **Compra**: Haz clic en *Agregar una línea* y pon el nombre de la fábrica que la va a maquilar (ej. *Fábrica China*) y el costo que te cobran por mano de obra.
|
||||
6. Guardar.
|
||||
|
||||
### 1.3 Unir el Insumo y la Camisa (La Lista de Materiales / BoM)
|
||||
Odoo necesita saber qué se usa para fabricar la camisa.
|
||||
1. Dentro de la pantalla de la `Camisa Custom`, haz clic en el botón superior que dice **Lista de Materiales (BoM)** y dale a **Crear**.
|
||||
2. **Tipo de BoM:** Selecciona **Subcontratación** *(Subcontracting)*.
|
||||
3. **Subcontratista:** Selecciona a la *Fábrica China*.
|
||||
4. **Componentes:** Abajo, agrega la `Tela Azul Premium` y los `Botones`, indicando la cantidad necesaria para 1 sola camisa.
|
||||
5. Guardar.
|
||||
|
||||
---
|
||||
|
||||
## FASE 2: La Venta (Responsable: Ventas B2B)
|
||||
El cliente corporativo acepta trabajar con nosotros.
|
||||
|
||||
> [!TIP]
|
||||
> **Precios Tabulados (Descuentos por Volumen)**
|
||||
> Si el precio al cliente cambia a partir de cierta cantidad (ej. si compran más de 50 camisas el precio baja), Ventas puede configurar esto automáticamente:
|
||||
> 1. Ir a **Ventas > Productos > Listas de Precios**.
|
||||
> 2. Crear una regla para la `Camisa Custom`, definiendo la "Cantidad Mínima" (ej. 50) y el nuevo precio. Al hacer la cotización, Odoo ajustará el precio solo si se supera esa cantidad.
|
||||
|
||||
1. Ir al módulo de **Ventas > Crear Cotización**.
|
||||
2. **Cliente:** `Club de Golf`
|
||||
3. Agregar el producto `Camisa Custom Club de Golf` y poner la cantidad (ej. 50 camisas).
|
||||
4. Hacer clic en **Confirmar**.
|
||||
> **Resultado:** Se genera la orden de salida del almacén (`ALMA/OUT`), pero se queda en estado **"En Espera"** porque aún no existen las camisas físicas.
|
||||
|
||||
---
|
||||
|
||||
## FASE 3: Mandar a Maquilar (Responsable: Compras)
|
||||
Ventas ya vendió, ahora Compras debe pedirle a la fábrica que trabaje.
|
||||
1. Ir al módulo de **Compras > Crear**.
|
||||
2. **Proveedor:** Seleccionar `Fábrica China`.
|
||||
3. **Producto:** Agregar `Camisa Custom Club de Golf` (50 camisas).
|
||||
4. Hacer clic en **Confirmar Orden**.
|
||||
> **Nota sobre Anticipos:** Si la fábrica pide un adelanto, en esta misma pantalla de Compras puedes darle a *Crear Factura* (Factura de Anticipo) para que Finanzas pague.
|
||||
|
||||
---
|
||||
|
||||
## FASE 4: Abastecer a la Fábrica de Insumos
|
||||
Al confirmar el Paso 3, Odoo automáticamente prepara los materiales para la fábrica, dependiendo del escenario que configuramos en la Fase 1:
|
||||
|
||||
* **Si usamos el Escenario A (Salió de nuestro almacén):**
|
||||
1. El responsable va a **Inventario**. Aparecerá una tarjeta llamada **Resuministro de Subcontratista**.
|
||||
2. Al entrar, Almacén verá una orden pidiendo enviar los botones y la tela.
|
||||
3. El bodeguero se los da a paquetería y en Odoo le da clic a **Validar**.
|
||||
|
||||
* **Si usamos el Escenario B (Triangulación directa del proveedor a la fábrica):**
|
||||
1. Odoo creó una **segunda Orden de Compra en estado Borrador** mágicamente.
|
||||
2. El de Compras va al módulo de **Compras** y verá esta nueva orden a nombre del proveedor de telas. Las cantidades estarán calculadas automáticamente (ej. 50 camisas x 1.5m = 75m) y la *Dirección de Entrega* será la Fábrica China.
|
||||
3. El de Compras solo entra y le da clic a **Confirmar**. ¡Listo!
|
||||
|
||||
---
|
||||
|
||||
## FASE 5: Recepción de Camisas Terminadas (Responsable: Almacén)
|
||||
Llegan las camisas listas.
|
||||
1. Ir a **Inventario > Recepciones (`ALMA/IN`)**.
|
||||
2. Abrir la recepción vinculada a la orden de la Fábrica China.
|
||||
3. Contar las camisas, poner la cantidad recibida y dar clic en **Validar**.
|
||||
> **El Costeo Automático:** Odoo acaba de consumir la tela y los botones, y le sumó el costo de la mano de obra. Todo está asignado al costo de la camisa terminada.
|
||||
|
||||
> [!NOTE]
|
||||
> **Añadir Costos Extras (Costos en Destino / Landed Costs)**
|
||||
> ¿Qué pasa si pagaste un flete de última hora, aduanas, o un planchado externo y quieres que eso sume al valor de las camisas?
|
||||
> 1. Ve a **Inventario > Operaciones > Costos en Destino**.
|
||||
> 2. Crea uno nuevo, ingresa el monto de la factura del flete y selecciona el movimiento de Recepción de las camisas (el que acabas de validar).
|
||||
> 3. Al confirmar, Odoo tomará ese gasto extra y lo repartirá equitativamente entre las 50 camisas, aumentando su valor contable.
|
||||
|
||||
---
|
||||
|
||||
## FASE 6: Entrega al Cliente Final (Responsable: Almacén / Finanzas)
|
||||
Como ya tenemos camisas, la orden de Ventas se desbloquea.
|
||||
1. Ir a **Inventario > Órdenes de Entrega (`ALMA/OUT`)**.
|
||||
2. La orden del *Club de Golf* (Fase 2) que estaba "En Espera", ahora dirá **"Listo"**.
|
||||
3. El empacador saca las camisas para el cliente y le da clic a **Validar**.
|
||||
4. Finalmente, Finanzas entra a la orden de Venta original y da clic en **Crear Factura** para el cliente. ¡Fin del proceso!
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
import xmlrpc.client
|
||||
|
||||
ODOO_URL = 'http://localhost:16001'
|
||||
ODOO_DB = 'hazard_new'
|
||||
ODOO_USER = 'admin'
|
||||
ODOO_PASS = 'admin'
|
||||
|
||||
common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
|
||||
uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_PASS, {})
|
||||
models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')
|
||||
|
||||
print("Actualizando lista de modulos...")
|
||||
models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'ir.module.module', 'update_list', [])
|
||||
|
||||
module_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'ir.module.module', 'search', [[('name', '=', 'hazard_reports')]])
|
||||
if module_ids:
|
||||
print(f"Instalando modulo hazard_reports (ID: {module_ids[0]})...")
|
||||
models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'ir.module.module', 'button_immediate_install', [module_ids])
|
||||
print("Instalacion exitosa.")
|
||||
else:
|
||||
print("Modulo no encontrado.")
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user