Skip to content

[ADD] base_company_dependant: widget UX multicompañía para campos com…#379

Merged
jjscarafia merged 8 commits intoingadhoc:19.0from
adhoc-dev:19.0-t-62336-jjs
Mar 17, 2026
Merged

[ADD] base_company_dependant: widget UX multicompañía para campos com…#379
jjscarafia merged 8 commits intoingadhoc:19.0from
adhoc-dev:19.0-t-62336-jjs

Conversation

@jjscarafia
Copy link
Member

…pany_dependent

En Odoo 18/19 los campos company_dependent dejaron de usar ir.property y ahora se almacenan directamente en la tabla del modelo como una columna JSONB (ej. {"1": 45, "2": false}). El ORM resuelve el valor para la compañía activa antes de enviarlo al cliente, pero esto produce tres pain points:

  1. El usuario no sabe si el valor que ve es "específico" (clave explícita en el JSON) o el "fallback global" (clave ausente, resuelve por ir.default).
  2. Modificar el valor puede afectar todas las compañías sin que el usuario lo sepa (estaba tocando el default).
  3. Para revisar el valor de cada compañía hay que hacer context-switching en el menú superior.

Nuevo módulo base_company_dependant que replica el paradigma del "Asistente de Traducciones" (icono fa-globe) pero para valores multicompañía, usando un icono fa-building-o.


models/ir_ui_view.pyBase(_inherit="base")

  • Sobrescribe _get_view_field_attributes() para añadir "company_dependent" a la lista de atributos que el ORM serializa al cliente al cargar una vista.
  • Sin este fix el frontend nunca recibe el metadato y el widget no se activa.
  • Sigue el mismo patrón que html_editor usa para exponer sanitize.

models/base_company_dependant.pymodels.AbstractModel base.company.dependant

  • get_company_dependent_meta(res_model, res_id): · Una sola query SQL para TODOS los campos company_dependent del modelo (evita N+1 al cargar el formulario con múltiples campos). · Devuelve {field_name: is_specific} donde is_specific=True significa que el company_id activo tiene clave explícita en el JSON.
  • get_company_dependent_values(res_model, res_id, field_name): · Lee la columna JSONB cruda via raw SQL (psycopg2.sql.Identifier). · Resuelve el fallback consultando ir.default. · Devuelve por cada compañía accesible (env.companies): {company_id, company_name, is_specific, value_id, display_value}. · Soporta tipos many2one, float e integer.
  • set_company_dependent_values(res_model, res_id, field_name, values_dict): · Recibe {str(company_id): value | false | "RESET"}. · false → guarda la clave con valor false (vacío explícito, is_specific=True). · "RESET" → hace pop() de la clave (restaura al fallback global). · Escribe el JSON crudo y llama a invalidate_recordset para limpiar caché ORM. · Todos los identificadores SQL van por psycopg2.sql.Identifier (no interpolación).

static/src/company_dependent_service.js — Servicio company_dependent

  • Caché por resModel:resId: la primera llamada dispara la RPC, las siguientes comparten la misma Promise en vuelo (batching automático).
  • Una vez resuelta, cachea el objeto plano para lecturas síncronas (getMetaSync).
  • invalidate(resModel, resId) para forzar re-fetch tras guardar en el diálogo.

static/src/many2one_patch.js — Patch de Many2OneField

  • No modifica el registro en el registry; usa patch() sobre el prototipo.
  • isCompanyDependent getter: props.record.fields[name].company_dependent === true.
  • En setup(): llama a useService("company_dependent") y useState siempre (sin condicional, respetando el orden de hooks de OWL). Solo registra onWillStart si isCompanyDependent es verdadero.
  • _loadCDMeta(): no invalida la caché (para aprovechar el batching cuando hay múltiples campos company_dependent en el mismo formulario). La invalidación la hace el diálogo antes de llamar al callback onSaved.
  • Reemplaza Many2OneField.template por base_company_dependant.Many2OneField e inyecta CompanyDependentButton en Many2OneField.components.

