Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Si ya existe un docstring, puede sugerirse un estilo básico acorde a PEP8, pero **no será un error** si faltan `return`, tipos o parámetros documentados.
5. No proponer cambios puramente estéticos (espacios, comillas simples vs dobles, orden de imports, etc.).
6. Mantener el feedback **muy conciso** en los PRs: priorizar pocos puntos claros, evitar párrafos largos y no repetir el contexto que ya está explicado en la descripción del PR.
7. Sobre traducciones: usar `_()` o `self.env._()` es indistinto; solo marcar si hay mensajes de error o textos no traducidos que deban serlo.

---

Expand All @@ -38,13 +39,7 @@
* Confirmar que todos los archivos usados (vistas, seguridad, datos, reportes, wizards) estén referenciados en el manifest.
* Verificar dependencias declaradas: que no falten módulos requeridos ni se declaren innecesarios.
* **Regla de versión (obligatoria):**
Siempre que el diff incluya **modificaciones en**:

* definición de campos o modelos (`models/*.py`, `wizards/*.py`),
* vistas o datos XML (`views/*.xml`, `data/*.xml`, `report/*.xml`, `wizards/*.xml`),
* seguridad (`security/*.csv`, `security/*.xml`),

**y el `__manifest__.py` no incrementa `version`, sugerir el bump de versión** (por ejemplo, `1.0.0 → 1.0.1`).
Solo sugerir bump de versión si el `__manifest__.py` no incrementa `version` y se modificó la estructura de un modelo, una vista, o algún record .xml (ej. cambios en definición de campos, vistas XML, datos XML, seguridad).
* Solo hacerlo una vez por revisión, aunque haya múltiples archivos afectados.

---
Expand Down Expand Up @@ -288,7 +283,7 @@ def migrate(cr, registry):
| ------------------ | -------------------------------------------------------------------------------------------------------- |
| Modelos | Relaciones válidas; constraints; uso adecuado de `@api.depends`; `super()` correcto |
| Vistas XML | Herencias correctas; campos válidos; adaptación a cambios de versión (p.ej. `<list>` vs `<tree>`) |
| Manifest | **Bump de versión obligatorio** si hay cambios en modelos/vistas/seguridad/datos; archivos referenciados |
| Manifest | **Bump de versión obligatorio** si hay cambios estructurales en modelos/vistas/records .xml; archivos referenciados |
| Seguridad | Accesos mínimos necesarios; reglas revisadas |
| Migraciones | **Si hay cambios estructurales, sugerir script en `migrations/` (pre/post/end)** y describir qué hace |
| Rendimiento / ORM | Evitar loops costosos; no SQL innecesario; aprovechar las optimizaciones del ORM de la versión |
Expand All @@ -298,7 +293,7 @@ def migrate(cr, registry):

## Heurística práctica para el bump de versión (general)

* **SI** el diff toca cualquiera de: `models/`, `views/`, `data/`, `report/`, `security/`, `wizards/`
* **SI** el diff modifica la estructura de un modelo, una vista, o algún record .xml (ej. cambios en definición de campos, vistas XML, datos XML, seguridad)
**Y** `__manifest__.py` no cambia `version` → **Sugerir bump**.
* **SI** hay scripts `migrations/pre_*.py` o `migrations/post_*.py` nuevos → **Sugerir al menos minor bump**.
* **SI** hay cambios que rompen compatibilidad (renombres, cambios de tipo con impacto, limpieza masiva de datos) → **Sugerir minor/major** según impacto.
Expand All @@ -321,12 +316,10 @@ def migrate(cr, registry):

## Resumen operativo para Copilot

1. **Detecta cambios en modelos/vistas/seguridad/datos → exige bump de `version` en `__manifest__.py`.**
1. **Detecta cambios estructurales en modelos, vistas o records .xml → exige bump de `version` en `__manifest__.py` si no está incrementada.**
2. **Si hay cambio estructural (según la lista actualizada) → propone y describe script(s) de migración en `migrations/` (pre/post/end)**, con enfoque idempotente y en lotes.
3. Distingue entre:

* **cuestiones generales** (válidas para cualquier versión),
* y **matices específicos de Odoo 18** (por ejemplo, uso de `<list>`, passkeys, tours y comportamiento del framework).
4. Mantén el feedback **concreto, breve y accionable**.

[^odoo18]: Resumen basado en la documentación oficial de Odoo 18 Release Notes y artículos técnicos que analizan sus mejoras de rendimiento y UX.
4. Mantén el feedback **concreto, breve y accionable**.
3 changes: 2 additions & 1 deletion base_ux/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
##############################################################################
{
"name": "Base UX",
"version": "18.0.3.0.0",
"version": "18.0.4.0.0",
"category": "Base",
"sequence": 14,
"summary": "",
Expand All @@ -35,6 +35,7 @@
"views/ir_actions_act_window_view.xml",
"views/mail_template_view.xml",
"views/res_company_view.xml",
"views/base_partner_merge_view.xml",
],
"demo": [],
"test": [],
Expand Down
14 changes: 14 additions & 0 deletions base_ux/views/base_partner_merge_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="base_partner_merge_automatic_wizard_form_inherit" model="ir.ui.view">
<field name="name">base.partner.merge.automatic.wizard.form.inherit</field>
<field name="model">base.partner.merge.automatic.wizard</field>
<field name="inherit_id" ref="base.base_partner_merge_automatic_wizard_form"/>
<field name="priority">99</field>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_merge']" position="attributes">
<attribute name="confirm">¿Desea confirmar la fusión de contactos?</attribute>
</xpath>
</field>
</record>
</odoo>
31 changes: 17 additions & 14 deletions report_copies/README.rst → export_bg/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,47 @@

.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
:alt: ADHOC SA
:target: https://www.adhoc.com.ar
:target: https://www.adhoc.inc

.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

=============
Report Copies
=============
=================
Export Background
=================

* Add the posibility to print more than 1 copy for the qweb reports.
Automatically exports large datasets (>500 records) in background to avoid timeouts.

Installation
============

To install this module, you need to:

#. Just install
Install the module and its dependency: ``base_bg``

Configuration
=============

To configure this module, you need to:
Optional: Configure the record threshold in **Settings > Technical > System Parameters**:

#. Don't need any configuration
* Key: ``export_bg.record_threshold``
* Default: ``500``

Usage
=====

To use this module, you need to:
1. Go to any list view and select records to export
2. Click **Export** and choose your format (CSV or XLSX)
3. If records exceed the threshold:
- You'll receive a notification: "Export sent to background"
- You'll receive a message with download link when ready

#. Just use it.
For exports under the threshold, it works as normal (instant download).

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: http://runbot.adhoc.com.ar/
:target: https://runbot.dev-adhoc.com/

Bug Tracker
===========
Expand Down Expand Up @@ -67,4 +70,4 @@ Maintainer

This module is maintained by the |company|.

To contribute to this module, please visit https://www.adhoc.com.ar.
To contribute to this module, please visit https://www.adhoc.inc.
1 change: 1 addition & 0 deletions export_bg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
28 changes: 14 additions & 14 deletions report_copies/__manifest__.py → export_bg/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
##############################################################################
#
# Copyright (C) 2020 ADHOC SA (http://www.adhoc.com.ar)
# Copyright (C) 2026 ADHOC SA (http://www.adhoc.com.ar)
# All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -17,25 +17,25 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

