Skip to content

[FIX] stock_ux: import a count onto the quant it belongs to - #1007

Open
mav-adhoc wants to merge 1 commit into
ingadhoc:19.0from
adhoc-dev:19.0-t-71351-mav
Open

[FIX] stock_ux: import a count onto the quant it belongs to#1007
mav-adhoc wants to merge 1 commit into
ingadhoc:19.0from
adhoc-dev:19.0-t-71351-mav

Conversation

@mav-adhoc

@mav-adhoc mav-adhoc commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

While importing, stock.quant.create skips the lookup of the existing quant so that one row stays one record. Every imported row therefore created a second line for a product that already had stock in that location, and the counted quantity ended up added to the stock on hand instead of replacing it. A counted 0 did nothing at all, because the count landed on a brand new empty line.

_load_records_create now resolves the quant by its natural key — product, location, lot, package and owner — and writes the count on it, creating the line only when there is none. It is the inverse of the skip core does while importing.

Notes

  • The override returns exactly one record per row, in order, so the invariant the core skip protects is preserved (_load_records zips its result with the rows it was handed). Rows sharing a key are the same line: the last count wins, instead of two lines that core's merge would later add up.
  • A lot name is only unique per product and the importer resolves it with a plain name_search, so a lot that resolved to another product's is re-pointed before matching. Without that, a tracked product duplicates its line all the same.
  • inventory_quantity_set is written explicitly: writing a counted 0 over a line that was never counted is a no-op write, the compute would not run, and the line would show no difference and stay out of "Apply all".
  • Only a row that actually counts is a count. A row with no counted quantity column, or with that cell left empty, names its line without saying anything about it, so it comes out untouched. Counting part of an exported file is the ordinary file, and an empty cell has to stay apart from a counted 0 — the converter turns both into the same 0.0, which is why they are told apart while the rows are still the strings of the file.
  • Only someone allowed to adjust inventory gets a count applied: without stock_ux.group_stock_inventory_adjustment the rows fall back to what core answers, instead of being written as superuser. The lookup runs with the author's own rights, so a row can only land on a line they are allowed to see.
  • One indexed lookup for the whole file rather than one per row, which is what a count of thousands of lines is.

Test plan

stock_ux/tests/test_quant_import.py, 9 tests: an existing line is replaced and not added up; a counted 0 empties the line; a line is still created when there is none; the lot matched is the one of the row's product; the inventoried quantity column applies on the right line; two rows for the same line count it once; a row with no counted quantity column leaves the line alone; a row with that cell empty leaves the line alone; an import by a user without the adjustment group changes nothing.

Ran on 19.0: 0 failed, 0 error(s) of 17 tests. With the model file reverted, 6 of the 9 fail — e.g. AssertionError: 2 != 1 : the count must land on the existing line. The other three are guards, each failing against an earlier version of this branch: an empty cell is not a counted 0, no row said 0, and the count being applied without the group.

Task: https://www.adhoc.inc/odoo/all-tasks/12857/all-tasks/71351

Copilot AI lite review requested due to automatic review settings August 24, 2026 19:28
@roboadhoc

Copy link
Copy Markdown
Collaborator

Pull request status dashboard

Copilot AI left a comment

Copy link
Copy Markdown

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 corrige el import de inventario sobre stock.quant para que cada fila aplique el conteo sobre la línea de stock existente (por clave natural: producto, ubicación, lote, paquete, propietario), evitando duplicar quants y que el conteo se “sume” al on-hand. Además, mejora el feedback cuando se intenta importar columnas no permitidas en un ajuste de inventario.

Changes:

  • Override de _load_records_create en stock.quant para resolver (o crear) el quant objetivo y escribir el conteo sobre él.
  • Normalización del lote importado (homónimos entre productos) antes del match por clave natural.
  • Nuevos tests que cubren reemplazo vs suma, conteo 0, creación cuando no existe, match correcto de lote y error con columna no importable.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
stock_ux/models/stock_quant.py Implementa el match por clave natural en import y escribe el conteo sobre el quant existente; agrega validación de columnas importables con error más explícito.
stock_ux/tests/test_quant_import.py Agrega suite de tests para asegurar que el import reemplaza cantidades (incluido 0) y respeta lote/producto.
stock_ux/tests/init.py Incluye el nuevo archivo de tests en el paquete.
stock_ux/README.rst Documenta el nuevo comportamiento del import de conteos y el error más explícito.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread stock_ux/models/stock_quant.py Outdated
def _write_imported_quantity(self, quant, vals):
"""Apply an imported row on the quant it matched, the same way core applies it on
the quant it gathers when it is not importing."""
quant = quant.sudo().with_context(inventory_mode=True)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, dropped. The lookup now runs with the author's own rights and the write in their own environment with inventory_mode=True, which is the path the editable list itself takes (this module grants write on stock.quant to the adjustment group). The whole matching branch is behind that group now: without it the rows fall back to what core answers. test_import_without_the_adjustment_group_changes_nothing covers it and fails on the previous version of this branch.

Comment thread stock_ux/models/stock_quant.py Outdated
Comment on lines +93 to +99
for index, vals in enumerate(values):
self._normalize_import_lot(vals)
quant = self._find_quant_by_natural_key(vals)
if quant:
records[index] = self._write_imported_quantity(quant, vals)
else:
to_create.append((index, vals))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed and fixed: only a row carrying inventory_quantity or inventory_quantity_auto_apply is treated as a count, anything else is left to core. test_row_without_a_count_leaves_the_line_alone reproduces the old behaviour, where the existing line came out counted as 0.

Comment on lines +27 to +33
def _set_import_default_location(self, values):
"""Same default location core applies while importing, needed before the lookup so
matching and creation agree on where the row lands."""
warehouse = self.env["stock.warehouse"].search([("company_id", "=", self.env.company.id)], limit=1)
for vals in values:
if "location_id" not in vals:
vals["location_id"] = warehouse.lot_stock_id.id

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not a crash: search(..., limit=1) returns an empty recordset and warehouse.lot_stock_id.id is False on it, which is exactly what core writes into the row in its own _load_records_create. The row then fails on the required location with the message core gives. Kept as is so that matching and creation agree on where a row lands.

While importing, core skips the lookup of the existing quant to keep one row
= one record, so every row created a second line for a product that already
had stock in that location: the counted quantity ended up added to the stock
on hand instead of replacing it, and a counted 0 did nothing at all.

Resolve the quant by its natural key (product, location, lot, package and
owner) and write the count on it, creating the line only when there is none.
The lookup returns one record per row, in order, so the invariant core
protects is preserved. A lot name is only unique per product, so re-point a
lot the importer resolved to another product's before matching.

Only a row that actually counts is a count, and only someone allowed to
adjust inventory gets one applied. A row with no counted quantity column, or
with that cell left empty, names its line without saying anything about it:
counting part of an exported file is the ordinary file, and the rows left
blank must come out untouched rather than emptied. A user without the
adjustment group falls back to what core answers rather than writing as
superuser.
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.

3 participants