static/src/company_dependent_button.jsCompanyDependentButton

  • Renderiza el icono fa-building-o.
  • Color dinámico: text-primary (específico), text-muted (fallback), text-secondary opacity-50 (cargando/null).
  • Tooltip descriptivo según estado.
  • Al hacer clic: guarda el registro (record.save()) antes de abrir el diálogo, igual que hace TranslationButton.

static/src/company_dependent_dialog.jsCompanyDependentDialog

  • Carga datos via get_company_dependent_values en onWillStart.
  • _getEffectiveRow(row): combina el estado original con los cambios pendientes (local state) para renderizado optimista sin necesidad de re-fetch.
  • getAutoCompleteSources(row): genera sources para AutoComplete con onSelect dentro de cada opción (API correcta de AutoComplete; el callback NO va como prop del componente).
  • onAutoCompleteChange(row, {inputValue, isOptionSelected}): detecta vaciado real (usuario borró texto y salió sin seleccionar) vs. selección normal.
  • onResetRow(row): marca { is_reset: true } → el guardado enviará "RESET".
  • onSave(): construye el dict para set_company_dependent_values, invalida la caché del servicio, y llama a onSaved (que recarga el record y re-fetchea el meta para actualizar el color del icono).
  • Getters para todos los strings con caracteres especiales (_t()) para evitar errores del tokenizador OWL con acentos/eñes en expresiones de template.

static/src/templates.xml

  • base_company_dependant.Many2OneField: wrapper flex con clase dinámica o_cd_fallback (activa CSS muted) + <CompanyDependentButton> condicional.
  • base_company_dependant.CompanyDependentButton: botón con fa-building-o, color según isSpecific.
  • base_company_dependant.CompanyDependentDialog: tabla responsive con columnas Compañía / Valor (AutoComplete) / Estado (badge) / Reset (botón).
  • Todos los operadores lógicos en expresiones: && y || (JS), no and/or (Python). Strings en props de componentes con comillas simples internas. Arrow functions con bloques if extraídas a métodos del componente.

static/src/company_dependent.css

  • .o_cd_fallback aplica color: var(--bs-secondary-color) + font-style: italic al input y a los links readonly del campo.
  • .o_cd_btn alinea el icono inline sin romper el layout flex del formulario.

  1. company_dependent no llegaba al cliente → fix en _get_view_field_attributes.
  2. Tokenizer OWL: strings con acentos/espacios en props de componentes → getters JS.
  3. Operadores and/or en templates OWL → reemplazados por &&/||.
  4. Arrow function con bloque if en template → extraída a método del componente.
  5. class="o_input w-100" en prop de componente evaluado como JS → 'o_input w-100'.
  6. Prop onSelect en <AutoComplete> no existe → movido a option.onSelect().
  7. Batching roto al invalidar caché en onWillStart → invalidar solo desde diálogo.

…pany_dependent

En Odoo 18/19 los campos `company_dependent` dejaron de usar `ir.property` y
ahora se almacenan directamente en la tabla del modelo como una columna JSONB
(ej. `{"1": 45, "2": false}`). El ORM resuelve el valor para la compañía activa
antes de enviarlo al cliente, pero esto produce tres pain points:

  1. El usuario no sabe si el valor que ve es "específico" (clave explícita en
     el JSON) o el "fallback global" (clave ausente, resuelve por ir.default).
  2. Modificar el valor puede afectar todas las compañías sin que el usuario
     lo sepa (estaba tocando el default).
  3. Para revisar el valor de cada compañía hay que hacer context-switching en
     el menú superior.

Nuevo módulo `base_company_dependant` que replica el paradigma del "Asistente
de Traducciones" (icono `fa-globe`) pero para valores multicompañía, usando un
icono `fa-building-o`.

---

**`models/ir_ui_view.py`** — `Base(_inherit="base")`
  - Sobrescribe `_get_view_field_attributes()` para añadir `"company_dependent"`
    a la lista de atributos que el ORM serializa al cliente al cargar una vista.
  - Sin este fix el frontend nunca recibe el metadato y el widget no se activa.
  - Sigue el mismo patrón que `html_editor` usa para exponer `sanitize`.