{
"name": "Report Copies",
"version": "15.0.1.0.0",
"category": "Web & Reports",
"sequence": 14,
"summary": "",
"name": "Export Background",
"version": "18.0.1.0.0",
"category": "Technical",
"author": "ADHOC SA",
"website": "www.adhoc.com.ar",
"website": "https://www.adhoc.com.ar",
"license": "AGPL-3",
"images": [],
"summary": "Export large datasets in background to avoid timeouts",
"depends": [
"base_bg",
"web",
],
"data": [
"views/report_templates.xml",
"views/ir_actions_views.xml",
],
"installable": False,
"data": [],
"assets": {
"web.assets_backend": [
"export_bg/static/src/views/list_controller.js",
],
},
"installable": True,
"auto_install": False,
"application": False,
}
1 change: 1 addition & 0 deletions export_bg/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import export_bg_mixin
57 changes: 57 additions & 0 deletions export_bg/models/export_bg_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import base64
import io
import json

from markupsafe import Markup
from odoo import _, api, models
from odoo.addons.web.controllers.export import CSVExport
from odoo.tools.misc import xlsxwriter


class IrModel(models.Model):
_name = "ir.model"
_inherit = ["ir.model", "base.bg"]

@api.model
def get_export_threshold(self):
"""Get the threshold for background export without requiring admin permissions."""
return int(self.env["ir.config_parameter"].sudo().get_param("export_bg.record_threshold", "500"))

def _prepare_export_data(self, data):
params = json.loads(data)
Model = self.env[params["model"]].with_context(**params.get("context", {}))
records = Model.browse(params["ids"]) if params.get("ids") else Model.search(params.get("domain", []))
return (
params,
[f["string"] for f in params["fields"]],
records.export_data([f["value"] for f in params["fields"]]).get("datas", []),
)

def web_export_csv(self, data):
params, headers, export_data = self._prepare_export_data(data)
content = CSVExport().from_data(params["fields"], headers, export_data).encode()
return self._save_attachment(params["model"], content, ".csv", "text/csv;charset=utf8")

def web_export_xlsx(self, data):
params, headers, export_data = self._prepare_export_data(data)
buf = io.BytesIO()
wb = xlsxwriter.Workbook(buf, {"in_memory": True})
ws = wb.add_worksheet()
ws.write_row(0, 0, headers)
for i, row in enumerate(export_data, 1):
ws.write_row(i, 0, row)
wb.close()
return self._save_attachment(
params["model"],
buf.getvalue(),
".xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)

def _save_attachment(self, model, content, ext, mime):
att = self.env["ir.attachment"].create(
{"name": f"{model}{ext}", "datas": base64.b64encode(content), "mimetype": mime}
)
return Markup(
f'<p>{_("Your export is ready!")}</p><p><a href="/web/content/{att.id}?download=true" class="btn btn-primary"><i class="fa fa-download"/> {_("Download")} {att.name}</a></p>'
)
43 changes: 43 additions & 0 deletions export_bg/static/src/views/list_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/** @odoo-module **/

import { patch } from "@web/core/utils/patch";
import { ListController } from "@web/views/list/list_controller";

patch(ListController.prototype, {
async downloadExport(fields, import_compat, format) {
const resIds = this.isDomainSelected ? false : await this.getSelectedResIds();
const recordCount = resIds ? resIds.length : (this.model.root.count || 0);

const threshold = await this.model.orm.call(
"ir.model",
"get_export_threshold",
[]
);

if (recordCount > threshold) {
const data = {
model: this.props.resModel,
fields: fields,
ids: resIds,
domain: this.model.root.domain,
import_compat: import_compat,
};

const method = format === "csv" ? "web_export_csv" : "web_export_xlsx";
const actionResult = await this.model.orm.call(
"ir.model",
"bg_enqueue",
[method],
{
data: JSON.stringify(data),
}
);

if (actionResult && actionResult.type === "ir.actions.client") {
this.env.services.action.doAction(actionResult);
}
} else {
await super.downloadExport(...arguments);
}
},
});
6 changes: 0 additions & 6 deletions report_copies/__init__.py

This file was deleted.

36 changes: 0 additions & 36 deletions report_copies/i18n/es.po

This file was deleted.

30 changes: 0 additions & 30 deletions report_copies/i18n/ru.po

This file was deleted.

5 changes: 0 additions & 5 deletions report_copies/models/__init__.py

This file was deleted.

Loading