From d433e50f2fb7a63a0b93848df11d60c3320e072c Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 27 Aug 2026 13:26:44 +0200 Subject: [PATCH 1/5] [ADD] stock_move_manual_quantity: a hand-typed quantity is the picked one An operator weighing a basket types 0.87 kg where the member ordered 1 kg, and a few hours later the line is back at 1 kg. Raising the quantity never reverts. The asymmetry is the reservation engine: below the demand the move goes back to `partially_available` (`_recompute_state`), which is exactly the state `_action_assign` looks for, while above it stays `assigned` and matches no domain. Two things call `_action_assign` on their own: the `Procurement: run scheduler` cron, and `_trigger_assign`, which runs every time another transfer of the same product is validated -- the second explains the "a few hours" better than a daily cron. Both respect one flag only, `picked`, and nobody was setting it: the checkbox is `optional="hide"` on the transfer form and absent from the detailed operation lists. So an onchange on `quantity` marks the record as picked. Onchanges only run from the interface, never from the reservation engine, so a manual edit is told apart from a reservation without guessing at contexts, and if the onchange ever stops firing the behaviour degrades to today's instead of freezing reservations across the system. `picked` also keeps `_free_reservation` from stealing the quantity when stock runs short elsewhere. The three lists where a quantity can be typed now load the field, since the client only sends back what the arch declares. When the typed quantity is below the demand, the demand follows it down. The move stays `assigned` and no backorder is asked for a weight that will never be completed. Only downwards: above the demand nothing needs adjusting. A line left at zero keeps its demand, so a transfer still offers its usual backorder choice for what was not delivered, and chained moves are only frozen -- lowering their demand would leave the next step of the route asking for more than this one delivers. `_pre_action_done_hook` needs the counterpart. Core auto-picks a transfer only when *no* move is picked yet, so freezing one weighed line would push the untouched ones to a backorder, or cancel them if the user answers "no backorder", with the goods already in the basket. Picking everything that carries a quantity keeps validation exactly as operators know it. Co-Authored-By: Claude Opus 5 --- stock_move_manual_quantity/__init__.py | 1 + stock_move_manual_quantity/__manifest__.py | 16 ++ stock_move_manual_quantity/models/__init__.py | 3 + .../models/stock_move.py | 60 ++++++ .../models/stock_move_line.py | 50 +++++ .../models/stock_picking.py | 31 +++ .../readme/CONFIGURE.rst | 2 + .../readme/CONTRIBUTORS.rst | 3 + stock_move_manual_quantity/readme/CREDITS.rst | 3 + .../readme/DESCRIPTION.rst | 22 +++ stock_move_manual_quantity/readme/INSTALL.rst | 8 + stock_move_manual_quantity/readme/USAGE.rst | 23 +++ stock_move_manual_quantity/tests/__init__.py | 1 + .../tests/test_manual_quantity.py | 184 ++++++++++++++++++ .../views/stock_move_line_views.xml | 29 +++ 15 files changed, 436 insertions(+) create mode 100644 stock_move_manual_quantity/__init__.py create mode 100644 stock_move_manual_quantity/__manifest__.py create mode 100644 stock_move_manual_quantity/models/__init__.py create mode 100644 stock_move_manual_quantity/models/stock_move.py create mode 100644 stock_move_manual_quantity/models/stock_move_line.py create mode 100644 stock_move_manual_quantity/models/stock_picking.py create mode 100644 stock_move_manual_quantity/readme/CONFIGURE.rst create mode 100644 stock_move_manual_quantity/readme/CONTRIBUTORS.rst create mode 100644 stock_move_manual_quantity/readme/CREDITS.rst create mode 100644 stock_move_manual_quantity/readme/DESCRIPTION.rst create mode 100644 stock_move_manual_quantity/readme/INSTALL.rst create mode 100644 stock_move_manual_quantity/readme/USAGE.rst create mode 100644 stock_move_manual_quantity/tests/__init__.py create mode 100644 stock_move_manual_quantity/tests/test_manual_quantity.py create mode 100644 stock_move_manual_quantity/views/stock_move_line_views.xml 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 + + + + + + + + From 2e7549f857ba019030e4147bf5c709f5ba0396ad Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 27 Aug 2026 13:26:56 +0200 Subject: [PATCH 2/5] [IMP] stock_picking_batch_custom: keep the weighed quantity on the operator view Basket Assembly is where the reverting quantity was noticed, so it depends on stock_move_manual_quantity now and its two lists load `picked`: the web client only saves the fields present in the arch, and without it the onchange that freezes a hand-typed quantity never reaches the server. Collecting a line does the same, minus the demand. It is the operator saying the goods are in the basket, so the quantity has to survive the reservation engine even when it was never retyped -- but only a quantity typed by hand says what the demand should become, and a line collected at zero would otherwise lose its demand and be cancelled on validation. Drops views/stock_move_line_views.xml on the way. It declared a second record under the id stock_picking_batch_views.xml already uses, so it was loaded first and immediately overwritten: dead weight that would have turned into a duplicate-field view had anyone renamed it. Co-Authored-By: Claude Opus 5 --- stock_picking_batch_custom/__manifest__.py | 4 +- .../models/stock_move_line.py | 15 ++++ stock_picking_batch_custom/readme/USAGE.rst | 5 ++ stock_picking_batch_custom/tests/__init__.py | 1 + .../tests/test_collected_picked.py | 73 +++++++++++++++++++ .../views/stock_move_line_views.xml | 36 --------- .../views/stock_picking_batch_views.xml | 6 ++ 7 files changed, 103 insertions(+), 37 deletions(-) create mode 100644 stock_picking_batch_custom/tests/test_collected_picked.py delete mode 100644 stock_picking_batch_custom/views/stock_move_line_views.xml diff --git a/stock_picking_batch_custom/__manifest__.py b/stock_picking_batch_custom/__manifest__.py index 23d05c5..7ad2a39 100644 --- a/stock_picking_batch_custom/__manifest__.py +++ b/stock_picking_batch_custom/__manifest__.py @@ -10,6 +10,9 @@ "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", # Ensure our related fields to sale/picking (home_delivery, pickup_slot_label) # are available by depending on the Aplicoop website_sale extension. @@ -18,7 +21,6 @@ "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": { diff --git a/stock_picking_batch_custom/models/stock_move_line.py b/stock_picking_batch_custom/models/stock_move_line.py index bc75ed5..03f575e 100644 --- a/stock_picking_batch_custom/models/stock_move_line.py +++ b/stock_picking_batch_custom/models/stock_move_line.py @@ -47,6 +47,21 @@ class StockMoveLine(models.Model): readonly=True, ) + 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 + @api.depends("picking_id") def _compute_consumer_group_id(self): for line in self: diff --git a/stock_picking_batch_custom/readme/USAGE.rst b/stock_picking_batch_custom/readme/USAGE.rst index 89f41e8..5dcdde0 100644 --- a/stock_picking_batch_custom/readme/USAGE.rst +++ b/stock_picking_batch_custom/readme/USAGE.rst @@ -12,3 +12,8 @@ Uso 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. + +5. 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``. diff --git a/stock_picking_batch_custom/tests/__init__.py b/stock_picking_batch_custom/tests/__init__.py index 161ba0b..a586ed8 100644 --- a/stock_picking_batch_custom/tests/__init__.py +++ b/stock_picking_batch_custom/tests/__init__.py @@ -1 +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_collected_picked.py b/stock_picking_batch_custom/tests/test_collected_picked.py new file mode 100644 index 0000000..c90176d --- /dev/null +++ b/stock_picking_batch_custom/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/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/stock_picking_batch_custom/views/stock_picking_batch_views.xml b/stock_picking_batch_custom/views/stock_picking_batch_views.xml index 5379efe..0300795 100644 --- a/stock_picking_batch_custom/views/stock_picking_batch_views.xml +++ b/stock_picking_batch_custom/views/stock_picking_batch_views.xml @@ -33,6 +33,8 @@ + + @@ -91,6 +93,10 @@ + + + + hide From 704f0def1b3a101e2c1d90c255a71506c77e2821 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 27 Aug 2026 14:26:28 +0200 Subject: [PATCH 3/5] [ADD] web_list_striped: zebra striping for all backend list views The zebra rows and the softer hover used to be shipped by stock_picking_batch_custom, but its selector (`table.o_list_table`) carried no module scope, so a warehouse addon was quietly restyling every list view in the database. The scope was lost in 07bd632, while porting the selectors from Odoo 16 to 18. The effect itself is wanted, so keep it and make it explicit: an asset-only addon whose documented purpose is the global reach. Nothing depends on it, so the striping can be turned off by uninstalling a single module. An Odoo theme would not work here: `theme_*` addons only govern the website frontend, and these rules live in `web.assets_backend`. Co-Authored-By: Claude Opus 5 --- web_list_striped/README.rst | 63 +++++++++++++++++++ web_list_striped/__init__.py | 0 web_list_striped/__manifest__.py | 18 ++++++ web_list_striped/readme/CONFIGURE.rst | 8 +++ web_list_striped/readme/CONTRIBUTORS.rst | 4 ++ web_list_striped/readme/CREDITS.rst | 7 +++ web_list_striped/readme/DESCRIPTION.rst | 15 +++++ web_list_striped/readme/INSTALL.rst | 11 ++++ web_list_striped/readme/USAGE.rst | 6 ++ .../static/src/css/web_list_striped.css | 18 ++++++ 10 files changed, 150 insertions(+) create mode 100644 web_list_striped/README.rst create mode 100644 web_list_striped/__init__.py create mode 100644 web_list_striped/__manifest__.py create mode 100644 web_list_striped/readme/CONFIGURE.rst create mode 100644 web_list_striped/readme/CONTRIBUTORS.rst create mode 100644 web_list_striped/readme/CREDITS.rst create mode 100644 web_list_striped/readme/DESCRIPTION.rst create mode 100644 web_list_striped/readme/INSTALL.rst create mode 100644 web_list_striped/readme/USAGE.rst create mode 100644 web_list_striped/static/src/css/web_list_striped.css 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); +} From ef1283be7cac795847e776a2fc5353fe6e173b3d Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 27 Aug 2026 14:26:47 +0200 Subject: [PATCH 4/5] [REF] stock_picking_batch_collect: rename and drop the aplicoop dependency "custom" said nothing about what the module does. It is really about collecting goods into baskets: the extra detailed-operation columns, the is_collected flag, the Product Summary tab, the per-company validation restrictions and the Basket Assembly operator view all serve that one job. Rename it accordingly. Invert the dependency while at it. A generic warehouse addon was dragging in an entire eCommerce application, and the whole coupling was a single field: stock.move.line.home_delivery, related to picking_id.home_delivery. Everything else was already duck-typed. website_sale_aplicoop now depends on this module and injects its own consumer group columns into these views. This removes duplicated logic rather than relocating it: stock.picking .batch_consumer_group_id re-derived from sale_id a value aplicoop already stored as stock.picking.consumer_group_id, and the duplicate carried no @api.depends, so it never recomputed reliably. The batch transfers list now shows the stored field, which is sortable and groupable. The two aplicoop tests that probed information_schema for the res_company batch_* columns can drop that guard: a real dependency guarantees them. Renaming an addon is not something a migrations/ script can do, since a renamed addon is a brand new module to Odoo and its migration scripts never run. A pre_init_hook does it instead: it fires on install after the Python is imported but before registry.load(), which is the window where remapping ir_model_data makes Odoo reuse the existing tables and columns. is_collected, the summary line table and the company settings all survive untouched. Two details the hook has to get right: - ir_model_constraint.module and ir_model_relation.module are integer FKs with ON DELETE CASCADE, so they must be repointed before the old module row is deleted or the bookkeeping goes with it. - Deleting an ir_model_data row does not cascade to the record it points at. Artifacts handed over to aplicoop only need the xmlid dropped, but artifacts that disappear need the record deleted too, or the field survives as an orphan manual field and the view as a custom view referencing it. Verified against a restored copy of the dev database: 50 xmlids moved, 19 summary lines and 5 collected move lines preserved, no orphans, both test suites green, and the module installs cleanly on a database without website_sale_aplicoop. Co-Authored-By: Claude Opus 5 --- .github/copilot-instructions.md | 13 +- README.md | 11 +- stock_picking_batch_collect/README.rst | 127 ++++++++++ stock_picking_batch_collect/__init__.py | 2 + .../__manifest__.py | 14 +- stock_picking_batch_collect/hooks.py | 117 +++++++++ stock_picking_batch_collect/i18n/es.po | 235 ++++++++++++++++++ stock_picking_batch_collect/i18n/eu.po | 235 ++++++++++++++++++ .../models/__init__.py | 2 +- .../models/res_company.py | 0 .../models/res_config_settings.py | 0 .../models/stock_backorder_confirmation.py | 0 .../models/stock_move_line.py | 21 -- .../models/stock_picking.py | 16 -- .../models/stock_picking_batch.py | 2 +- .../readme/CONFIGURE.rst | 32 +++ .../readme/CONTRIBUTORS.rst | 0 .../readme/CREDITS.rst | 0 .../readme/DESCRIPTION.rst | 26 ++ .../readme/INSTALL.rst | 2 +- .../readme/USAGE.rst | 16 +- .../security/ir.model.access.csv | 0 .../static/description/icon.png | Bin .../static/src/css/stock_picking_batch.css | 10 - .../tests/__init__.py | 0 .../tests/test_batch_summary.py | 0 .../tests/test_collected_picked.py | 0 .../views/res_config_settings_views.xml | 0 .../views/stock_picking_batch_views.xml | 18 +- stock_picking_batch_custom/README.rst | 66 ----- stock_picking_batch_custom/__init__.py | 1 - stock_picking_batch_custom/i18n/es.po | 235 ------------------ stock_picking_batch_custom/i18n/eu.po | 235 ------------------ .../readme/CONFIGURE.rst | 8 - .../readme/DESCRIPTION.rst | 14 -- website_sale_aplicoop/README_DEV.md | 26 +- website_sale_aplicoop/__manifest__.py | 6 +- website_sale_aplicoop/models/__init__.py | 3 +- .../models/stock_move_line_extension.py | 28 +++ .../tests/test_multi_company.py | 37 +-- .../tests/test_record_rules.py | 37 +-- .../views/stock_picking_batch_views.xml | 40 +++ 42 files changed, 926 insertions(+), 709 deletions(-) create mode 100644 stock_picking_batch_collect/README.rst create mode 100644 stock_picking_batch_collect/__init__.py rename {stock_picking_batch_custom => stock_picking_batch_collect}/__manifest__.py (63%) create mode 100644 stock_picking_batch_collect/hooks.py create mode 100644 stock_picking_batch_collect/i18n/es.po create mode 100644 stock_picking_batch_collect/i18n/eu.po rename {stock_picking_batch_custom => stock_picking_batch_collect}/models/__init__.py (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/models/res_company.py (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/models/res_config_settings.py (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/models/stock_backorder_confirmation.py (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/models/stock_move_line.py (72%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/models/stock_picking.py (68%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/models/stock_picking_batch.py (99%) create mode 100644 stock_picking_batch_collect/readme/CONFIGURE.rst rename {stock_picking_batch_custom => stock_picking_batch_collect}/readme/CONTRIBUTORS.rst (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/readme/CREDITS.rst (100%) create mode 100644 stock_picking_batch_collect/readme/DESCRIPTION.rst rename {stock_picking_batch_custom => stock_picking_batch_collect}/readme/INSTALL.rst (80%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/readme/USAGE.rst (51%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/security/ir.model.access.csv (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/static/description/icon.png (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/static/src/css/stock_picking_batch.css (82%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/tests/__init__.py (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/tests/test_batch_summary.py (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/tests/test_collected_picked.py (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/views/res_config_settings_views.xml (100%) rename {stock_picking_batch_custom => stock_picking_batch_collect}/views/stock_picking_batch_views.xml (83%) delete mode 100644 stock_picking_batch_custom/README.rst delete mode 100644 stock_picking_batch_custom/__init__.py delete mode 100644 stock_picking_batch_custom/i18n/es.po delete mode 100644 stock_picking_batch_custom/i18n/eu.po delete mode 100644 stock_picking_batch_custom/readme/CONFIGURE.rst delete mode 100644 stock_picking_batch_custom/readme/DESCRIPTION.rst create mode 100644 website_sale_aplicoop/models/stock_move_line_extension.py create mode 100644 website_sale_aplicoop/views/stock_picking_batch_views.xml 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_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_custom/__manifest__.py b/stock_picking_batch_collect/__manifest__.py similarity index 63% rename from stock_picking_batch_custom/__manifest__.py rename to stock_picking_batch_collect/__manifest__.py index 7ad2a39..7e61eac 100644 --- a/stock_picking_batch_custom/__manifest__.py +++ b/stock_picking_batch_collect/__manifest__.py @@ -1,10 +1,10 @@ # 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", + "name": "Stock Picking Batch Collect", + "version": "18.0.2.0.0", "category": "Warehouse", - "summary": "Extra columns for batch detailed operations", + "summary": "Collect batch operations: operator view, extra columns and product summary", "author": "Odoo Community Association (OCA), Criptomart", "maintainers": ["Criptomart"], "website": "https://github.com/Criptomart", @@ -14,9 +14,6 @@ # re-reserved by the scheduler. "stock_move_manual_quantity", "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", @@ -25,7 +22,10 @@ ], "assets": { "web.assets_backend": [ - "stock_picking_batch_custom/static/src/css/stock_picking_batch.css", + "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 72% rename from stock_picking_batch_custom/models/stock_move_line.py rename to stock_picking_batch_collect/models/stock_move_line.py index 03f575e..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,17 +35,6 @@ 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, - ) - def write(self, vals): res = super().write(vals) if vals.get("is_collected"): @@ -61,12 +49,3 @@ class StockMoveLine(models.Model): lines.picked = True lines._freeze_manual_quantity(align_demand=False) return res - - @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 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_custom/readme/USAGE.rst b/stock_picking_batch_collect/readme/USAGE.rst similarity index 51% rename from stock_picking_batch_custom/readme/USAGE.rst rename to stock_picking_batch_collect/readme/USAGE.rst index 5dcdde0..73eea25 100644 --- a/stock_picking_batch_custom/readme/USAGE.rst +++ b/stock_picking_batch_collect/readme/USAGE.rst @@ -2,7 +2,7 @@ Uso === 1. Accede a **Inventory > Operations > Batch Transfers** y abre un lote. -2. Pestaña **Detailed Operations**: usa el selector de columnas para activar: +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. @@ -11,9 +11,19 @@ Uso 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. +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. La cantidad que teclea el operario (el peso real de la balanza) queda fijada: +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_custom/tests/__init__.py b/stock_picking_batch_collect/tests/__init__.py similarity index 100% rename from stock_picking_batch_custom/tests/__init__.py rename to stock_picking_batch_collect/tests/__init__.py 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_custom/tests/test_collected_picked.py b/stock_picking_batch_collect/tests/test_collected_picked.py similarity index 100% rename from stock_picking_batch_custom/tests/test_collected_picked.py rename to stock_picking_batch_collect/tests/test_collected_picked.py 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 0300795..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,8 +30,6 @@ context="{'display_default_code': False}"/> - - @@ -54,23 +52,12 @@ - + - - stock.picking.batch.picking.tree.consumer.group - stock.picking - - - - - - - - stock.move.line.list.batch.custom stock.move.line @@ -89,8 +76,6 @@ - - @@ -115,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/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/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/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/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 + + + + + + + + From 6a59d9f6ad279beb580a29c5a751268c3b4ef150 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 27 Aug 2026 15:24:34 +0200 Subject: [PATCH 5/5] [IMP] website_sale_aplicoop: compact mobile layout for the Eskaera shop Reorder cart/tags/category/search/products into one flex row with Bootstrap order utilities, so mobile shows cart, tags, category, search, then products, while desktop keeps its current layout. Product cards become a two-column grid of compact tiles from the smallest phones, with a 4:3 photo instead of a tall rectangle. Origin, supplier and tags move behind a per-product "i" toggle instead of always reserving their own row, which also drops the now-unused any_product_has_tags plumbing. Co-Authored-By: Claude Sonnet 5 --- .../controllers/website_sale.py | 20 -- .../static/src/css/base/variables.css | 1 - .../src/css/components/product-card.css | 109 +++++++-- .../static/src/css/sections/products-grid.css | 13 +- .../static/src/js/website_sale.js | 17 ++ .../views/website_templates.xml | 209 +++++++++--------- 6 files changed, 217 insertions(+), 152 deletions(-) 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/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/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 @@