**`models/base_company_dependant.py`** — `models.AbstractModel` `base.company.dependant`
  - `get_company_dependent_meta(res_model, res_id)`:
      · Una sola query SQL para TODOS los campos `company_dependent` del modelo
        (evita N+1 al cargar el formulario con múltiples campos).
      · Devuelve `{field_name: is_specific}` donde `is_specific=True` significa
        que el `company_id` activo tiene clave explícita en el JSON.
  - `get_company_dependent_values(res_model, res_id, field_name)`:
      · Lee la columna JSONB cruda via raw SQL (`psycopg2.sql.Identifier`).
      · Resuelve el fallback consultando `ir.default`.
      · Devuelve por cada compañía accesible (`env.companies`):
        `{company_id, company_name, is_specific, value_id, display_value}`.
      · Soporta tipos `many2one`, `float` e `integer`.
  - `set_company_dependent_values(res_model, res_id, field_name, values_dict)`:
      · Recibe `{str(company_id): value | false | "RESET"}`.
      · `false` → guarda la clave con valor `false` (vacío explícito, is_specific=True).
      · `"RESET"` → hace `pop()` de la clave (restaura al fallback global).
      · Escribe el JSON crudo y llama a `invalidate_recordset` para limpiar caché ORM.
      · Todos los identificadores SQL van por `psycopg2.sql.Identifier` (no interpolación).

---

**`static/src/company_dependent_service.js`** — Servicio `company_dependent`
  - Caché por `resModel:resId`: la primera llamada dispara la RPC, las siguientes
    comparten la misma Promise en vuelo (batching automático).
  - Una vez resuelta, cachea el objeto plano para lecturas síncronas (`getMetaSync`).
  - `invalidate(resModel, resId)` para forzar re-fetch tras guardar en el diálogo.

**`static/src/many2one_patch.js`** — Patch de `Many2OneField`
  - No modifica el registro en el registry; usa `patch()` sobre el prototipo.
  - `isCompanyDependent` getter: `props.record.fields[name].company_dependent === true`.
  - En `setup()`: llama a `useService("company_dependent")` y `useState` siempre
    (sin condicional, respetando el orden de hooks de OWL). Solo registra
    `onWillStart` si `isCompanyDependent` es verdadero.
  - `_loadCDMeta()`: no invalida la caché (para aprovechar el batching cuando
    hay múltiples campos company_dependent en el mismo formulario). La invalidación
    la hace el diálogo antes de llamar al callback `onSaved`.
  - Reemplaza `Many2OneField.template` por `base_company_dependant.Many2OneField`
    e inyecta `CompanyDependentButton` en `Many2OneField.components`.

**`static/src/company_dependent_button.js`** — `CompanyDependentButton`
  - Renderiza el icono `fa-building-o`.
  - Color dinámico: `text-primary` (específico), `text-muted` (fallback),
    `text-secondary opacity-50` (cargando/null).
  - Tooltip descriptivo según estado.
  - Al hacer clic: guarda el registro (`record.save()`) antes de abrir el diálogo,
    igual que hace `TranslationButton`.

**`static/src/company_dependent_dialog.js`** — `CompanyDependentDialog`
  - Carga datos via `get_company_dependent_values` en `onWillStart`.
  - `_getEffectiveRow(row)`: combina el estado original con los cambios pendientes
    (local state) para renderizado optimista sin necesidad de re-fetch.
  - `getAutoCompleteSources(row)`: genera sources para `AutoComplete` con `onSelect`
    **dentro de cada opción** (API correcta de AutoComplete; el callback NO va
    como prop del componente).
  - `onAutoCompleteChange(row, {inputValue, isOptionSelected})`: detecta vaciado
    real (usuario borró texto y salió sin seleccionar) vs. selección normal.
  - `onResetRow(row)`: marca `{ is_reset: true }` → el guardado enviará `"RESET"`.
  - `onSave()`: construye el dict para `set_company_dependent_values`, invalida
    la caché del servicio, y llama a `onSaved` (que recarga el record y re-fetchea
    el meta para actualizar el color del icono).
  - Getters para todos los strings con caracteres especiales (`_t()`) para evitar
    errores del tokenizador OWL con acentos/eñes en expresiones de template.

