diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index cb37e4b..0f33715 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -189,9 +189,11 @@ addons-cm/ ├── membership_monthly_invoicing/ # Factura mensual de membresía por socio (cron) ├── membership_expiry_reminder/ # Email recordatorio de renovación próxima ├── # --- Logística y contabilidad --- -├── stock_picking_batch_custom/ # Batch picking: columnas extra + vista operario (Basket Assembly) +├── stock_picking_batch_collect/ # Batch picking: columnas extra + vista operario (Basket Assembly) ├── account_banking_mandate_batch/ # Crear/validar mandatos SEPA en bloque desde contactos -└── l10n_es_edi_tbai_reagyp_recibidas/ # Fix TicketBAI REAGYP facturas recibidas (19 → 02) +├── l10n_es_edi_tbai_reagyp_recibidas/ # Fix TicketBAI REAGYP facturas recibidas (19 → 02) +├── # --- UI de backend --- +└── web_list_striped/ # Efecto cebra en todas las listas del backend (sólo CSS) ```` @@ -233,7 +235,8 @@ addons-cm/ **Logística y contabilidad** -- [stock_picking_batch_custom](../stock_picking_batch_custom/README.rst) - Columnas extra y vista operario para batch picking +- [stock_picking_batch_collect](../stock_picking_batch_collect/README.rst) - Columnas extra, resumen por producto y vista operario para batch picking +- [web_list_striped](../web_list_striped/README.rst) - Efecto cebra en todas las listas del backend (sólo CSS) - [account_banking_mandate_batch](../account_banking_mandate_batch/README.rst) - Creación masiva de mandatos SEPA desde contactos - [l10n_es_edi_tbai_reagyp_recibidas](../l10n_es_edi_tbai_reagyp_recibidas/README.rst) - Fix TicketBAI REAGYP en facturas recibidas (clave régimen 19 → 02) @@ -758,6 +761,10 @@ last_purchase_price_compute_type != "manual_update" # Para auto-cálculo `product_pricelist_total_margin`, `product_origin_char`, `l10n_es_edi_tbai_reagyp_recibidas`. `product_origin` (OCA) sustituido por `product_origin_char` (custom). - **2026-06-04**: `product_sale_price_from_pricelist` aplica descuentos de proveedor desde `supplierinfo`. +- **2026-08-27**: `stock_picking_batch_custom` renombrado a `stock_picking_batch_collect` + (v18.0.2.0.0). Se invierte la dependencia: ya no depende de `website_sale_aplicoop`; es este + el que depende de él e inyecta las columnas de grupo de consumo. El efecto cebra global se + extrae al nuevo addon `web_list_striped`. El renombrado en BD lo hace un `pre_init_hook`. - **2026-06**: `stock_picking_batch_custom` — vista operario a pantalla completa (Basket Assembly), ordenación de operaciones por categoría/producto/partner. - **2026-02-18**: Refactor `product_main_seller` - Remover alias innecesario `default_supplier_id` diff --git a/README.md b/README.md index 8dbf2e0..2a34a8b 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ Addons desarrollados por Criptomart para necesidades específicas del proyecto. | [product_price_category_supplier](product_price_category_supplier/) | 18.0.1.0.0 | Categoría de precio por defecto en proveedor + actualización masiva de productos | | [product_pricelist_total_margin](product_pricelist_total_margin/) | 18.0.1.2.0 | Margen aditivo (no compuesto) en tarifas encadenadas, con límites globales | | [product_sale_price_from_pricelist](product_sale_price_from_pricelist/) | 18.0.2.7.0 | Calcula precio de venta desde último precio de compra vía tarifa configurable | -| [stock_picking_batch_custom](stock_picking_batch_custom/) | 18.0.1.0.0 | Columnas extra en operaciones detalladas de lotes: partner, categoría, recogido | -| [website_sale_aplicoop](website_sale_aplicoop/) | 18.0.1.9.0 | Sistema de pedidos colaborativos para grupos de consumo (reemplazo de Aplicoop) | +| [stock_picking_batch_collect](stock_picking_batch_collect/) | 18.0.2.0.0 | Montaje de cestas en lotes: columnas extra, resumen por producto y vista operario | +| [web_list_striped](web_list_striped/) | 18.0.1.0.0 | Efecto cebra y hover suave en todas las listas del backend (sólo CSS) | +| [website_sale_aplicoop](website_sale_aplicoop/) | 18.0.1.17.0 | Sistema de pedidos colaborativos para grupos de consumo (reemplazo de Aplicoop) | ## Dependencias entre addons custom @@ -40,6 +41,12 @@ website_sale_aplicoop └── product_main_seller └── product_price_category_supplier └── product_price_category + └── stock_picking_batch_collect + └── stock_move_manual_quantity + +stock_picking_batch_collect + └── stock_move_manual_quantity + └── stock_picking_batch (Odoo core) account_invoice_triple_discount_readonly └── account_invoice_triple_discount diff --git a/stock_move_manual_quantity/__init__.py b/stock_move_manual_quantity/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/stock_move_manual_quantity/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_move_manual_quantity/__manifest__.py b/stock_move_manual_quantity/__manifest__.py new file mode 100644 index 0000000..43bf599 --- /dev/null +++ b/stock_move_manual_quantity/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ # noqa: B018 + "name": "Stock Move Manual Quantity", + "version": "18.0.1.0.0", + "category": "Warehouse", + "summary": "A hand-typed quantity is final: never re-reserved automatically", + "license": "AGPL-3", + "author": "Odoo Community Association (OCA), Criptomart", + "maintainers": ["Criptomart"], + "website": "https://git.criptomart.net/criptomart/addons-cm", + "depends": ["stock"], + "data": [ + "views/stock_move_line_views.xml", + ], +} diff --git a/stock_move_manual_quantity/models/__init__.py b/stock_move_manual_quantity/models/__init__.py new file mode 100644 index 0000000..d7b52af --- /dev/null +++ b/stock_move_manual_quantity/models/__init__.py @@ -0,0 +1,3 @@ +from . import stock_move +from . import stock_move_line +from . import stock_picking diff --git a/stock_move_manual_quantity/models/stock_move.py b/stock_move_manual_quantity/models/stock_move.py new file mode 100644 index 0000000..9a82907 --- /dev/null +++ b/stock_move_manual_quantity/models/stock_move.py @@ -0,0 +1,60 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api +from odoo import models +from odoo.tools import float_compare +from odoo.tools import float_is_zero + + +class StockMove(models.Model): + _inherit = "stock.move" + + @api.onchange("quantity") + def _onchange_quantity_manual(self): + """Mark the move as picked when a user types its quantity. + + Covers the ``Operations`` tab of a transfer, where the quantity is + edited on the move and ``_set_quantity`` spreads it to the lines. + """ + for move in self: + if move.state not in ("done", "cancel"): + move.picked = True + + def write(self, vals): + res = super().write(vals) + if "quantity" in vals and vals.get("picked"): + self._align_demand_to_manual_quantity() + return res + + def _align_demand_to_manual_quantity(self): + """Lower the demand of a move to the quantity a user typed by hand. + + A move whose quantity is below its demand goes back to + ``partially_available`` (``_recompute_state``), and that is precisely + what makes the scheduler reserve the difference again. Aligning the + demand keeps the move ``assigned`` and drops the backorder question for + a quantity that is final: a weighed product is never completed later. + + The demand is only ever lowered. Above it the move is already + ``assigned``, so nothing has to be touched. + """ + for move in self: + if move.state in ("draft", "done", "cancel") or move.is_inventory: + continue + if move.move_dest_ids: + # Chained moves: lowering the demand here would leave the next + # step of the route asking for more than this one delivers. + continue + rounding = move.product_uom.rounding + if float_is_zero(move.quantity, precision_rounding=rounding): + # Nothing was picked: leave the demand alone so the transfer + # still offers its usual backorder choice. + continue + if ( + float_compare( + move.quantity, move.product_uom_qty, precision_rounding=rounding + ) + < 0 + ): + move.product_uom_qty = move.quantity diff --git a/stock_move_manual_quantity/models/stock_move_line.py b/stock_move_manual_quantity/models/stock_move_line.py new file mode 100644 index 0000000..b7c74d4 --- /dev/null +++ b/stock_move_manual_quantity/models/stock_move_line.py @@ -0,0 +1,50 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api +from odoo import models + + +class StockMoveLine(models.Model): + _inherit = "stock.move.line" + + @api.onchange("quantity") + def _onchange_quantity_manual(self): + """A quantity typed by a user is the quantity physically handled. + + Onchanges only run from the user interface, never from the reservation + engine, so this marks the lines a person edited without freezing the + ones ``_action_assign`` reserves on its own. + """ + for line in self: + if line.state not in ("done", "cancel"): + line.picked = True + + def write(self, vals): + res = super().write(vals) + # `_onchange_quantity_manual` makes the interface send both fields in + # the same write, which is what tells a manual edit from a reservation. + if "quantity" in vals and vals.get("picked"): + self._freeze_manual_quantity() + return res + + def _freeze_manual_quantity(self, align_demand=True): + """Protect a hand-typed quantity from the reservation engine. + + ``_action_assign`` (run by the ``Procurement: run scheduler`` cron and + by ``_trigger_assign`` whenever another transfer of the same product is + validated) tops up every move that is not picked until it reaches its + demand, and ``_free_reservation`` steals quantities from move lines + that are not picked. Marking the whole move keeps both away from it. + """ + lines = self.filtered(lambda ml: ml.state not in ("done", "cancel")) + if not lines: + return + siblings = (lines.move_id.move_line_ids - lines).filtered( + lambda ml: not ml.picked and ml.state not in ("done", "cancel") + ) + if siblings: + # `_action_done` unlinks the non picked lines of a picked move. + siblings.picked = True + if align_demand: + lines.move_id._align_demand_to_manual_quantity() diff --git a/stock_move_manual_quantity/models/stock_picking.py b/stock_move_manual_quantity/models/stock_picking.py new file mode 100644 index 0000000..d0641e3 --- /dev/null +++ b/stock_move_manual_quantity/models/stock_picking.py @@ -0,0 +1,31 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models +from odoo.tools import float_is_zero + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + def _pre_action_done_hook(self): + """Validate every line holding a quantity, picked by hand or not. + + Core only auto-picks a transfer when *no* move is picked yet, so as + soon as an operator freezes one weighed line the untouched ones would + be pushed to a backorder -- or cancelled, if the user answers "no + backorder" -- even though their goods are in the basket. Picking + everything that carries a quantity keeps the behaviour operators know. + """ + for picking in self.filtered(lambda p: p.state not in ("done", "cancel")): + moves = picking.move_ids.filtered( + lambda move: not move.picked + and not move.scrapped + and move.state not in ("done", "cancel") + and not float_is_zero( + move.quantity, precision_rounding=move.product_uom.rounding + ) + ) + if moves: + moves.picked = True + return super()._pre_action_done_hook() diff --git a/stock_move_manual_quantity/readme/CONFIGURE.rst b/stock_move_manual_quantity/readme/CONFIGURE.rst new file mode 100644 index 0000000..6a053eb --- /dev/null +++ b/stock_move_manual_quantity/readme/CONFIGURE.rst @@ -0,0 +1,2 @@ +No configuration is required: the behaviour applies to every transfer as soon +as the module is installed. diff --git a/stock_move_manual_quantity/readme/CONTRIBUTORS.rst b/stock_move_manual_quantity/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..ecd0958 --- /dev/null +++ b/stock_move_manual_quantity/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Criptomart `_: + + * Analysis of the reservation engine and implementation diff --git a/stock_move_manual_quantity/readme/CREDITS.rst b/stock_move_manual_quantity/readme/CREDITS.rst new file mode 100644 index 0000000..d1ba675 --- /dev/null +++ b/stock_move_manual_quantity/readme/CREDITS.rst @@ -0,0 +1,3 @@ +**Authors:** + +* Criptomart diff --git a/stock_move_manual_quantity/readme/DESCRIPTION.rst b/stock_move_manual_quantity/readme/DESCRIPTION.rst new file mode 100644 index 0000000..db47fe0 --- /dev/null +++ b/stock_move_manual_quantity/readme/DESCRIPTION.rst @@ -0,0 +1,22 @@ +When an operator types a quantity on a transfer line, that quantity is the one +that was physically handled: a bag of apples ordered as 1 kg that weighs +0.87 kg on the scale. + +Odoo, however, treats a quantity below the demand as an incomplete +*reservation*. The move goes back to ``partially_available`` and +``_action_assign`` tops it up again to the demanded quantity, either from the +``Procurement: run scheduler`` cron or from ``_trigger_assign``, which runs +every time another transfer of the same product is validated. A few hours +later the weighed 0.87 kg is back to 1 kg. Raising the quantity is never +reverted, because the move stays ``assigned`` and no longer matches those +domains. + +This module makes a hand-typed quantity final: + +* Editing a quantity from the interface marks the move as **picked**, which is + what keeps ``_action_assign`` and ``_free_reservation`` away from it. +* When the typed quantity is below the demand, the demand is lowered to match, + so the move stays ``assigned`` and no backorder is asked for a quantity that + will never be completed. +* Transfers still validate every line that carries a quantity, picked by hand + or not, so freezing one line never pushes the untouched ones to a backorder. diff --git a/stock_move_manual_quantity/readme/INSTALL.rst b/stock_move_manual_quantity/readme/INSTALL.rst new file mode 100644 index 0000000..5e1e1b9 --- /dev/null +++ b/stock_move_manual_quantity/readme/INSTALL.rst @@ -0,0 +1,8 @@ +To install this module, you need to: + +#. Update the Apps list +#. Install *Stock Move Manual Quantity* + +:: + + docker-compose run --rm odoo odoo -d odoo --stop-after-init -i stock_move_manual_quantity diff --git a/stock_move_manual_quantity/readme/USAGE.rst b/stock_move_manual_quantity/readme/USAGE.rst new file mode 100644 index 0000000..eddf61f --- /dev/null +++ b/stock_move_manual_quantity/readme/USAGE.rst @@ -0,0 +1,23 @@ +Nothing to configure: the behaviour applies to every transfer as soon as the +module is installed. + +**Weighing a line** + +#. Open the transfer (or the batch operator view) and type the real quantity, + for example 0.87 instead of the demanded 1. +#. The move is marked as picked and, because 0.87 is below the demand, the + demand becomes 0.87 too. The move stays *Ready*. +#. Neither the scheduler nor the validation of another transfer of the same + product will change that quantity again. + +**Notes** + +* The demand is only ever lowered, never raised: above the demand the move is + already fully reserved and nothing needs to be adjusted. +* A line left at zero keeps the standard behaviour, so the transfer still + offers its usual backorder choice for what was not delivered. +* Chained moves (multi-step routes) are only frozen, their demand is left + untouched so the next step of the route stays consistent. +* Lowering the demand is logged in the transfer chatter, and it is what the + sale order uses to know how much is still to deliver while the transfer is + not validated. diff --git a/stock_move_manual_quantity/tests/__init__.py b/stock_move_manual_quantity/tests/__init__.py new file mode 100644 index 0000000..9a3ccd6 --- /dev/null +++ b/stock_move_manual_quantity/tests/__init__.py @@ -0,0 +1 @@ +from . import test_manual_quantity diff --git a/stock_move_manual_quantity/tests/test_manual_quantity.py b/stock_move_manual_quantity/tests/test_manual_quantity.py new file mode 100644 index 0000000..fe2d6a8 --- /dev/null +++ b/stock_move_manual_quantity/tests/test_manual_quantity.py @@ -0,0 +1,184 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.tests import Form +from odoo.tests import TransactionCase +from odoo.tests import tagged + + +@tagged("-at_install", "post_install") +class TestManualQuantity(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.uom_kg = cls.env.ref("uom.product_uom_kgm") + cls.stock_location = cls.env.ref("stock.stock_location_stock") + cls.customer_location = cls.env.ref("stock.stock_location_customers") + cls.picking_type_out = cls.env.ref("stock.picking_type_out") + + cls.apples = cls.env["product.product"].create( + { + "name": "Apples", + "is_storable": True, + "uom_id": cls.uom_kg.id, + "uom_po_id": cls.uom_kg.id, + } + ) + cls.bread = cls.env["product.product"].create( + { + "name": "Bread", + "is_storable": True, + "uom_id": cls.uom_kg.id, + "uom_po_id": cls.uom_kg.id, + } + ) + for product in cls.apples | cls.bread: + cls.env["stock.quant"]._update_available_quantity( + product, cls.stock_location, 100.0 + ) + + # Helpers + + def _create_picking(self, products_qty): + """Create a confirmed and reserved delivery for ``{product: demand}``.""" + picking = self.env["stock.picking"].create( + { + "picking_type_id": self.picking_type_out.id, + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + "move_ids": [ + Command.create( + { + "name": product.name, + "product_id": product.id, + "product_uom": product.uom_id.id, + "product_uom_qty": qty, + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + } + ) + for product, qty in products_qty.items() + ], + } + ) + picking.action_confirm() + picking.action_assign() + return picking + + def _type_quantity(self, line, quantity): + """Reproduce what the web client sends when a user types a quantity. + + The onchange marks the line as picked, so both fields travel together + in the same write. + """ + edited = line.new(origin=line) + edited.quantity = quantity + edited._onchange_quantity_manual() + line.write({"quantity": quantity, "picked": edited.picked}) + + # Tests + + def test_onchange_marks_the_line_as_picked(self): + picking = self._create_picking({self.apples: 1.0}) + line = picking.move_ids.move_line_ids + + edited = line.new(origin=line) + edited.quantity = 0.87 + edited._onchange_quantity_manual() + + self.assertTrue(edited.picked) + + def test_reservation_does_not_restore_a_lowered_quantity(self): + picking = self._create_picking({self.apples: 1.0}) + move = picking.move_ids + self.assertEqual(move.quantity, 1.0) + + self._type_quantity(move.move_line_ids, 0.87) + + self.assertTrue(move.picked) + self.assertEqual(move.product_uom_qty, 0.87) + self.assertEqual(move.state, "assigned") + + # The two entry points that used to top the move back up to its demand. + move._action_assign() + self.env["procurement.group"].run_scheduler() + + self.assertEqual(move.quantity, 0.87) + self.assertEqual(move.move_line_ids.quantity, 0.87) + + def test_a_raised_quantity_keeps_its_demand(self): + picking = self._create_picking({self.apples: 1.0}) + move = picking.move_ids + + self._type_quantity(move.move_line_ids, 1.2) + + self.assertEqual(move.product_uom_qty, 1.0) + self.assertEqual(move.state, "assigned") + + move._action_assign() + + self.assertEqual(move.quantity, 1.2) + + def test_an_empty_line_keeps_its_demand(self): + """A line left at zero still offers the standard backorder choice.""" + picking = self._create_picking({self.apples: 1.0}) + move = picking.move_ids + + self._type_quantity(move.move_line_ids, 0.0) + + self.assertEqual(move.product_uom_qty, 1.0) + + def test_reserving_does_not_mark_moves_as_picked(self): + """Only a person marks a move as picked, never the reservation.""" + picking = self._create_picking({self.apples: 1.0}) + + picking.move_ids._action_assign() + self.env["procurement.group"].run_scheduler() + + self.assertFalse(picking.move_ids.picked) + self.assertFalse(picking.move_ids.move_line_ids.picked) + + def test_editing_the_move_quantity_freezes_it_too(self): + """The Operations tab of a transfer edits the quantity on the move.""" + picking = self._create_picking({self.apples: 1.0}) + move = picking.move_ids + + with Form(picking) as picking_form: + with picking_form.move_ids_without_package.edit(0) as move_form: + move_form.quantity = 0.87 + + self.assertTrue(move.picked) + self.assertEqual(move.product_uom_qty, 0.87) + + move._action_assign() + + self.assertEqual(move.quantity, 0.87) + + def test_untouched_lines_are_still_validated(self): + """Freezing one line must not push the others to a backorder.""" + picking = self._create_picking({self.apples: 1.0, self.bread: 2.0}) + apples_move = picking.move_ids.filtered( + lambda move: move.product_id == self.apples + ) + bread_move = picking.move_ids - apples_move + + self._type_quantity(apples_move.move_line_ids, 0.87) + picking.button_validate() + + self.assertEqual(picking.state, "done") + self.assertEqual(apples_move.quantity, 0.87) + self.assertEqual(bread_move.state, "done") + self.assertEqual(bread_move.quantity, 2.0) + self.assertFalse(picking.backorder_ids) + + def test_operation_views_load_the_picked_field(self): + """The client only sends back the fields present in the arch.""" + move_line = self.env["stock.move.line"] + for xml_id in ( + "stock.view_stock_move_line_operation_tree", + "stock.view_stock_move_line_detailed_operation_tree", + ): + with self.subTest(view=xml_id): + view = move_line.get_view(self.env.ref(xml_id).id, "list") + self.assertIn('name="picked"', view["arch"]) diff --git a/stock_move_manual_quantity/views/stock_move_line_views.xml b/stock_move_manual_quantity/views/stock_move_line_views.xml new file mode 100644 index 0000000..91b50b9 --- /dev/null +++ b/stock_move_manual_quantity/views/stock_move_line_views.xml @@ -0,0 +1,29 @@ + + + + + stock.move.line.operations.manual.quantity + stock.move.line + + + + + + + + + + stock.move.line.detailed.operations.manual.quantity + stock.move.line + + + + + + + + diff --git a/stock_picking_batch_collect/README.rst b/stock_picking_batch_collect/README.rst new file mode 100644 index 0000000..75a70b6 --- /dev/null +++ b/stock_picking_batch_collect/README.rst @@ -0,0 +1,127 @@ +=========================== +Stock Picking Batch Collect +=========================== + +Visión general +============== +Este módulo convierte un lote de picking en una hoja de trabajo para el operario +que monta las cestas: amplía las operaciones detalladas, añade un resumen por +producto y una vista a pantalla completa para el almacén. + +- ``picking_partner_id`` (Partner del albarán) para que el personal de almacén + identifique rápido el cliente/proveedor. +- ``product_categ_id`` (Categoría de producto) para ordenar y agrupar. Las + líneas del lote se ordenan por categoría, producto y partner. +- ``product_default_code`` (Referencia interna) como columna opcional. +- ``is_collected`` (Recogido) como check manual en cada línea para marcar si se + ha recolectado. +- Nueva pestaña **Product Summary** con totales por producto (demandado, hecho, + pendiente), categoría y el check de recogido consolidado. +- Botón **Basket Assembly**: una vista de lista a pantalla completa, con + cantidades grandes y controles táctiles, pensada para tablet en el almacén. +- Restricciones de validación configurables por compañía: el lote no se puede + validar hasta que las líneas estén marcadas como recogidas. + +Las columnas se añaden con ``optional="show"`` en la vista de líneas del lote +(salvo ``product_default_code``, oculta por defecto), de modo que el usuario +puede desactivarlas desde el selector de columnas sin recargar la vista. + +Si además está instalado ``website_sale_aplicoop``, ese módulo añade a estas +mismas vistas las columnas de grupo de consumo (**Consumer Group** y **Home +Delivery**). Este módulo no depende de él: funciona igual sin la parte de +eCommerce. + +Instalación +=========== + +Actualizar o instalar el módulo: + +:: + + docker-compose run --rm odoo odoo -d odoo --stop-after-init -u stock_picking_batch_collect + +Este módulo sustituye a ``stock_picking_batch_custom``. Al instalarlo sobre una +base de datos que tuviera el módulo antiguo, un ``pre_init_hook`` adopta sus +datos (ver ``hooks.py``): se conservan las líneas recogidas, el resumen por +producto y la configuración por compañía. + +Configuración +============= + +Restricciones de validación +--------------------------- + +En **Inventory > Configuration > Settings**, bloque *Operations*, hay dos +ajustes por compañía: + +- **Restricciones del resumen de productos**: exige que las líneas de la + pestaña *Product Summary* estén marcadas como recogidas para validar el lote. + Desactivado por defecto (el check es sólo informativo). +- **Restricciones de operaciones detalladas**: exige que las líneas de la + pestaña *Detailed Operations* estén marcadas como recogidas. + **Activado por defecto**: recién instalado el módulo, un lote no se puede + validar hasta marcar las líneas. + +Cada restricción tiene un alcance: + +- *Sólo procesadas*: se comprueban únicamente las líneas con cantidad hecha + mayor que cero (por defecto). +- *Todas*: se comprueban todas las líneas del lote, procesadas o no. + +Columnas +-------- + +Las columnas están visibles por defecto. Para ajustarlas: + +- Abrir un **Lote de picking**. +- Ir a la pestaña **Detailed Operations**. +- Abrir el **selector de columnas** y activar o desactivar *Partner*, + *Product Category*, *Product Code* o *Collected* según necesidad. + +Uso +=== + +1. Accede a **Inventory > Operations > Batch Transfers** y abre un lote. +2. Pestaña **Detailed Operations**: usa el selector de columnas para ajustar: + + - **Partner** (``picking_partner_id``) para ver el cliente/proveedor. + - **Product Category** (``product_categ_id``) para ordenar/agrupación por categoría. + - **Collected** (``is_collected``) para marcar manualmente líneas recolectadas. + +3. Pestaña **Product Summary**: consulta los totales por producto (demandado, + hecho y pendiente) y marca el check de recogido consolidado si corresponde. + +4. Con el lote **en progreso**, el botón **Basket Assembly** abre la vista de + operario: la misma lista de líneas a pantalla completa, con la cantidad en + grande y el check *Collected* como interruptor táctil. Está pensada para + trabajar desde una tablet mientras se montan las cestas. + +5. Ordena o agrupa por categoría en cualquiera de las vistas según convenga. + +6. La cantidad que teclea el operario (el peso real de la balanza) queda fijada: + se marca como *Picked* y ni el planificador ni la validación de otros + albaranes vuelven a modificarla. Marcar **Collected** protege igualmente la + cantidad de la línea. Ver ``stock_move_manual_quantity``. + +7. Al validar el lote, según la configuración de la compañía, se comprueba que + las líneas estén recogidas y se avisa con la lista de productos pendientes. + La comprobación se ejecuta después de resolver el backorder, de modo que + sólo bloquea sobre las líneas que realmente se van a procesar. + +Contribuidores +============== + +* Criptomart + +Créditos +======== + +Autor +----- + +* Criptomart + +Financiador +----------- + +* Elika Bilbo diff --git a/stock_picking_batch_collect/__init__.py b/stock_picking_batch_collect/__init__.py new file mode 100644 index 0000000..4a5c3e3 --- /dev/null +++ b/stock_picking_batch_collect/__init__.py @@ -0,0 +1,2 @@ +from . import models # noqa: F401 +from .hooks import pre_init_hook # noqa: F401 diff --git a/stock_picking_batch_collect/__manifest__.py b/stock_picking_batch_collect/__manifest__.py new file mode 100644 index 0000000..7e61eac --- /dev/null +++ b/stock_picking_batch_collect/__manifest__.py @@ -0,0 +1,31 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ # noqa: B018 + "name": "Stock Picking Batch Collect", + "version": "18.0.2.0.0", + "category": "Warehouse", + "summary": "Collect batch operations: operator view, extra columns and product summary", + "author": "Odoo Community Association (OCA), Criptomart", + "maintainers": ["Criptomart"], + "website": "https://github.com/Criptomart", + "license": "AGPL-3", + "depends": [ + # A quantity typed by the operator is the weighed one: it must not be + # re-reserved by the scheduler. + "stock_move_manual_quantity", + "stock_picking_batch", + ], + "data": [ + "security/ir.model.access.csv", + "views/res_config_settings_views.xml", + "views/stock_picking_batch_views.xml", + ], + "assets": { + "web.assets_backend": [ + "stock_picking_batch_collect/static/src/css/stock_picking_batch.css", + ], + }, + # Adopts the data of the former `stock_picking_batch_custom` when upgrading + # a database where that module was installed. See `hooks.py`. + "pre_init_hook": "pre_init_hook", +} diff --git a/stock_picking_batch_collect/hooks.py b/stock_picking_batch_collect/hooks.py new file mode 100644 index 0000000..7dcdaf4 --- /dev/null +++ b/stock_picking_batch_collect/hooks.py @@ -0,0 +1,117 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +_logger = logging.getLogger(__name__) + +OLD_MODULE = "stock_picking_batch_custom" +NEW_MODULE = "stock_picking_batch_collect" + +# Fields handed over to `website_sale_aplicoop`, which declares the very same +# ones. Dropping just the xmlid is enough and is what we want: Odoo matches the +# existing row again by (model, name) and re-registers it under aplicoop's +# ownership later in the same run (aplicoop depends on us, so it loads after +# us). Both are non-stored, so no column is touched. +HANDED_OVER = ( + "field_stock_move_line__home_delivery", + "field_stock_move_line__consumer_group_id", +) + +# Artifacts that disappear for good, because aplicoop already stores the same +# value as `stock.picking.consumer_group_id`. Here the record itself has to go +# too, not only the xmlid: deleting an `ir_model_data` row does not cascade, so +# the field would survive as an orphan manual field and the view as a custom +# view still pointing at it. Mapped as table -> xmlids. +DROPPED = { + "ir_model_fields": ("field_stock_picking__batch_consumer_group_id",), + "ir_ui_view": ("view_stock_picking_batch_picking_tree_consumer_group",), +} + + +def pre_init_hook(env): + """Adopt the database records of the former ``stock_picking_batch_custom``. + + A renamed addon is a brand new module for Odoo, so a versioned + ``migrations/`` script would never run. This hook does instead, on install, + after the Python is imported but before ``registry.load()`` and + ``init_models()``: remapping ``ir_model_data`` in that window makes Odoo + reuse the existing tables and columns instead of creating new ones, so + ``stock_move_line.is_collected``, the ``stock_picking_batch_summary_line`` + table and the ``res_company.batch_*`` settings all survive untouched. + + A no-op on a database where the old module was never installed. + """ + + cr = env.cr + cr.execute("SELECT id FROM ir_module_module WHERE name = %s", (OLD_MODULE,)) + old = cr.fetchone() + if not old: + return + old_id = old[0] + + cr.execute("SELECT id FROM ir_module_module WHERE name = %s", (NEW_MODULE,)) + new = cr.fetchone() + if not new: + # Should not happen: the module list is refreshed before installing. + _logger.warning("%s: no module row to adopt %s into", NEW_MODULE, OLD_MODULE) + return + new_id = new[0] + + _logger.info( + "Adopting %s (id=%s) as %s (id=%s)", OLD_MODULE, old_id, NEW_MODULE, new_id + ) + + # 1. Hand every xmlid over to the new module name. + cr.execute( + "UPDATE ir_model_data SET module = %s WHERE module = %s", + (NEW_MODULE, OLD_MODULE), + ) + _logger.info("Moved %s xmlids to %s", cr.rowcount, NEW_MODULE) + + # 2. Give back what is no longer ours, so aplicoop can claim it. + cr.execute( + "DELETE FROM ir_model_data WHERE module = %s AND name IN %s", + (NEW_MODULE, HANDED_OVER), + ) + + # 3. Drop for good what neither module defines any more, record included. + for table, xmlids in DROPPED.items(): + cr.execute( + "DELETE FROM %s WHERE id IN (" + " SELECT res_id FROM ir_model_data" + " WHERE module = %%s AND name IN %%s" + ")" % table, + (NEW_MODULE, xmlids), + ) + if cr.rowcount: + _logger.info("Dropped %s obsolete row(s) from %s", cr.rowcount, table) + cr.execute( + "DELETE FROM ir_model_data WHERE module = %s AND name IN %s", + (NEW_MODULE, tuple(name for names in DROPPED.values() for name in names)), + ) + + # 4. `ir_model_constraint.module` and `ir_model_relation.module` are integer + # FKs to `ir_module_module` with ON DELETE CASCADE: they must be + # repointed *before* the old row goes away, or the bookkeeping for our + # SQL constraints is silently destroyed with it. + cr.execute( + "UPDATE ir_model_constraint SET module = %s WHERE module = %s", + (new_id, old_id), + ) + cr.execute( + "UPDATE ir_model_relation SET module = %s WHERE module = %s", + (new_id, old_id), + ) + + # 5. Drop the dependency rows, both ours and any pointing at the old name. + cr.execute( + "DELETE FROM ir_module_module_dependency WHERE module_id = %s OR name = %s", + (old_id, OLD_MODULE), + ) + + # 6. Finally the module row itself. We delete the *old* one and keep the + # new one: the loader already holds the new row's id in memory, so + # renaming the old row in place would leave Odoo writing to a record + # that no longer exists. + cr.execute("DELETE FROM ir_module_module WHERE id = %s", (old_id,)) diff --git a/stock_picking_batch_collect/i18n/es.po b/stock_picking_batch_collect/i18n/es.po new file mode 100644 index 0000000..4bad2cb --- /dev/null +++ b/stock_picking_batch_collect/i18n/es.po @@ -0,0 +1,235 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_picking_batch_collect +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-05-21 13:18+0000\n" +"PO-Revision-Date: 2026-05-21 13:18+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_detailed_restriction_scope__all +msgid "All detailed lines" +msgstr "Todas las líneas detalladas" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_summary_restriction_scope__all +msgid "All summary products" +msgstr "Todos los productos del resumen" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_backorder_confirmation +msgid "Backorder Confirmation" +msgstr "Confirmación de entrega parcial" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__batch_id +msgid "Batch" +msgstr "Lote" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking__batch_consumer_group_id +msgid "Batch Consumer Group" +msgstr "Grupo de consumidores del lote" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Batch Detailed Operations Restriction" +msgstr "Restricción de operaciones detalladas del lote" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_picking_batch_summary_line +msgid "Batch Product Summary Line" +msgstr "Línea de resumen de productos del lote" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Batch Product Summary Restriction" +msgstr "Restricción del resumen de productos del lote" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_picking_batch +msgid "Batch Transfer" +msgstr "Traslado por lote" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__is_collected +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__is_collected +msgid "Collected" +msgstr "Recogido" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de configuración" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Configuración de restricciones para la pestaña Operaciones Detalladas." +msgstr "" +"Configuración de restricciones para la pestaña Operaciones Detalladas." + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Configuración de restricciones para la pestaña Product Summary." +msgstr "Configuración de restricciones para la pestaña Resumen de productos." + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__consumer_group_id +msgid "Consumer Group" +msgstr "Grupo de consumidores" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__qty_demanded +msgid "Demanded Quantity" +msgstr "Cantidad demandada" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_detailed_restriction_scope +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_detailed_restriction_scope +msgid "Detailed Operations Restriction Scope" +msgstr "Ámbito de restricción de operaciones detalladas" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__qty_done +msgid "Done Quantity" +msgstr "Cantidad hecha" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_detailed_restriction_enabled +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_detailed_restriction_enabled +msgid "Enforce Detailed Operations Restriction" +msgstr "Aplicar restricción de operaciones detalladas" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_summary_restriction_enabled +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_summary_restriction_enabled +msgid "Enforce Product Summary Restriction" +msgstr "Aplicar restricción del resumen de productos" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__home_delivery +msgid "Home Delivery" +msgstr "Entrega a domicilio" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__id +msgid "ID" +msgstr "ID" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_detailed_restriction_scope__processed +msgid "Only processed lines" +msgstr "Solo líneas procesadas" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_summary_restriction_scope__processed +msgid "Only processed products" +msgstr "Solo productos procesados" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__qty_pending +msgid "Pending Quantity" +msgstr "Cantidad pendiente" + +#. module: stock_picking_batch_collect +#. odoo-python +#: code:addons/stock_picking_batch_collect/models/stock_picking_batch.py:0 +msgid "Pending products: %(products)s" +msgstr "Productos pendientes: %(products)s" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__product_id +msgid "Product" +msgstr "Producto" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__product_categ_id +msgid "Product Category" +msgstr "Categoría de producto" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__product_categ_id +msgid "Product Category (Batch)" +msgstr "Categoría de producto (lote)" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_move_line +msgid "Product Moves (Stock Move Line)" +msgstr "Movimientos de producto (línea de movimiento de stock)" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch__summary_line_ids +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.view_stock_picking_batch_form_inherit_summary +msgid "Product Summary" +msgstr "Resumen de productos" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_summary_restriction_scope +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_summary_restriction_scope +msgid "Product Summary Restriction Scope" +msgstr "Ámbito de restricción del resumen de productos" + +#. module: stock_picking_batch_collect +#: model:ir.model.constraint,message:stock_picking_batch_collect.constraint_stock_picking_batch_summary_line_product_required +msgid "Product is required for summary lines." +msgstr "El producto es obligatorio en las líneas de resumen." + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_picking +msgid "Transfer" +msgstr "Traslado" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__product_uom_id +msgid "Unit of Measure" +msgstr "Unidad de medida" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,help:stock_picking_batch_collect.field_stock_move_line__home_delivery +msgid "Whether this picking includes home delivery (from sale order)" +msgstr "Si este albarán incluye entrega a domicilio (del pedido de venta)" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.view_stock_picking_batch_form_inherit_summary +msgid "Basket Assembly" +msgstr "Montaje de Cestas" diff --git a/stock_picking_batch_collect/i18n/eu.po b/stock_picking_batch_collect/i18n/eu.po new file mode 100644 index 0000000..62cd2c9 --- /dev/null +++ b/stock_picking_batch_collect/i18n/eu.po @@ -0,0 +1,235 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_picking_batch_collect +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-05-21 13:21+0000\n" +"PO-Revision-Date: 2026-05-21 13:21+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: eu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_detailed_restriction_scope__all +msgid "All detailed lines" +msgstr "Lerro xehatu guztiak" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_summary_restriction_scope__all +msgid "All summary products" +msgstr "Laburpeneko produktu guztiak" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_backorder_confirmation +msgid "Backorder Confirmation" +msgstr "Eskaeraren informazioa" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__batch_id +msgid "Batch" +msgstr "Lotea" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking__batch_consumer_group_id +msgid "Batch Consumer Group" +msgstr "Loteko kontsumitzaile taldea" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Batch Detailed Operations Restriction" +msgstr "Loteko eragiketa xehatuen murrizketa" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_picking_batch_summary_line +msgid "Batch Product Summary Line" +msgstr "Loteko produktuen laburpen-lerroa" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Batch Product Summary Restriction" +msgstr "Loteko produktuen laburpenaren murrizketa" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_picking_batch +msgid "Batch Transfer" +msgstr "Lote-transferentzia" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__is_collected +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__is_collected +msgid "Collected" +msgstr "Jasota" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_res_company +msgid "Companies" +msgstr "Enpresak" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_res_config_settings +msgid "Config Settings" +msgstr "Konfigurazio ezarpenak" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Configuración de restricciones para la pestaña Operaciones Detalladas." +msgstr "Eragiketa xehatuak fitxarako murrizketen konfigurazioa." + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.res_config_settings_view_form_inherit_batch_custom +msgid "Configuración de restricciones para la pestaña Product Summary." +msgstr "Produktuen laburpena fitxarako murrizketen konfigurazioa." + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__consumer_group_id +msgid "Consumer Group" +msgstr "Kontsumitzaile taldea" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__create_uid +msgid "Created by" +msgstr "Nork sortua" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__create_date +msgid "Created on" +msgstr "Noiz sortua" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__qty_demanded +msgid "Demanded Quantity" +msgstr "Eskatutako kantitatea" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_detailed_restriction_scope +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_detailed_restriction_scope +msgid "Detailed Operations Restriction Scope" +msgstr "Eragiketa xehatuen murrizketaren irismena" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__display_name +msgid "Display Name" +msgstr "Bistaratzeko izena" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__qty_done +msgid "Done Quantity" +msgstr "Egindako kantitatea" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_detailed_restriction_enabled +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_detailed_restriction_enabled +msgid "Enforce Detailed Operations Restriction" +msgstr "Eragiketa xehatuen murrizketa aplikatu" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_summary_restriction_enabled +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_summary_restriction_enabled +msgid "Enforce Product Summary Restriction" +msgstr "Produktuen laburpenaren murrizketa aplikatu" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__home_delivery +msgid "Home Delivery" +msgstr "Etxez etxeko entrega" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__id +msgid "ID" +msgstr "ID" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__write_uid +msgid "Last Updated by" +msgstr "Azken eguneratzailea" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__write_date +msgid "Last Updated on" +msgstr "Azken eguneratzea" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_detailed_restriction_scope__processed +msgid "Only processed lines" +msgstr "Prozesatutako lerroak soilik" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields.selection,name:stock_picking_batch_collect.selection__res_company__batch_summary_restriction_scope__processed +msgid "Only processed products" +msgstr "Prozesatutako produktuak soilik" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__qty_pending +msgid "Pending Quantity" +msgstr "Zain dagoen kantitatea" + +#. module: stock_picking_batch_collect +#. odoo-python +#: code:addons/stock_picking_batch_collect/models/stock_picking_batch.py:0 +msgid "Pending products: %(products)s" +msgstr "Zain dauden produktuak: %(products)s" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__product_id +msgid "Product" +msgstr "Produktua" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__product_categ_id +msgid "Product Category" +msgstr "Produktu-kategoria" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_move_line__product_categ_id +msgid "Product Category (Batch)" +msgstr "Produktu-kategoria (lotea)" + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_move_line +msgid "Product Moves (Stock Move Line)" +msgstr "Produktuen mugimenduak (stock mugimenduaren lerroa)" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch__summary_line_ids +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.view_stock_picking_batch_form_inherit_summary +msgid "Product Summary" +msgstr "Produktuen laburpena" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_company__batch_summary_restriction_scope +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_res_config_settings__batch_summary_restriction_scope +msgid "Product Summary Restriction Scope" +msgstr "Produktuen laburpenaren murrizketaren irismena" + +#. module: stock_picking_batch_collect +#: model:ir.model.constraint,message:stock_picking_batch_collect.constraint_stock_picking_batch_summary_line_product_required +msgid "Product is required for summary lines." +msgstr "Produktua derrigorrezkoa da laburpen-lerroetan." + +#. module: stock_picking_batch_collect +#: model:ir.model,name:stock_picking_batch_collect.model_stock_picking +msgid "Transfer" +msgstr "Transferentzia" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,field_description:stock_picking_batch_collect.field_stock_picking_batch_summary_line__product_uom_id +msgid "Unit of Measure" +msgstr "Neurri-unitatea" + +#. module: stock_picking_batch_collect +#: model:ir.model.fields,help:stock_picking_batch_collect.field_stock_move_line__home_delivery +msgid "Whether this picking includes home delivery (from sale order)" +msgstr "" +"Albaran honek etxez etxeko entrega barne hartzen duen (salmenta-eskaeratik)" + +#. module: stock_picking_batch_collect +#: model_terms:ir.ui.view,arch_db:stock_picking_batch_collect.view_stock_picking_batch_form_inherit_summary +msgid "Basket Assembly" +msgstr "Saskien Prestaketa" diff --git a/stock_picking_batch_custom/models/__init__.py b/stock_picking_batch_collect/models/__init__.py similarity index 100% rename from stock_picking_batch_custom/models/__init__.py rename to stock_picking_batch_collect/models/__init__.py index 8be5195..5b85812 100644 --- a/stock_picking_batch_custom/models/__init__.py +++ b/stock_picking_batch_collect/models/__init__.py @@ -1,6 +1,6 @@ from . import res_company # noqa: F401 from . import res_config_settings # noqa: F401 -from . import stock_move_line # noqa: F401 from . import stock_backorder_confirmation # noqa: F401 +from . import stock_move_line # noqa: F401 from . import stock_picking # noqa: F401 from . import stock_picking_batch # noqa: F401 diff --git a/stock_picking_batch_custom/models/res_company.py b/stock_picking_batch_collect/models/res_company.py similarity index 100% rename from stock_picking_batch_custom/models/res_company.py rename to stock_picking_batch_collect/models/res_company.py diff --git a/stock_picking_batch_custom/models/res_config_settings.py b/stock_picking_batch_collect/models/res_config_settings.py similarity index 100% rename from stock_picking_batch_custom/models/res_config_settings.py rename to stock_picking_batch_collect/models/res_config_settings.py diff --git a/stock_picking_batch_custom/models/stock_backorder_confirmation.py b/stock_picking_batch_collect/models/stock_backorder_confirmation.py similarity index 100% rename from stock_picking_batch_custom/models/stock_backorder_confirmation.py rename to stock_picking_batch_collect/models/stock_backorder_confirmation.py diff --git a/stock_picking_batch_custom/models/stock_move_line.py b/stock_picking_batch_collect/models/stock_move_line.py similarity index 57% rename from stock_picking_batch_custom/models/stock_move_line.py rename to stock_picking_batch_collect/models/stock_move_line.py index bc75ed5..c87320a 100644 --- a/stock_picking_batch_custom/models/stock_move_line.py +++ b/stock_picking_batch_collect/models/stock_move_line.py @@ -1,7 +1,6 @@ # Copyright 2026 Criptomart # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api from odoo import fields from odoo import models @@ -36,22 +35,17 @@ class StockMoveLine(models.Model): copy=False, ) - home_delivery = fields.Boolean( - related="picking_id.home_delivery", - readonly=True, - ) - - consumer_group_id = fields.Many2one( - comodel_name="res.partner", - compute="_compute_consumer_group_id", - readonly=True, - ) - - @api.depends("picking_id") - def _compute_consumer_group_id(self): - for line in self: - picking = line.picking_id - if picking: - line.consumer_group_id = picking.batch_consumer_group_id - else: - line.consumer_group_id = False + def write(self, vals): + res = super().write(vals) + if vals.get("is_collected"): + # Collecting a line is the operator saying the goods are in the + # basket: its quantity must survive the reservation engine even if + # it was never retyped. The demand is left alone, only a quantity + # typed by hand adjusts it. + lines = self.filtered( + lambda line: not line.picked and line.state not in ("done", "cancel") + ) + if lines: + lines.picked = True + lines._freeze_manual_quantity(align_demand=False) + return res diff --git a/stock_picking_batch_custom/models/stock_picking.py b/stock_picking_batch_collect/models/stock_picking.py similarity index 68% rename from stock_picking_batch_custom/models/stock_picking.py rename to stock_picking_batch_collect/models/stock_picking.py index 2e94ffa..1cb06ef 100644 --- a/stock_picking_batch_custom/models/stock_picking.py +++ b/stock_picking_batch_collect/models/stock_picking.py @@ -1,28 +1,12 @@ # Copyright 2026 Criptomart # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields from odoo import models class StockPicking(models.Model): _inherit = "stock.picking" - batch_consumer_group_id = fields.Many2one( - comodel_name="res.partner", - compute="_compute_batch_consumer_group_id", - readonly=True, - string="Batch Consumer Group", - ) - - def _compute_batch_consumer_group_id(self): - for picking in self: - sale = picking.sale_id if "sale_id" in picking._fields else False - if sale and "consumer_group_id" in sale._fields: - picking.batch_consumer_group_id = sale.consumer_group_id - else: - picking.batch_consumer_group_id = False - def _pre_action_done_hook(self): """Run collected checks only after Odoo resolves backorders. diff --git a/stock_picking_batch_custom/models/stock_picking_batch.py b/stock_picking_batch_collect/models/stock_picking_batch.py similarity index 99% rename from stock_picking_batch_custom/models/stock_picking_batch.py rename to stock_picking_batch_collect/models/stock_picking_batch.py index 6c7a409..553796a 100644 --- a/stock_picking_batch_custom/models/stock_picking_batch.py +++ b/stock_picking_batch_collect/models/stock_picking_batch.py @@ -231,7 +231,7 @@ class StockPickingBatch(models.Model): "views": [ ( self.env.ref( - "stock_picking_batch_custom.view_move_line_batch_operator" + "stock_picking_batch_collect.view_move_line_batch_operator" ).id, "list", ) diff --git a/stock_picking_batch_collect/readme/CONFIGURE.rst b/stock_picking_batch_collect/readme/CONFIGURE.rst new file mode 100644 index 0000000..bda97fc --- /dev/null +++ b/stock_picking_batch_collect/readme/CONFIGURE.rst @@ -0,0 +1,32 @@ +Configuración +============= + +Restricciones de validación +--------------------------- + +En **Inventory > Configuration > Settings**, bloque *Operations*, hay dos +ajustes por compañía: + +- **Restricciones del resumen de productos**: exige que las líneas de la + pestaña *Product Summary* estén marcadas como recogidas para validar el lote. + Desactivado por defecto (el check es sólo informativo). +- **Restricciones de operaciones detalladas**: exige que las líneas de la + pestaña *Detailed Operations* estén marcadas como recogidas. + **Activado por defecto**: recién instalado el módulo, un lote no se puede + validar hasta marcar las líneas. + +Cada restricción tiene un alcance: + +- *Sólo procesadas*: se comprueban únicamente las líneas con cantidad hecha + mayor que cero (por defecto). +- *Todas*: se comprueban todas las líneas del lote, procesadas o no. + +Columnas +-------- + +Las columnas están visibles por defecto. Para ajustarlas: + +- Abrir un **Lote de picking**. +- Ir a la pestaña **Detailed Operations**. +- Abrir el **selector de columnas** y activar o desactivar *Partner*, + *Product Category*, *Product Code* o *Collected* según necesidad. diff --git a/stock_picking_batch_custom/readme/CONTRIBUTORS.rst b/stock_picking_batch_collect/readme/CONTRIBUTORS.rst similarity index 100% rename from stock_picking_batch_custom/readme/CONTRIBUTORS.rst rename to stock_picking_batch_collect/readme/CONTRIBUTORS.rst diff --git a/stock_picking_batch_custom/readme/CREDITS.rst b/stock_picking_batch_collect/readme/CREDITS.rst similarity index 100% rename from stock_picking_batch_custom/readme/CREDITS.rst rename to stock_picking_batch_collect/readme/CREDITS.rst diff --git a/stock_picking_batch_collect/readme/DESCRIPTION.rst b/stock_picking_batch_collect/readme/DESCRIPTION.rst new file mode 100644 index 0000000..dc348e7 --- /dev/null +++ b/stock_picking_batch_collect/readme/DESCRIPTION.rst @@ -0,0 +1,26 @@ +Este módulo convierte un lote de picking en una hoja de trabajo para el operario +que monta las cestas: amplía las operaciones detalladas, añade un resumen por +producto y una vista a pantalla completa para el almacén. + +- ``picking_partner_id`` (Partner del albarán) para que el personal de almacén + identifique rápido el cliente/proveedor. +- ``product_categ_id`` (Categoría de producto) para ordenar y agrupar. Las + líneas del lote se ordenan por categoría, producto y partner. +- ``product_default_code`` (Referencia interna) como columna opcional. +- ``is_collected`` (Recogido) como check manual en cada línea para marcar si se + ha recolectado. +- Nueva pestaña **Product Summary** con totales por producto (demandado, hecho, + pendiente), categoría y el check de recogido consolidado. +- Botón **Basket Assembly**: una vista de lista a pantalla completa, con + cantidades grandes y controles táctiles, pensada para tablet en el almacén. +- Restricciones de validación configurables por compañía: el lote no se puede + validar hasta que las líneas estén marcadas como recogidas. + +Las columnas se añaden con ``optional="show"`` en la vista de líneas del lote +(salvo ``product_default_code``, oculta por defecto), de modo que el usuario +puede desactivarlas desde el selector de columnas sin recargar la vista. + +Si además está instalado ``website_sale_aplicoop``, ese módulo añade a estas +mismas vistas las columnas de grupo de consumo (**Consumer Group** y **Home +Delivery**). Este módulo no depende de él: funciona igual sin la parte de +eCommerce. diff --git a/stock_picking_batch_custom/readme/INSTALL.rst b/stock_picking_batch_collect/readme/INSTALL.rst similarity index 80% rename from stock_picking_batch_custom/readme/INSTALL.rst rename to stock_picking_batch_collect/readme/INSTALL.rst index b259091..05042a7 100644 --- a/stock_picking_batch_custom/readme/INSTALL.rst +++ b/stock_picking_batch_collect/readme/INSTALL.rst @@ -5,4 +5,4 @@ Actualizar o instalar el módulo: :: - docker-compose run --rm odoo odoo -d odoo --stop-after-init -u stock_picking_batch_custom + docker-compose run --rm odoo odoo -d odoo --stop-after-init -u stock_picking_batch_collect diff --git a/stock_picking_batch_collect/readme/USAGE.rst b/stock_picking_batch_collect/readme/USAGE.rst new file mode 100644 index 0000000..73eea25 --- /dev/null +++ b/stock_picking_batch_collect/readme/USAGE.rst @@ -0,0 +1,29 @@ +Uso +=== + +1. Accede a **Inventory > Operations > Batch Transfers** y abre un lote. +2. Pestaña **Detailed Operations**: usa el selector de columnas para ajustar: + + - **Partner** (``picking_partner_id``) para ver el cliente/proveedor. + - **Product Category** (``product_categ_id``) para ordenar/agrupación por categoría. + - **Collected** (``is_collected``) para marcar manualmente líneas recolectadas. + +3. Pestaña **Product Summary**: consulta los totales por producto (demandado, + hecho y pendiente) y marca el check de recogido consolidado si corresponde. + +4. Con el lote **en progreso**, el botón **Basket Assembly** abre la vista de + operario: la misma lista de líneas a pantalla completa, con la cantidad en + grande y el check *Collected* como interruptor táctil. Está pensada para + trabajar desde una tablet mientras se montan las cestas. + +5. Ordena o agrupa por categoría en cualquiera de las vistas según convenga. + +6. La cantidad que teclea el operario (el peso real de la balanza) queda fijada: + se marca como *Picked* y ni el planificador ni la validación de otros + albaranes vuelven a modificarla. Marcar **Collected** protege igualmente la + cantidad de la línea. Ver ``stock_move_manual_quantity``. + +7. Al validar el lote, según la configuración de la compañía, se comprueba que + las líneas estén recogidas y se avisa con la lista de productos pendientes. + La comprobación se ejecuta después de resolver el backorder, de modo que + sólo bloquea sobre las líneas que realmente se van a procesar. diff --git a/stock_picking_batch_custom/security/ir.model.access.csv b/stock_picking_batch_collect/security/ir.model.access.csv similarity index 100% rename from stock_picking_batch_custom/security/ir.model.access.csv rename to stock_picking_batch_collect/security/ir.model.access.csv diff --git a/stock_picking_batch_custom/static/description/icon.png b/stock_picking_batch_collect/static/description/icon.png similarity index 100% rename from stock_picking_batch_custom/static/description/icon.png rename to stock_picking_batch_collect/static/description/icon.png diff --git a/stock_picking_batch_custom/static/src/css/stock_picking_batch.css b/stock_picking_batch_collect/static/src/css/stock_picking_batch.css similarity index 82% rename from stock_picking_batch_custom/static/src/css/stock_picking_batch.css rename to stock_picking_batch_collect/static/src/css/stock_picking_batch.css index eada54e..549a93a 100644 --- a/stock_picking_batch_custom/static/src/css/stock_picking_batch.css +++ b/stock_picking_batch_collect/static/src/css/stock_picking_batch.css @@ -1,13 +1,3 @@ -/* zebra striping for list views in this module (Odoo 18 selectors) */ - -table.o_list_table tbody tr.o_data_row:nth-child(even) td { - background-color: rgba(0, 0, 0, 0.03); -} - -table.o_list_table tbody tr.o_data_row:hover td { - background-color: rgba(0, 0, 0, 0.045); -} - /* Widen the quantity column in detailed operations so it is easier to tap */ /* Note: arch class lands on the root
of the list controller, not */ .o_batch_move_line_list th[data-name="quantity"], diff --git a/stock_picking_batch_collect/tests/__init__.py b/stock_picking_batch_collect/tests/__init__.py new file mode 100644 index 0000000..a586ed8 --- /dev/null +++ b/stock_picking_batch_collect/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_batch_summary # noqa: F401 +from . import test_collected_picked # noqa: F401 diff --git a/stock_picking_batch_custom/tests/test_batch_summary.py b/stock_picking_batch_collect/tests/test_batch_summary.py similarity index 100% rename from stock_picking_batch_custom/tests/test_batch_summary.py rename to stock_picking_batch_collect/tests/test_batch_summary.py diff --git a/stock_picking_batch_collect/tests/test_collected_picked.py b/stock_picking_batch_collect/tests/test_collected_picked.py new file mode 100644 index 0000000..c90176d --- /dev/null +++ b/stock_picking_batch_collect/tests/test_collected_picked.py @@ -0,0 +1,73 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.tests import TransactionCase +from odoo.tests import tagged + + +@tagged("-at_install", "post_install") +class TestCollectedPicked(TransactionCase): + """Collecting a line protects its quantity from the reservation engine.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stock_location = cls.env.ref("stock.stock_location_stock") + cls.customer_location = cls.env.ref("stock.stock_location_customers") + cls.picking_type = cls.env.ref("stock.picking_type_out") + cls.product = cls.env["product.product"].create( + { + "name": "Collected Apples", + "is_storable": True, + "uom_id": cls.env.ref("uom.product_uom_kgm").id, + "uom_po_id": cls.env.ref("uom.product_uom_kgm").id, + } + ) + cls.env["stock.quant"]._update_available_quantity( + cls.product, cls.stock_location, 50.0 + ) + + def _create_picking(self, demand): + picking = self.env["stock.picking"].create( + { + "picking_type_id": self.picking_type.id, + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + "move_ids": [ + Command.create( + { + "name": self.product.name, + "product_id": self.product.id, + "product_uom": self.product.uom_id.id, + "product_uom_qty": demand, + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + } + ) + ], + } + ) + picking.action_confirm() + picking.action_assign() + return picking + + def test_collecting_a_line_marks_it_picked(self): + picking = self._create_picking(2.0) + line = picking.move_ids.move_line_ids + + line.is_collected = True + + self.assertTrue(line.picked) + self.assertTrue(picking.move_ids.picked) + + def test_collecting_a_line_keeps_its_demand(self): + """Only a hand-typed quantity lowers the demand, collecting does not.""" + picking = self._create_picking(2.0) + move = picking.move_ids + move.move_line_ids.write({"quantity": 1.5, "picked": True}) + + move.move_line_ids.is_collected = True + + self.assertEqual(move.product_uom_qty, 1.5) + self.assertTrue(move.picked) diff --git a/stock_picking_batch_custom/views/res_config_settings_views.xml b/stock_picking_batch_collect/views/res_config_settings_views.xml similarity index 100% rename from stock_picking_batch_custom/views/res_config_settings_views.xml rename to stock_picking_batch_collect/views/res_config_settings_views.xml diff --git a/stock_picking_batch_custom/views/stock_picking_batch_views.xml b/stock_picking_batch_collect/views/stock_picking_batch_views.xml similarity index 83% rename from stock_picking_batch_custom/views/stock_picking_batch_views.xml rename to stock_picking_batch_collect/views/stock_picking_batch_views.xml index 5379efe..38c8d70 100644 --- a/stock_picking_batch_custom/views/stock_picking_batch_views.xml +++ b/stock_picking_batch_collect/views/stock_picking_batch_views.xml @@ -30,9 +30,9 @@ context="{'display_default_code': False}"/> - - + + @@ -52,23 +52,12 @@ - + - - stock.picking.batch.picking.tree.consumer.group - stock.picking - - - - - - - - stock.move.line.list.batch.custom stock.move.line @@ -87,10 +76,12 @@ - - + + + + hide @@ -109,5 +100,4 @@ - diff --git a/stock_picking_batch_custom/README.rst b/stock_picking_batch_custom/README.rst deleted file mode 100644 index 964a1a7..0000000 --- a/stock_picking_batch_custom/README.rst +++ /dev/null @@ -1,66 +0,0 @@ -============================ -Stock Picking Batch Custom -============================ - -Visión general -============== -Este módulo amplía las operaciones detalladas y añade un resumen por producto -en los lotes de picking: - -- ``picking_partner_id`` (Partner del albarán) para identificar cliente/proveedor. -- ``product_categ_id`` (Categoría de producto) para ordenar y agrupar. -- ``is_collected`` (Recogido) como check manual en cada línea para marcar si se ha - recolectado. -- Nueva pestaña **Product Summary** con totales por producto (demandado, hecho, - pendiente), categoría y el check de recogido consolidado. - -Instalación -=========== - -Actualizar o instalar el módulo: - -:: - - docker-compose run --rm odoo odoo -d odoo --stop-after-init -u stock_picking_batch_custom - -Configuración -============= - -No requiere configuración adicional. Para usar las columnas: - -- Abrir un **Lote de picking**. -- Ir a la pestaña **Detailed Operations**. -- Abrir el **selector de columnas** y activar *Partner*, *Product Category* y *Collected* según necesidad. - -Uso -=== - -1. Accede a **Inventory > Operations > Batch Transfers** y abre un lote. -2. Pestaña **Detailed Operations**: usa el selector de columnas para activar: - - - **Partner** (``picking_partner_id``) para ver el cliente/proveedor. - - **Product Category** (``product_categ_id``) para ordenar/agrupación por categoría. - - **Collected** (``is_collected``) para marcar manualmente líneas recolectadas. - -3. Pestaña **Product Summary**: consulta los totales por producto (demandado, - hecho y pendiente) y marca el check de recogido consolidado si corresponde. - -4. Ordena o agrupa por categoría en cualquiera de las vistas según convenga. - -Contribuidores -============== - -* Criptomart - -Créditos -======== - -Autor ------ - -* Criptomart - -Financiador ------------ - -* Elika Bilbo diff --git a/stock_picking_batch_custom/__init__.py b/stock_picking_batch_custom/__init__.py deleted file mode 100644 index ce9807d..0000000 --- a/stock_picking_batch_custom/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models # noqa: F401 diff --git a/stock_picking_batch_custom/__manifest__.py b/stock_picking_batch_custom/__manifest__.py deleted file mode 100644 index 23d05c5..0000000 --- a/stock_picking_batch_custom/__manifest__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2026 Criptomart -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -{ # noqa: B018 - "name": "Stock Picking Batch Custom", - "version": "18.0.1.0.0", - "category": "Warehouse", - "summary": "Extra columns for batch detailed operations", - "author": "Odoo Community Association (OCA), Criptomart", - "maintainers": ["Criptomart"], - "website": "https://github.com/Criptomart", - "license": "AGPL-3", - "depends": [ - "stock_picking_batch", - # Ensure our related fields to sale/picking (home_delivery, pickup_slot_label) - # are available by depending on the Aplicoop website_sale extension. - "website_sale_aplicoop", - ], - "data": [ - "security/ir.model.access.csv", - "views/res_config_settings_views.xml", - "views/stock_move_line_views.xml", - "views/stock_picking_batch_views.xml", - ], - "assets": { - "web.assets_backend": [ - "stock_picking_batch_custom/static/src/css/stock_picking_batch.css", - ], - }, -} diff --git a/stock_picking_batch_custom/i18n/es.po b/stock_picking_batch_custom/i18n/es.po deleted file mode 100644 index e0a4108..0000000 --- a/stock_picking_batch_custom/i18n/es.po +++ /dev/null @@ -1,235 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * stock_picking_batch_custom -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-21 13:18+0000\n" -"PO-Revision-Date: 2026-05-21 13:18+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"Language: es\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_detailed_restriction_scope__all -msgid "All detailed lines" -msgstr "Todas las líneas detalladas" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_summary_restriction_scope__all -msgid "All summary products" -msgstr "Todos los productos del resumen" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_backorder_confirmation -msgid "Backorder Confirmation" -msgstr "Confirmación de entrega parcial" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__batch_id -msgid "Batch" -msgstr "Lote" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking__batch_consumer_group_id -msgid "Batch Consumer Group" -msgstr "Grupo de consumidores del lote" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Batch Detailed Operations Restriction" -msgstr "Restricción de operaciones detalladas del lote" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_picking_batch_summary_line -msgid "Batch Product Summary Line" -msgstr "Línea de resumen de productos del lote" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Batch Product Summary Restriction" -msgstr "Restricción del resumen de productos del lote" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_picking_batch -msgid "Batch Transfer" -msgstr "Traslado por lote" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__is_collected -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__is_collected -msgid "Collected" -msgstr "Recogido" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_res_company -msgid "Companies" -msgstr "Compañías" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_res_config_settings -msgid "Config Settings" -msgstr "Ajustes de configuración" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Configuración de restricciones para la pestaña Operaciones Detalladas." -msgstr "" -"Configuración de restricciones para la pestaña Operaciones Detalladas." - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Configuración de restricciones para la pestaña Product Summary." -msgstr "Configuración de restricciones para la pestaña Resumen de productos." - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__consumer_group_id -msgid "Consumer Group" -msgstr "Grupo de consumidores" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__create_uid -msgid "Created by" -msgstr "Creado por" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__create_date -msgid "Created on" -msgstr "Creado el" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__qty_demanded -msgid "Demanded Quantity" -msgstr "Cantidad demandada" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_detailed_restriction_scope -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_detailed_restriction_scope -msgid "Detailed Operations Restriction Scope" -msgstr "Ámbito de restricción de operaciones detalladas" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__display_name -msgid "Display Name" -msgstr "Nombre mostrado" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__qty_done -msgid "Done Quantity" -msgstr "Cantidad hecha" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_detailed_restriction_enabled -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_detailed_restriction_enabled -msgid "Enforce Detailed Operations Restriction" -msgstr "Aplicar restricción de operaciones detalladas" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_summary_restriction_enabled -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_summary_restriction_enabled -msgid "Enforce Product Summary Restriction" -msgstr "Aplicar restricción del resumen de productos" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__home_delivery -msgid "Home Delivery" -msgstr "Entrega a domicilio" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__id -msgid "ID" -msgstr "ID" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__write_uid -msgid "Last Updated by" -msgstr "Última actualización por" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__write_date -msgid "Last Updated on" -msgstr "Última actualización el" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_detailed_restriction_scope__processed -msgid "Only processed lines" -msgstr "Solo líneas procesadas" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_summary_restriction_scope__processed -msgid "Only processed products" -msgstr "Solo productos procesados" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__qty_pending -msgid "Pending Quantity" -msgstr "Cantidad pendiente" - -#. module: stock_picking_batch_custom -#. odoo-python -#: code:addons/stock_picking_batch_custom/models/stock_picking_batch.py:0 -msgid "Pending products: %(products)s" -msgstr "Productos pendientes: %(products)s" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__product_id -msgid "Product" -msgstr "Producto" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__product_categ_id -msgid "Product Category" -msgstr "Categoría de producto" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__product_categ_id -msgid "Product Category (Batch)" -msgstr "Categoría de producto (lote)" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_move_line -msgid "Product Moves (Stock Move Line)" -msgstr "Movimientos de producto (línea de movimiento de stock)" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch__summary_line_ids -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.view_stock_picking_batch_form_inherit_summary -msgid "Product Summary" -msgstr "Resumen de productos" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_summary_restriction_scope -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_summary_restriction_scope -msgid "Product Summary Restriction Scope" -msgstr "Ámbito de restricción del resumen de productos" - -#. module: stock_picking_batch_custom -#: model:ir.model.constraint,message:stock_picking_batch_custom.constraint_stock_picking_batch_summary_line_product_required -msgid "Product is required for summary lines." -msgstr "El producto es obligatorio en las líneas de resumen." - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_picking -msgid "Transfer" -msgstr "Traslado" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__product_uom_id -msgid "Unit of Measure" -msgstr "Unidad de medida" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,help:stock_picking_batch_custom.field_stock_move_line__home_delivery -msgid "Whether this picking includes home delivery (from sale order)" -msgstr "Si este albarán incluye entrega a domicilio (del pedido de venta)" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.view_stock_picking_batch_form_inherit_summary -msgid "Basket Assembly" -msgstr "Montaje de Cestas" diff --git a/stock_picking_batch_custom/i18n/eu.po b/stock_picking_batch_custom/i18n/eu.po deleted file mode 100644 index 82b3d93..0000000 --- a/stock_picking_batch_custom/i18n/eu.po +++ /dev/null @@ -1,235 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * stock_picking_batch_custom -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-21 13:21+0000\n" -"PO-Revision-Date: 2026-05-21 13:21+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"Language: eu\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_detailed_restriction_scope__all -msgid "All detailed lines" -msgstr "Lerro xehatu guztiak" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_summary_restriction_scope__all -msgid "All summary products" -msgstr "Laburpeneko produktu guztiak" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_backorder_confirmation -msgid "Backorder Confirmation" -msgstr "Eskaeraren informazioa" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__batch_id -msgid "Batch" -msgstr "Lotea" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking__batch_consumer_group_id -msgid "Batch Consumer Group" -msgstr "Loteko kontsumitzaile taldea" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Batch Detailed Operations Restriction" -msgstr "Loteko eragiketa xehatuen murrizketa" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_picking_batch_summary_line -msgid "Batch Product Summary Line" -msgstr "Loteko produktuen laburpen-lerroa" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Batch Product Summary Restriction" -msgstr "Loteko produktuen laburpenaren murrizketa" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_picking_batch -msgid "Batch Transfer" -msgstr "Lote-transferentzia" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__is_collected -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__is_collected -msgid "Collected" -msgstr "Jasota" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_res_company -msgid "Companies" -msgstr "Enpresak" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_res_config_settings -msgid "Config Settings" -msgstr "Konfigurazio ezarpenak" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Configuración de restricciones para la pestaña Operaciones Detalladas." -msgstr "Eragiketa xehatuak fitxarako murrizketen konfigurazioa." - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.res_config_settings_view_form_inherit_batch_custom -msgid "Configuración de restricciones para la pestaña Product Summary." -msgstr "Produktuen laburpena fitxarako murrizketen konfigurazioa." - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__consumer_group_id -msgid "Consumer Group" -msgstr "Kontsumitzaile taldea" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__create_uid -msgid "Created by" -msgstr "Nork sortua" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__create_date -msgid "Created on" -msgstr "Noiz sortua" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__qty_demanded -msgid "Demanded Quantity" -msgstr "Eskatutako kantitatea" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_detailed_restriction_scope -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_detailed_restriction_scope -msgid "Detailed Operations Restriction Scope" -msgstr "Eragiketa xehatuen murrizketaren irismena" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__display_name -msgid "Display Name" -msgstr "Bistaratzeko izena" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__qty_done -msgid "Done Quantity" -msgstr "Egindako kantitatea" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_detailed_restriction_enabled -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_detailed_restriction_enabled -msgid "Enforce Detailed Operations Restriction" -msgstr "Eragiketa xehatuen murrizketa aplikatu" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_summary_restriction_enabled -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_summary_restriction_enabled -msgid "Enforce Product Summary Restriction" -msgstr "Produktuen laburpenaren murrizketa aplikatu" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__home_delivery -msgid "Home Delivery" -msgstr "Etxez etxeko entrega" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__id -msgid "ID" -msgstr "ID" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__write_uid -msgid "Last Updated by" -msgstr "Azken eguneratzailea" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__write_date -msgid "Last Updated on" -msgstr "Azken eguneratzea" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_detailed_restriction_scope__processed -msgid "Only processed lines" -msgstr "Prozesatutako lerroak soilik" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields.selection,name:stock_picking_batch_custom.selection__res_company__batch_summary_restriction_scope__processed -msgid "Only processed products" -msgstr "Prozesatutako produktuak soilik" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__qty_pending -msgid "Pending Quantity" -msgstr "Zain dagoen kantitatea" - -#. module: stock_picking_batch_custom -#. odoo-python -#: code:addons/stock_picking_batch_custom/models/stock_picking_batch.py:0 -msgid "Pending products: %(products)s" -msgstr "Zain dauden produktuak: %(products)s" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__product_id -msgid "Product" -msgstr "Produktua" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__product_categ_id -msgid "Product Category" -msgstr "Produktu-kategoria" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_move_line__product_categ_id -msgid "Product Category (Batch)" -msgstr "Produktu-kategoria (lotea)" - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_move_line -msgid "Product Moves (Stock Move Line)" -msgstr "Produktuen mugimenduak (stock mugimenduaren lerroa)" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch__summary_line_ids -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.view_stock_picking_batch_form_inherit_summary -msgid "Product Summary" -msgstr "Produktuen laburpena" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_company__batch_summary_restriction_scope -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_res_config_settings__batch_summary_restriction_scope -msgid "Product Summary Restriction Scope" -msgstr "Produktuen laburpenaren murrizketaren irismena" - -#. module: stock_picking_batch_custom -#: model:ir.model.constraint,message:stock_picking_batch_custom.constraint_stock_picking_batch_summary_line_product_required -msgid "Product is required for summary lines." -msgstr "Produktua derrigorrezkoa da laburpen-lerroetan." - -#. module: stock_picking_batch_custom -#: model:ir.model,name:stock_picking_batch_custom.model_stock_picking -msgid "Transfer" -msgstr "Transferentzia" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,field_description:stock_picking_batch_custom.field_stock_picking_batch_summary_line__product_uom_id -msgid "Unit of Measure" -msgstr "Neurri-unitatea" - -#. module: stock_picking_batch_custom -#: model:ir.model.fields,help:stock_picking_batch_custom.field_stock_move_line__home_delivery -msgid "Whether this picking includes home delivery (from sale order)" -msgstr "" -"Albaran honek etxez etxeko entrega barne hartzen duen (salmenta-eskaeratik)" - -#. module: stock_picking_batch_custom -#: model_terms:ir.ui.view,arch_db:stock_picking_batch_custom.view_stock_picking_batch_form_inherit_summary -msgid "Basket Assembly" -msgstr "Saskien Prestaketa" diff --git a/stock_picking_batch_custom/readme/CONFIGURE.rst b/stock_picking_batch_custom/readme/CONFIGURE.rst deleted file mode 100644 index 2213143..0000000 --- a/stock_picking_batch_custom/readme/CONFIGURE.rst +++ /dev/null @@ -1,8 +0,0 @@ -Configuración -============= - -No requiere configuración adicional. Para usar las columnas: - -- Abrir un **Lote de picking**. -- Ir a la pestaña **Detailed Operations**. -- Abrir el **selector de columnas** y activar *Partner* y *Product Category* según necesidad. diff --git a/stock_picking_batch_custom/readme/DESCRIPTION.rst b/stock_picking_batch_custom/readme/DESCRIPTION.rst deleted file mode 100644 index da6edad..0000000 --- a/stock_picking_batch_custom/readme/DESCRIPTION.rst +++ /dev/null @@ -1,14 +0,0 @@ -Este módulo amplía las operaciones detalladas y añade un resumen por producto -en los lotes de picking: - -- ``picking_partner_id`` (Partner del albarán) para que el personal de almacén - identifique rápido el cliente/proveedor. -- ``product_categ_id`` (Categoría de producto) para ordenar y agrupar. -- ``is_collected`` (Recogido) como check manual en cada línea para marcar si se - ha recolectado. -- Nueva pestaña **Product Summary** con totales por producto (demandado, hecho, - pendiente), categoría y el check de recogido consolidado. - -Las columnas se añaden como ``optional="hide"`` en la vista de líneas del lote, -de modo que el usuario puede activarlas desde el selector de columnas sin -recargar la vista por defecto. diff --git a/stock_picking_batch_custom/readme/USAGE.rst b/stock_picking_batch_custom/readme/USAGE.rst deleted file mode 100644 index 89f41e8..0000000 --- a/stock_picking_batch_custom/readme/USAGE.rst +++ /dev/null @@ -1,14 +0,0 @@ -Uso -=== - -1. Accede a **Inventory > Operations > Batch Transfers** y abre un lote. -2. Pestaña **Detailed Operations**: usa el selector de columnas para activar: - - - **Partner** (``picking_partner_id``) para ver el cliente/proveedor. - - **Product Category** (``product_categ_id``) para ordenar/agrupación por categoría. - - **Collected** (``is_collected``) para marcar manualmente líneas recolectadas. - -3. Pestaña **Product Summary**: consulta los totales por producto (demandado, - hecho y pendiente) y marca el check de recogido consolidado si corresponde. - -4. Ordena o agrupa por categoría en cualquiera de las vistas según convenga. diff --git a/stock_picking_batch_custom/tests/__init__.py b/stock_picking_batch_custom/tests/__init__.py deleted file mode 100644 index 161ba0b..0000000 --- a/stock_picking_batch_custom/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import test_batch_summary # noqa: F401 diff --git a/stock_picking_batch_custom/views/stock_move_line_views.xml b/stock_picking_batch_custom/views/stock_move_line_views.xml deleted file mode 100644 index 7a294d3..0000000 --- a/stock_picking_batch_custom/views/stock_move_line_views.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - stock.move.line.list.batch.custom - stock.move.line - - - - hide - - - hide - - - hide - - - hide - - - hide - - - - - - - - - - - - - - - diff --git a/web_list_striped/README.rst b/web_list_striped/README.rst new file mode 100644 index 0000000..d07f1bf --- /dev/null +++ b/web_list_striped/README.rst @@ -0,0 +1,63 @@ +================ +Web List Striped +================ + +Visión general +============== + +Aplica un efecto cebra (filas alternas sombreadas) y un resaltado de hover más +suave a **todas** las vistas de lista del backend de Odoo. + +El alcance global es intencionado: es justamente lo que hace este módulo. Sirve +para listas largas de almacén, contabilidad o inventario, donde seguir una fila +con la vista es más fácil si las filas alternan de color. + +Es un módulo de sólo CSS: no define modelos, campos ni vistas. Para volver al +aspecto estándar de Odoo basta con desinstalarlo. + +Nota técnica: el selector usa ``:nth-child(even)``, que cuenta todas las ``tr`` +del ``tbody`` y no sólo las filas de datos. En listas agrupadas o con fila de +*"Añadir línea"* la paridad de las bandas puede desajustarse. +``:nth-child(even of .o_data_row)`` lo resolvería, pero exige Chrome 111+ o +Firefox 113+, lo que no es una suposición segura en tablets de almacén. + +Instalación +=========== + +Instalar el módulo: + +:: + + docker-compose run --rm odoo odoo -d odoo --stop-after-init -i web_list_striped + +No tiene más dependencias que ``web``, así que se puede instalar y desinstalar +en cualquier momento sin afectar a datos. + +Configuración +============= + +No requiere configuración. El efecto se aplica al instalar el módulo y +desaparece al desinstalarlo. + +Para cambiar la intensidad de las bandas, edita los valores ``rgba()`` de +``static/src/css/web_list_striped.css``. + +Uso +=== + +No hay nada que usar: una vez instalado, cualquier vista de lista del backend +(Contactos, Facturas, Albaranes, Operaciones detalladas de un lote...) muestra +las filas alternas sombreadas y un hover más suave. + +Contribuidores +============== + +* Criptomart + +Créditos +======== + +Autor +----- + +* Criptomart diff --git a/web_list_striped/__init__.py b/web_list_striped/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_list_striped/__manifest__.py b/web_list_striped/__manifest__.py new file mode 100644 index 0000000..678c1cf --- /dev/null +++ b/web_list_striped/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ # noqa: B018 + "name": "Web List Striped", + "version": "18.0.1.0.0", + "category": "Technical", + "summary": "Zebra striping and softer hover for all backend list views", + "author": "Odoo Community Association (OCA), Criptomart", + "maintainers": ["Criptomart"], + "website": "https://git.criptomart.net/criptomart/addons-cm", + "license": "AGPL-3", + "depends": ["web"], + "assets": { + "web.assets_backend": [ + "web_list_striped/static/src/css/web_list_striped.css", + ], + }, +} diff --git a/web_list_striped/readme/CONFIGURE.rst b/web_list_striped/readme/CONFIGURE.rst new file mode 100644 index 0000000..3e6d6d3 --- /dev/null +++ b/web_list_striped/readme/CONFIGURE.rst @@ -0,0 +1,8 @@ +Configuración +============= + +No requiere configuración. El efecto se aplica al instalar el módulo y +desaparece al desinstalarlo. + +Para cambiar la intensidad de las bandas, edita los valores ``rgba()`` de +``static/src/css/web_list_striped.css``. diff --git a/web_list_striped/readme/CONTRIBUTORS.rst b/web_list_striped/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..46076fe --- /dev/null +++ b/web_list_striped/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +Contribuidores +============== + +* Criptomart diff --git a/web_list_striped/readme/CREDITS.rst b/web_list_striped/readme/CREDITS.rst new file mode 100644 index 0000000..0aef29c --- /dev/null +++ b/web_list_striped/readme/CREDITS.rst @@ -0,0 +1,7 @@ +Créditos +======== + +Autor +----- + +* Criptomart diff --git a/web_list_striped/readme/DESCRIPTION.rst b/web_list_striped/readme/DESCRIPTION.rst new file mode 100644 index 0000000..f46dad4 --- /dev/null +++ b/web_list_striped/readme/DESCRIPTION.rst @@ -0,0 +1,15 @@ +Aplica un efecto cebra (filas alternas sombreadas) y un resaltado de hover más +suave a **todas** las vistas de lista del backend de Odoo. + +El alcance global es intencionado: es justamente lo que hace este módulo. Sirve +para listas largas de almacén, contabilidad o inventario, donde seguir una fila +con la vista es más fácil si las filas alternan de color. + +Es un módulo de sólo CSS: no define modelos, campos ni vistas. Para volver al +aspecto estándar de Odoo basta con desinstalarlo. + +Nota técnica: el selector usa ``:nth-child(even)``, que cuenta todas las ``tr`` +del ``tbody`` y no sólo las filas de datos. En listas agrupadas o con fila de +*"Añadir línea"* la paridad de las bandas puede desajustarse. +``:nth-child(even of .o_data_row)`` lo resolvería, pero exige Chrome 111+ o +Firefox 113+, lo que no es una suposición segura en tablets de almacén. diff --git a/web_list_striped/readme/INSTALL.rst b/web_list_striped/readme/INSTALL.rst new file mode 100644 index 0000000..3d083d0 --- /dev/null +++ b/web_list_striped/readme/INSTALL.rst @@ -0,0 +1,11 @@ +Instalación +=========== + +Instalar el módulo: + +:: + + docker-compose run --rm odoo odoo -d odoo --stop-after-init -i web_list_striped + +No tiene más dependencias que ``web``, así que se puede instalar y desinstalar +en cualquier momento sin afectar a datos. diff --git a/web_list_striped/readme/USAGE.rst b/web_list_striped/readme/USAGE.rst new file mode 100644 index 0000000..c169599 --- /dev/null +++ b/web_list_striped/readme/USAGE.rst @@ -0,0 +1,6 @@ +Uso +=== + +No hay nada que usar: una vez instalado, cualquier vista de lista del backend +(Contactos, Facturas, Albaranes, Operaciones detalladas de un lote...) muestra +las filas alternas sombreadas y un hover más suave. diff --git a/web_list_striped/static/src/css/web_list_striped.css b/web_list_striped/static/src/css/web_list_striped.css new file mode 100644 index 0000000..f0ddb82 --- /dev/null +++ b/web_list_striped/static/src/css/web_list_striped.css @@ -0,0 +1,18 @@ +/* Zebra striping for every backend list view. + * + * The reach is global on purpose: that is what this addon is for. Uninstall it + * to get the stock Odoo look back. + * + * Note: `:nth-child(even)` counts every `tr` in the `tbody`, not only the data + * rows, so parity can desync in grouped lists or lists carrying an "Add a line" + * row. `:nth-child(even of .o_data_row)` fixes that but needs Chrome 111+ / + * Firefox 113+, which is not a safe assumption for the warehouse tablets. + */ + +table.o_list_table tbody tr.o_data_row:nth-child(even) td { + background-color: rgba(0, 0, 0, 0.03); +} + +table.o_list_table tbody tr.o_data_row:hover td { + background-color: rgba(0, 0, 0, 0.045); +} diff --git a/website_sale_aplicoop/README_DEV.md b/website_sale_aplicoop/README_DEV.md index 32c5c62..efbb2dc 100644 --- a/website_sale_aplicoop/README_DEV.md +++ b/website_sale_aplicoop/README_DEV.md @@ -135,22 +135,36 @@ La página inicial renderiza la primera página server-side. El botón "Cargar m | Cron | Frecuencia | Acción | | ---- | ---------- | ------ | -| `_cron_confirm_group_orders` | Diario (medianoche) | Confirma sale.orders pasado el cutoff; crea lotes de picking agrupados por consumer_group + pickup_date | +| `_cron_confirm_group_orders` | Diario (medianoche) | Confirma sale.orders pasado el cutoff; crea lotes de picking por pedido de grupo, uno por tipo de operación (`_create_picking_batches`) | | `_cron_update_dates` | Diario | Recalcula `delivery_date` y `pickup_date` en órdenes activas | ## Dependencias +Según `__manifest__.py`: + ```text website_sale_aplicoop ├── website_sale (Odoo core) + ├── website_sale_stock (Odoo core) + ├── payment (Odoo core) + ├── product (Odoo core) ├── sale (Odoo core) - ├── product_main_seller (OCA) - ├── product_sale_price_from_pricelist (custom) - │ └── product_pricelist_total_margin (custom) - │ └── product_price_category (OCA) - └── stock_picking_batch_custom (custom) + ├── stock (Odoo core) + ├── account (Odoo core) + ├── stock_picking_batch (Odoo core) + ├── stock_picking_batch_collect (custom) + │ ├── stock_move_manual_quantity (custom) + │ └── stock_picking_batch (Odoo core) + ├── product_get_price_helper (OCA) + └── l10n_es_partner (OCA) ``` +La dependencia sobre `stock_picking_batch_collect` es deliberada y va en este +sentido: ese addon es genérico de almacén y no sabe nada de grupos de consumo. +Es `website_sale_aplicoop` quien inyecta en sus vistas las columnas +**Consumer Group** y **Home Delivery** (ver `views/stock_picking_batch_views.xml` +y `models/stock_move_line_extension.py`). + ## Instalación y actualización ```bash diff --git a/website_sale_aplicoop/__manifest__.py b/website_sale_aplicoop/__manifest__.py index e908de8..51ab842 100644 --- a/website_sale_aplicoop/__manifest__.py +++ b/website_sale_aplicoop/__manifest__.py @@ -3,7 +3,7 @@ { # noqa: B018 "name": "Website Sale - Aplicoop", - "version": "18.0.1.16.0", + "version": "18.0.1.17.0", "category": "Website/Sale", "summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders", "author": "Odoo Community Association (OCA), Criptomart", @@ -18,6 +18,9 @@ "sale", "stock", "stock_picking_batch", + # Owns the batch collect views into which we inject the consumer group + # columns (home_delivery, consumer_group_id). + "stock_picking_batch_collect", "account", "product_get_price_helper", "l10n_es_partner", @@ -43,6 +46,7 @@ "views/product_template_views.xml", "views/sale_order_views.xml", "views/stock_picking_views.xml", + "views/stock_picking_batch_views.xml", "views/portal_templates.xml", "views/load_from_history_templates.xml", ], diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py index 5254423..f0a55a6 100644 --- a/website_sale_aplicoop/controllers/website_sale.py +++ b/website_sale_aplicoop/controllers/website_sale.py @@ -890,13 +890,6 @@ class AplicoopWebsiteSale(WebsiteSale): product_display_info, filtered_products_dict, ) = self._prepare_products_maps(products, pricelist) - # Whether the tags row needs to be reserved on every card: if nothing in - # this batch has a published tag, the row is skipped entirely instead of - # leaving an empty aria-hidden gap on every product (see product-card.css). - any_product_has_tags = any( - v["published_tags"] for v in filtered_products_dict.values() - ) - # Inject draft sale demand context so ribbons/is_out_of_stock use forecasted net qty products, product_max_qty = self._prepare_draft_stock_data(products) @@ -941,7 +934,6 @@ class AplicoopWebsiteSale(WebsiteSale): "placed_order_url": placed_order_url, "products": products, "filtered_product_tags": filtered_products_dict, - "any_product_has_tags": any_product_has_tags, "cart": cart, "available_categories": available_categories, "category_hierarchy": category_hierarchy, @@ -1053,11 +1045,6 @@ class AplicoopWebsiteSale(WebsiteSale): "product": product, "published_tags": published_tags, } - # See eskaera_shop for why this decides whether to reserve the tags row. - any_product_has_tags = any( - v["published_tags"] for v in filtered_products_dict.values() - ) - product_display_info = {} for product in products_page: product_display_info[product.id] = self._prepare_product_display_info( @@ -1075,7 +1062,6 @@ class AplicoopWebsiteSale(WebsiteSale): "group_order": group_order, "products": products_page, "filtered_product_tags": filtered_products_dict, - "any_product_has_tags": any_product_has_tags, "product_supplier_info": product_supplier_info, "product_price_info": product_price_info, "product_display_info": product_display_info, @@ -1185,11 +1171,6 @@ class AplicoopWebsiteSale(WebsiteSale): } for product in products_page } - # See eskaera_shop for why this decides whether to reserve the tags row. - any_product_has_tags = any( - v["published_tags"] for v in filtered_products_dict.values() - ) - # Inject draft demand context for ribbons/stock flags products_page, product_max_qty = self._prepare_draft_stock_data(products_page) @@ -1203,7 +1184,6 @@ class AplicoopWebsiteSale(WebsiteSale): "group_order": group_order, "products": products_page, "filtered_product_tags": filtered_products_dict, - "any_product_has_tags": any_product_has_tags, "product_supplier_info": product_supplier_info, "product_price_info": product_price_info, "product_display_info": product_display_info, diff --git a/website_sale_aplicoop/models/__init__.py b/website_sale_aplicoop/models/__init__.py index 23d4133..37fdb5c 100644 --- a/website_sale_aplicoop/models/__init__.py +++ b/website_sale_aplicoop/models/__init__.py @@ -1,11 +1,12 @@ from . import group_order # noqa: F401 from . import group_order_slot # noqa: F401 +from . import js_translations # noqa: F401 from . import payment_transaction # noqa: F401 from . import product_category_extension # noqa: F401 from . import product_extension # noqa: F401 from . import res_config_settings # noqa: F401 from . import res_partner_extension # noqa: F401 from . import sale_order_extension # noqa: F401 +from . import stock_move_line_extension # noqa: F401 from . import stock_picking_extension # noqa: F401 from . import website # noqa: F401 -from . import js_translations # noqa: F401 diff --git a/website_sale_aplicoop/models/stock_move_line_extension.py b/website_sale_aplicoop/models/stock_move_line_extension.py new file mode 100644 index 0000000..17ac67a --- /dev/null +++ b/website_sale_aplicoop/models/stock_move_line_extension.py @@ -0,0 +1,28 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields +from odoo import models + + +class StockMoveLine(models.Model): + """Surface the consumer group data on the batch operation lines. + + The picking already carries these values as stored related fields, so the + move line only has to hop one step further. Chaining off ``picking_id`` + instead of re-deriving from ``sale_id`` keeps a single source of truth. + """ + + _inherit = "stock.move.line" + + home_delivery = fields.Boolean( + related="picking_id.home_delivery", + string="Home Delivery", + readonly=True, + ) + + consumer_group_id = fields.Many2one( + related="picking_id.consumer_group_id", + string="Consumer Group", + readonly=True, + ) diff --git a/website_sale_aplicoop/static/src/css/base/variables.css b/website_sale_aplicoop/static/src/css/base/variables.css index 4073997..009f0ce 100644 --- a/website_sale_aplicoop/static/src/css/base/variables.css +++ b/website_sale_aplicoop/static/src/css/base/variables.css @@ -152,7 +152,6 @@ /* ========== PRODUCT CARD MEDIA ========== */ --ac-card-media-ratio: 4 / 3; - --ac-card-media-height: clamp(5rem, 4rem + 6vw, 8.5rem); /* ========== Z-INDEX ========== */ --ac-z-dropdown: 1000; diff --git a/website_sale_aplicoop/static/src/css/components/product-card.css b/website_sale_aplicoop/static/src/css/components/product-card.css index 852270b..6c648aa 100644 --- a/website_sale_aplicoop/static/src/css/components/product-card.css +++ b/website_sale_aplicoop/static/src/css/components/product-card.css @@ -6,13 +6,23 @@ * Markup (eskaera_shop_products): * .product-card-wrapper.product-card * └ .card - * ├ img.product-img-cover | .product-img-placeholder - * ├ .card-body (title, tags, supplier, origin, price) + * ├ .product-media + * │ ├ img.product-img-cover | .product-img-placeholder + * │ └ button.product-info-toggle (only when the product has a + * │ tag, a supplier or an origin to show) + * ├ .card-body (title, .product-info-panel, price) * └ form.add-to-cart-form → components/quantity-control.css * * The wrapper carries the chrome (border, radius, elevation); the inner * Bootstrap .card is neutralised through its own custom properties so the two * boxes stop drawing a double frame. + * + * Tags/supplier/origin used to sit unconditionally in the card body so every + * card's price row lined up regardless of which fields a product had. The + * compact grid has no room for that: they now live in .product-info-panel, + * collapsed by default and revealed by .product-info-toggle (see + * website_sale.js for the click handler, delegated on #products-grid same as + * the quantity stepper). */ .product-card { @@ -41,14 +51,23 @@ /* ---------- Media ---------- */ +.product-media { + position: relative; + flex-shrink: 0; +} + .product-card .product-image, .product-img-cover, .product-img-fixed, .product-img-placeholder { display: block; - flex-shrink: 0; width: 100%; - height: var(--ac-card-media-height); + /* 4:3, not square: a square photo ate too much vertical space on a + two-up mobile grid. aspect-ratio keeps it fluid across card widths + instead of a fixed height that over- or under-crops depending on how + many columns fit. */ + aspect-ratio: var(--ac-card-media-ratio); + height: auto; border-radius: var(--ac-radius-md) var(--ac-radius-md) 0 0; object-fit: cover; background-color: var(--ac-surface-muted); @@ -61,6 +80,68 @@ color: var(--ac-text-muted); } +/* ---------- Info toggle & panel ---------- */ + +.product-info-toggle { + position: absolute; + top: var(--ac-space-2xs); + right: var(--ac-space-2xs); + display: flex; + align-items: center; + justify-content: center; + width: 1.375rem; + height: 1.375rem; + padding: 0; + border: 1px solid var(--ac-border-strong); + border-radius: var(--ac-radius-pill); + font-size: var(--ac-text-2xs); + color: var(--ac-text-secondary); + background-color: rgb(255 255 255 / 90%); + cursor: pointer; + transition: background-color var(--ac-transition-fast), color var(--ac-transition-fast); +} + +.product-info-toggle[aria-expanded="true"] { + color: var(--ac-text-on-fill); + background-color: var(--ac-color-primary-strong); + border-color: var(--ac-color-primary-strong); +} + +.product-info-panel { + display: flex; + flex-direction: column; + gap: var(--ac-space-3xs); + margin-bottom: var(--ac-space-3xs); + padding: var(--ac-space-2xs); + border: 1px solid var(--ac-border); + border-radius: var(--ac-radius-sm); + background-color: var(--ac-surface-sunken); +} + +.product-info-panel[hidden] { + display: none; +} + +@media (hover: hover) and (pointer: fine) { + .product-info-toggle:hover { + color: var(--ac-text-on-fill); + background-color: var(--ac-color-primary); + border-color: var(--ac-color-primary); + } +} + +.product-info-toggle:focus-visible { + outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color); + outline-offset: var(--ac-focus-ring-offset); +} + +@media (pointer: coarse) { + .product-info-toggle { + width: var(--ac-touch-target); + height: var(--ac-touch-target); + } +} + /* ---------- Body ---------- */ .product-card .card-body { @@ -107,10 +188,6 @@ align-content: center; gap: var(--ac-space-3xs); justify-content: center; - /* Reserved even when a product has no tags (rendered empty, aria-hidden — - see eskaera_shop_products), so the supplier/origin/price rows below line - up across every card in the grid row. Matches one row of .badge-km. */ - min-height: calc(var(--ac-text-2xs) * var(--ac-leading-tight) + 2 * var(--ac-space-3xs) + 2px); margin: 0; padding: 0; } @@ -131,14 +208,12 @@ /* ---------- Supplier & origin ---------- */ -/* Both rows are reserved even when empty (rendered aria-hidden — see - eskaera_shop_products) and clamped to a fixed line count, so the price row - below lines up across every card in the grid row regardless of which - products actually have a supplier name or an origin. */ +/* Only ever rendered inside .product-info-panel now (see + eskaera_shop_products), so there is no longer a cross-card alignment to + protect — no reserved space, no forced line clamp. */ .product-card .product-supplier, .product-card .product-origin { margin: 0; - overflow: hidden; font-size: var(--ac-text-xs); font-weight: var(--ac-weight-normal); text-align: center; @@ -146,17 +221,11 @@ } .product-card .product-supplier { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - line-clamp: 2; - min-height: calc(2em * var(--ac-leading-tight)); overflow-wrap: break-word; } .product-card .product-origin { - min-height: calc(1em * var(--ac-leading-tight)); - white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; } diff --git a/website_sale_aplicoop/static/src/css/sections/products-grid.css b/website_sale_aplicoop/static/src/css/sections/products-grid.css index 5151ca3..d994649 100644 --- a/website_sale_aplicoop/static/src/css/sections/products-grid.css +++ b/website_sale_aplicoop/static/src/css/sections/products-grid.css @@ -5,17 +5,18 @@ * * The grid used to pin an exact column count at seven breakpoints (1 → 6). It * now derives the count from the space available, which keeps the card width - * stable no matter how wide the sidebar or the container is. The single query - * left is the deliberate design decision: one card per row on phones, where a - * two-up grid would shrink names and controls below comfortable size. + * stable no matter how wide the sidebar or the container is. * - * 9rem is the card's minimum width; at the widest container (1320px minus the - * category sidebar) it still resolves to six columns, as before. + * Two columns from the smallest phones: with the compact "cromo" card (4:3 + * photo, no inline tags/supplier/origin — see product-card.css) a single + * full-width column read as a tall row rather than a browsable grid. 9rem is + * the card's minimum width from 576px up; at the widest container (1320px + * minus the category sidebar) it still resolves to six columns, as before. */ .products-grid { display: grid; - grid-template-columns: 1fr; + grid-template-columns: repeat(2, 1fr); gap: var(--ac-space-md); margin-bottom: var(--ac-space-xl); } diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js index 6926f18..10eb073 100644 --- a/website_sale_aplicoop/static/src/js/website_sale.js +++ b/website_sale_aplicoop/static/src/js/website_sale.js @@ -1246,6 +1246,23 @@ } }); + // Product info toggle: shows/hides the origin/supplier/tags panel + // that the compact card no longer displays inline (via event + // delegation, same reasoning as the stepper above). + productsGrid.addEventListener("click", function (e) { + var infoBtn = e.target.closest(".product-info-toggle"); + if (!infoBtn) return; + + e.preventDefault(); + var panelId = infoBtn.getAttribute("aria-controls"); + var panel = panelId ? document.getElementById(panelId) : null; + if (!panel) return; + + var isOpen = infoBtn.getAttribute("aria-expanded") === "true"; + infoBtn.setAttribute("aria-expanded", isOpen ? "false" : "true"); + panel.hidden = isOpen; + }); + // Add to cart button (via event delegation on grid) productsGrid.addEventListener("click", handleAddToCart); diff --git a/website_sale_aplicoop/tests/test_multi_company.py b/website_sale_aplicoop/tests/test_multi_company.py index a1f9d68..bffd074 100644 --- a/website_sale_aplicoop/tests/test_multi_company.py +++ b/website_sale_aplicoop/tests/test_multi_company.py @@ -14,39 +14,16 @@ class TestMultiCompanyGroupOrder(TransactionCase): def setUp(self): super().setUp() - # Crear dos compañías + # Crear dos compañías. `stock_picking_batch_collect` es dependencia del + # módulo, así que los campos de restricción de lote siempre existen. company_model = self.env["res.company"] - # Compatibilidad con esquemas legacy: columna NOT NULL presente sin campo ORM. - self.env.cr.execute(""" - SELECT column_name - FROM information_schema.columns - WHERE table_name = 'res_company' - AND column_name IN ('batch_summary_restriction_scope', 'batch_detailed_restriction_scope') - """) - existing_columns = {row[0] for row in self.env.cr.fetchall()} - if ( - "batch_summary_restriction_scope" in existing_columns - and "batch_summary_restriction_scope" not in company_model._fields - ): - self.env.cr.execute( - "ALTER TABLE res_company ALTER COLUMN batch_summary_restriction_scope SET DEFAULT 'processed'" - ) - if ( - "batch_detailed_restriction_scope" in existing_columns - and "batch_detailed_restriction_scope" not in company_model._fields - ): - self.env.cr.execute( - "ALTER TABLE res_company ALTER COLUMN batch_detailed_restriction_scope SET DEFAULT 'processed'" - ) - def _company_vals(name): - vals = {"name": name} - if "batch_summary_restriction_scope" in company_model._fields: - vals["batch_summary_restriction_scope"] = "processed" - if "batch_detailed_restriction_scope" in company_model._fields: - vals["batch_detailed_restriction_scope"] = "processed" - return vals + return { + "name": name, + "batch_summary_restriction_scope": "processed", + "batch_detailed_restriction_scope": "processed", + } self.company1 = company_model.create(_company_vals("Company 1")) self.company2 = company_model.create(_company_vals("Company 2")) diff --git a/website_sale_aplicoop/tests/test_record_rules.py b/website_sale_aplicoop/tests/test_record_rules.py index 99604ee..55bef5c 100644 --- a/website_sale_aplicoop/tests/test_record_rules.py +++ b/website_sale_aplicoop/tests/test_record_rules.py @@ -14,39 +14,16 @@ class TestGroupOrderRecordRules(TransactionCase): def setUp(self): super().setUp() - # Crear dos compañías + # Crear dos compañías. `stock_picking_batch_collect` es dependencia del + # módulo, así que los campos de restricción de lote siempre existen. company_model = self.env["res.company"] - # Compatibilidad con esquemas legacy: columna NOT NULL presente sin campo ORM. - self.env.cr.execute(""" - SELECT column_name - FROM information_schema.columns - WHERE table_name = 'res_company' - AND column_name IN ('batch_summary_restriction_scope', 'batch_detailed_restriction_scope') - """) - existing_columns = {row[0] for row in self.env.cr.fetchall()} - if ( - "batch_summary_restriction_scope" in existing_columns - and "batch_summary_restriction_scope" not in company_model._fields - ): - self.env.cr.execute( - "ALTER TABLE res_company ALTER COLUMN batch_summary_restriction_scope SET DEFAULT 'processed'" - ) - if ( - "batch_detailed_restriction_scope" in existing_columns - and "batch_detailed_restriction_scope" not in company_model._fields - ): - self.env.cr.execute( - "ALTER TABLE res_company ALTER COLUMN batch_detailed_restriction_scope SET DEFAULT 'processed'" - ) - def _company_vals(name): - vals = {"name": name} - if "batch_summary_restriction_scope" in company_model._fields: - vals["batch_summary_restriction_scope"] = "processed" - if "batch_detailed_restriction_scope" in company_model._fields: - vals["batch_detailed_restriction_scope"] = "processed" - return vals + return { + "name": name, + "batch_summary_restriction_scope": "processed", + "batch_detailed_restriction_scope": "processed", + } self.company1 = company_model.create(_company_vals("Company 1")) self.company2 = company_model.create(_company_vals("Company 2")) diff --git a/website_sale_aplicoop/views/stock_picking_batch_views.xml b/website_sale_aplicoop/views/stock_picking_batch_views.xml new file mode 100644 index 0000000..d5f2a7f --- /dev/null +++ b/website_sale_aplicoop/views/stock_picking_batch_views.xml @@ -0,0 +1,40 @@ + + + + + stock.move.line.batch.operator.consumer.group + stock.move.line + + + + + + + + + + + + stock.move.line.list.batch.consumer.group + stock.move.line + + + + + + + + + + + + stock.picking.batch.picking.tree.consumer.group + stock.picking + + + + + + + + diff --git a/website_sale_aplicoop/views/website_templates.xml b/website_sale_aplicoop/views/website_templates.xml index e92e9de..0b89f4a 100644 --- a/website_sale_aplicoop/views/website_templates.xml +++ b/website_sale_aplicoop/views/website_templates.xml @@ -274,56 +274,15 @@ -
-
-
-
- - - -
-

-

-
- -
-
- -
-
-
- - - - - - - - - -
-
-
-
-
-
-
+ +
+
My Cart
@@ -362,7 +321,49 @@
-
+ +
+
+ + + + + + + + + +
+
+
+
+ +
+
+
+ + + +
+

+

+
@@ -763,16 +764,31 @@