**`static/src/templates.xml`**
  - `base_company_dependant.Many2OneField`: wrapper flex con clase dinámica
    `o_cd_fallback` (activa CSS muted) + `<CompanyDependentButton>` condicional.
  - `base_company_dependant.CompanyDependentButton`: botón con `fa-building-o`,
    color según `isSpecific`.
  - `base_company_dependant.CompanyDependentDialog`: tabla responsive con columnas
    Compañía / Valor (AutoComplete) / Estado (badge) / Reset (botón).
  - Todos los operadores lógicos en expresiones: `&&` y `||` (JS), no `and`/`or`
    (Python). Strings en props de componentes con comillas simples internas.
    Arrow functions con bloques `if` extraídas a métodos del componente.

**`static/src/company_dependent.css`**
  - `.o_cd_fallback` aplica `color: var(--bs-secondary-color)` + `font-style: italic`
    al input y a los links readonly del campo.
  - `.o_cd_btn` alinea el icono inline sin romper el layout flex del formulario.

---

1. `company_dependent` no llegaba al cliente → fix en `_get_view_field_attributes`.
2. Tokenizer OWL: strings con acentos/espacios en props de componentes → getters JS.
3. Operadores `and`/`or` en templates OWL → reemplazados por `&&`/`||`.
4. Arrow function con bloque `if` en template → extraída a método del componente.
5. `class="o_input w-100"` en prop de componente evaluado como JS → `'o_input w-100'`.
6. Prop `onSelect` en `<AutoComplete>` no existe → movido a `option.onSelect()`.
7. Batching roto al invalidar caché en `onWillStart` → invalidar solo desde diálogo.
Copilot AI review requested due to automatic review settings March 10, 2026 14:00
@roboadhoc
Copy link
Contributor

Pull request status dashboard

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR incorpora un nuevo módulo base_company_dependant para mejorar la UX de campos company_dependent en Odoo 19, agregando un indicador visual y un diálogo de edición multicompañía (estilo “Asistente de Traducciones”) para gestionar valores específicos vs. fallback sin cambiar de compañía activa.

Changes:

  • Añade un widget/patch en frontend (Many2One) con botón e indicador de estado “específico vs. fallback” y un diálogo multicompañía para editar/resetear valores.
  • Implementa un servicio JS con caché/batching para cargar metadata is_specific por registro.
  • Agrega un backend RPC (base.company.dependant) que lee/escribe directamente la columna JSONB de campos company_dependent, y un hook en ir.ui.view para exponer el atributo company_dependent al cliente.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
base_company_dependant/static/src/templates.xml Templates OWL del Many2One extendido, botón e interfaz del diálogo.
base_company_dependant/static/src/many2one_patch.js Patch de Many2OneField para cargar meta y renderizar el botón/estilo.
base_company_dependant/static/src/company_dependent_service.js Servicio JS con caché y batching de RPC por registro.
base_company_dependant/static/src/company_dependent_dialog.js Lógica del diálogo: carga, autocomplete, cambios locales y guardado.
base_company_dependant/static/src/company_dependent_button.js Botón que abre el diálogo y fuerza record.save() previo.
base_company_dependant/static/src/company_dependent.css Estilos para estado fallback, botón e interfaz del diálogo.
base_company_dependant/models/ir_ui_view.py Expone company_dependent en atributos serializados de campos al cliente.
base_company_dependant/models/base_company_dependant.py API backend para meta/lectura/escritura de JSONB company_dependent.
base_company_dependant/models/init.py Exporta los modelos del módulo.
base_company_dependant/manifest.py Manifest y carga de assets backend.
base_company_dependant/init.py Inicialización del módulo.
base_company_dependant/README.rst Documentación funcional y técnica del módulo.

Comment on lines +72 to +82
def _resolve_m2o_display(self, comodel_name, value_id):
"""Devuelve el display_name de un registro Many2one, o None si no existe."""
if not value_id:
return None
try:
record = self.env[comodel_name].sudo().browse(int(value_id))
if record.exists():
return record.display_name
except Exception:
pass
return None
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_resolve_m2o_display usa sudo() y atrapa cualquier excepción sin registrar nada. Esto puede filtrar display_name de registros a los que el usuario no tiene acceso, y además oculta errores reales. Sería mejor evitar sudo(), y en todo caso capturar AccessError de forma explícita (devolviendo None) y loguear el resto; de paso se elimina el _logger actualmente sin uso (ruff/pyflakes lo va a marcar).

Copilot uses AI. Check for mistakes.
Comment on lines +105 to +109
field = model_obj._fields.get(field_name)

if not field or not field.company_dependent:
raise ValueError(f"El campo '{field_name}' en '{res_model}' no es company_dependent.")

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aquí se lanza ValueError con un f-string no traducible. Como esto se invoca vía RPC desde el backend, suele ser más útil levantar UserError/ValidationError con _() para que el mensaje sea amigable y traducible, y para evitar trazas innecesarias en cliente.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +68
get labelSpecific() {
return _t("Especifico");
}

get labelDefault() {
return _t("Por Defecto");
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Textos en UI sin tildes: "Especifico" → "Específico" y (si se mantiene el estilo) conviene consistencia con mayúsculas en "Por defecto". Al estar en _t(), se mostrarán tal cual al usuario.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +72
get loadingText() {
return _t("Cargando valores por compania...");
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Texto de carga con faltas: "compania" debería ser "compañía" (y, si aplica, tildes en "Cargando valores por compañía...").

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +81
get footerNoteHtml() {
// Devuelve partes separadas para el template (evita innerHTML crudo)
return {
vaciar: _t("Vaciar"),
vaciarDesc: _t("guarda un valor vacio explicito (badge Especifico)."),
reset: _t("Reset"),
resetDesc: _t("elimina la clave del JSON y restaura el valor por defecto global."),
};
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En el pie se usan textos sin tildes: "vacio", "explicito" deberían ser "vacío", "explícito". Al estar en _t(), conviene corregirlos para que la UI se vea profesional.

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +86
Fase 2 (pendiente)
==================

* Soporte para campos ``Float`` e ``Integer``.
* Botón «Copiar a todas las compañías hijas».

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El README marca Float/Integer como "Fase 2 (pendiente)", pero en este PR ya hay soporte en el diálogo/template y en backend para float/integer. Conviene actualizar esta sección para evitar documentación desfasada (o ajustar el código si realmente no se quería incluir aún).

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +114
self.env["ir.model.access"].check(res_model, "read")
model_obj = self.env[res_model]
field = model_obj._fields.get(field_name)

if not field or not field.company_dependent:
raise ValueError(f"El campo '{field_name}' en '{res_model}' no es company_dependent.")

raw_json = self._get_raw_json(model_obj._table, field_name, res_id)

# Fallback global: ir.default
fallback_value = self.env["ir.default"]._get(res_model, field_name)

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Estos métodos hacen SELECT/UPDATE directo sobre la tabla y solo validan ACLs del modelo (ir.model.access), pero no aplican reglas de registro (record rules). Así, un usuario podría leer valores de un res_id al que no tiene acceso o incluso inferir datos saltándose el ORM. Antes de leer por SQL, conviene hacer browse(res_id).exists() y forzar check_access_rule('read') (y/o una lectura ORM mínima) para asegurar que el registro es accesible.

Copilot uses AI. Check for mistakes.
Comment on lines +232 to +247
self.env["ir.model.access"].check(res_model, "read")
model_obj = self.env[res_model]
company_key = str(self.env.company.id)

cd_fields = [name for name, f in model_obj._fields.items() if f.company_dependent and f.store]
if not cd_fields:
return {}

# Una sola SELECT para todos los campos company_dependent
self.env.cr.execute(
sql.SQL("SELECT {cols} FROM {table} WHERE id = %s").format(
cols=sql.SQL(", ").join(sql.Identifier(f) for f in cd_fields),
table=sql.Identifier(model_obj._table),
),
(res_id,),
)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El meta se obtiene por SQL y puede revelar si existen claves específicas (y potencialmente valores en otras partes) para registros que el usuario no debería ver, porque no se aplican record rules. Igual que en get_company_dependent_values, es recomendable validar browse(res_id).exists() y check_access_rule('read') antes del SELECT.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +56
get autoCompletePlaceholder() {
return _t("Sin valor (vaciar explicitamente)");
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hay varios textos traducibles con faltas de ortografía (sin tilde): "explicitamente" debería ser "explícitamente". Al ir dentro de _t(), esto termina en el UI tal cual.

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +217
self.env["ir.model.access"].check(res_model, "write")
model_obj = self.env[res_model]
field = model_obj._fields.get(field_name)

if not field or not field.company_dependent:
raise ValueError(f"El campo '{field_name}' en '{res_model}' no es company_dependent.")

raw_json = self._get_raw_json(model_obj._table, field_name, res_id)

for company_id_str, value in values_dict.items():
key = str(company_id_str)
if value == "RESET":
raw_json.pop(key, None)
else:
raw_json[key] = value

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En la escritura se actualiza el JSONB por SQL sin validar reglas de registro ni que el res_id exista. Además, values_dict permite enviar company_ids arbitrarios: habría que limitar/validar que los IDs estén dentro de env.companies (o del set permitido por el usuario), y rechazar/ignorar el resto. Recomiendo: browse(res_id).exists() + check_access_rule('write') antes del UPDATE y filtrar values_dict por compañías accesibles.

Copilot uses AI. Check for mistakes.
…display

**Domain filtering in multicompany dialog**
- Replace `field.get_comodel_domain()` with new `_get_field_static_domain()` helper.
  In Odoo 19, `get_comodel_domain` silently returns `Domain.TRUE` for string
  domains (e.g. `"[('account_type', '=', 'asset_receivable')]"`), causing the
  autocomplete to ignore the field's static filter. The new helper handles all
  three domain formats: callable, list/Domain, and string (via `ast.literal_eval`).
- Each dialog row now receives the correct combined domain:
  static field domain + `_check_company_domain(company)`.

**Write via ORM instead of raw JSON**
- `set_company_dependent_values`: explicit values (ID or False) are now written
  via `record.with_company(company).write(...)` so that `check_company`,
  `ondelete` constraints and access rules are enforced by the ORM.
- RESET operation still uses direct SQL (the only case where the ORM has no
  native equivalent: removing a key from the JSONB column).

**Fallback resolution**
- Replace `ir.default._get()` with `model_obj.default_get([field_name])` to
  also resolve defaults defined in Python code (`default=...`), not only those
  stored in `ir.default`.
- When `is_specific=False`, `value_id` and `display_value` are now populated
  from the resolved fallback so the dialog input shows the inherited value in grey.
@jjscarafia
Copy link
Member Author

@roboadhoc r+

@roboadhoc
Copy link
Contributor

@jjscarafia because this PR has multiple commits, I need to know how to merge it:

  • merge to merge directly, using the PR as merge commit message
  • rebase-merge to rebase and merge, using the PR as merge commit message
  • rebase-ff to rebase and fast-forward

@jjscarafia
Copy link
Member Author

@roboadhoc rebase-ff nobump

@roboadhoc
Copy link
Contributor

Merge method set to rebase and fast-forward.

@roboadhoc
Copy link
Contributor

@jjscarafia 'ci/runbot-modified-modules' failed on this reviewed PR.

4 similar comments
@roboadhoc
Copy link
Contributor

@jjscarafia 'ci/runbot-modified-modules' failed on this reviewed PR.

@roboadhoc
Copy link
Contributor

@jjscarafia 'ci/runbot-modified-modules' failed on this reviewed PR.

@roboadhoc
Copy link
Contributor

@jjscarafia 'ci/runbot-modified-modules' failed on this reviewed PR.

@roboadhoc
Copy link
Contributor

@jjscarafia 'ci/runbot-modified-modules' failed on this reviewed PR.

@jjscarafia jjscarafia merged commit cd0d5b1 into ingadhoc:19.0 Mar 17, 2026
3 of 4 checks passed
@jjscarafia jjscarafia deleted the 19.0-t-62336-jjs branch March 17, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants