diff --git a/stock_picking_batch_custom/README.rst b/stock_picking_batch_custom/README.rst
index 964a1a7..9893a8f 100644
--- a/stock_picking_batch_custom/README.rst
+++ b/stock_picking_batch_custom/README.rst
@@ -1,66 +1,13 @@
-============================
+===============================
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:
+.. contents::
+ :local:
-- ``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
+.. include:: readme/DESCRIPTION.rst
+.. include:: readme/INSTALL.rst
+.. include:: readme/CONFIGURE.rst
+.. include:: readme/USAGE.rst
+.. include:: readme/CONTRIBUTORS.rst
+.. include:: readme/CREDITS.rst
diff --git a/stock_picking_batch_custom/__manifest__.py b/stock_picking_batch_custom/__manifest__.py
index 27e80bd..4882701 100644
--- a/stock_picking_batch_custom/__manifest__.py
+++ b/stock_picking_batch_custom/__manifest__.py
@@ -13,8 +13,6 @@
"stock_picking_batch",
],
"data": [
- "security/ir.model.access.csv",
"views/stock_move_line_views.xml",
- "views/stock_picking_batch_views.xml",
],
}
diff --git a/stock_picking_batch_custom/models/__init__.py b/stock_picking_batch_custom/models/__init__.py
index 24709d5..5a228fe 100644
--- a/stock_picking_batch_custom/models/__init__.py
+++ b/stock_picking_batch_custom/models/__init__.py
@@ -1,2 +1 @@
from . import stock_move_line # noqa: F401
-from . import stock_picking_batch # noqa: F401
diff --git a/stock_picking_batch_custom/models/stock_move_line.py b/stock_picking_batch_custom/models/stock_move_line.py
index 82bd6d2..cb4ad48 100644
--- a/stock_picking_batch_custom/models/stock_move_line.py
+++ b/stock_picking_batch_custom/models/stock_move_line.py
@@ -10,14 +10,8 @@ class StockMoveLine(models.Model):
product_categ_id = fields.Many2one(
comodel_name="product.category",
- string="Product Category (Batch)",
+ string="Product Category",
related="product_id.categ_id",
store=True,
readonly=True,
)
-
- is_collected = fields.Boolean(
- string="Collected",
- default=False,
- copy=False,
- )
diff --git a/stock_picking_batch_custom/models/stock_picking_batch.py b/stock_picking_batch_custom/models/stock_picking_batch.py
deleted file mode 100644
index c2c77fc..0000000
--- a/stock_picking_batch_custom/models/stock_picking_batch.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# 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
-
-
-class StockPickingBatch(models.Model):
- _inherit = "stock.picking.batch"
-
- summary_line_ids = fields.One2many(
- comodel_name="stock.picking.batch.summary.line",
- inverse_name="batch_id",
- string="Product Summary",
- compute="_compute_summary_line_ids",
- store=True,
- readonly=True,
- )
-
- @api.depends(
- "move_line_ids.move_id.product_id",
- "move_line_ids.move_id.product_uom_qty",
- "move_line_ids.move_id.quantity",
- "move_line_ids.move_id.product_uom",
- "move_line_ids.move_id.state",
- )
- def _compute_summary_line_ids(self):
- """Aggregate move quantities per product and keep collected flag.
-
- - Demand: move.product_uom_qty converted to product UoM.
- - Done: move.quantity converted to product UoM.
- - Pending: demand - done.
- - Keep is_collected value if product already present.
- - Skip cancelled moves.
- """
-
- for batch in self:
- # Base: limpiar si no hay líneas
- if not batch.move_line_ids:
- batch.summary_line_ids = [fields.Command.clear()]
- continue
-
- previous_collected = {
- line.product_id.id: line.is_collected
- for line in batch.summary_line_ids
- if line.product_id
- }
- # Use regular dict to avoid accidental creation of empty entries
- aggregates = {}
-
- # Demand per move (count once per move)
- for move in batch.move_ids:
- if move.state == "cancel" or not move.product_id:
- continue
- product = move.product_id
- product_uom = product.uom_id
- demand = move.product_uom._compute_quantity(
- move.product_uom_qty, product_uom, rounding_method="HALF-UP"
- )
- if product.id not in aggregates:
- aggregates[product.id] = {
- "product_id": product.id,
- "product_uom_id": product_uom.id,
- "qty_demanded": 0.0,
- "qty_done": 0.0,
- }
- aggregates[product.id]["qty_demanded"] += demand
-
- # Done per move line
- for line in batch.move_line_ids:
- move = line.move_id
- if move and move.state == "cancel":
- continue
- product = line.product_id
- if not product:
- continue
- product_uom = product.uom_id
- done = line.product_uom_id._compute_quantity(
- line.quantity, product_uom, rounding_method="HALF-UP"
- )
- if product.id not in aggregates:
- aggregates[product.id] = {
- "product_id": product.id,
- "product_uom_id": product_uom.id,
- "qty_demanded": 0.0,
- "qty_done": 0.0,
- }
- aggregates[product.id]["qty_done"] += done
-
- commands = []
- products_in_totals = set()
- existing_by_product = {
- line.product_id.id: line for line in batch.summary_line_ids
- }
-
- for product_id, values in aggregates.items():
- # Double-check: skip if product_id is not valid
- if not product_id or not values.get("product_id"):
- continue
- products_in_totals.add(product_id)
- existing_line = existing_by_product.get(product_id)
- is_collected = previous_collected.get(product_id, False)
- if existing_line:
- commands.append(
- fields.Command.update(
- existing_line.id,
- {
- "product_uom_id": values["product_uom_id"],
- "qty_demanded": values["qty_demanded"],
- "qty_done": values["qty_done"],
- "is_collected": is_collected,
- },
- )
- )
- else:
- # Ensure all required fields are present before create
- if values["product_id"] and values["product_uom_id"]:
- commands.append(
- fields.Command.create(
- {
- "product_id": values["product_id"],
- "product_uom_id": values["product_uom_id"],
- "qty_demanded": values["qty_demanded"],
- "qty_done": values["qty_done"],
- "is_collected": is_collected,
- }
- )
- )
-
- obsolete_lines = [
- fields.Command.unlink(line.id)
- for line in batch.summary_line_ids
- if line.product_id.id not in products_in_totals
- ]
-
- if commands or obsolete_lines:
- batch.summary_line_ids = commands + obsolete_lines
- else:
- batch.summary_line_ids = [fields.Command.clear()]
-
-
-class StockPickingBatchSummaryLine(models.Model):
- _name = "stock.picking.batch.summary.line"
- _description = "Batch Product Summary Line"
- _order = "product_categ_id, product_id"
-
- _sql_constraints = [
- (
- "product_required",
- "CHECK(product_id IS NOT NULL)",
- "Product is required for summary lines.",
- ),
- ]
-
- batch_id = fields.Many2one(
- comodel_name="stock.picking.batch",
- ondelete="cascade",
- required=True,
- index=True,
- )
- product_id = fields.Many2one(
- comodel_name="product.product",
- required=True,
- index=True,
- )
- product_categ_id = fields.Many2one(
- comodel_name="product.category",
- string="Product Category",
- related="product_id.categ_id",
- store=True,
- readonly=True,
- )
- product_uom_id = fields.Many2one(
- comodel_name="uom.uom",
- string="Unit of Measure",
- required=True,
- )
- qty_demanded = fields.Float(
- string="Demanded Quantity",
- digits="Product Unit of Measure",
- required=True,
- default=0.0,
- )
- qty_done = fields.Float(
- string="Done Quantity",
- digits="Product Unit of Measure",
- required=True,
- default=0.0,
- )
- qty_pending = fields.Float(
- string="Pending Quantity",
- digits="Product Unit of Measure",
- compute="_compute_qty_pending",
- store=True,
- )
- is_collected = fields.Boolean(string="Collected", default=False)
-
- @api.depends("qty_demanded", "qty_done")
- def _compute_qty_pending(self):
- for line in self:
- line.qty_pending = line.qty_demanded - line.qty_done
diff --git a/stock_picking_batch_custom/readme/DESCRIPTION.rst b/stock_picking_batch_custom/readme/DESCRIPTION.rst
index da6edad..d8fc673 100644
--- a/stock_picking_batch_custom/readme/DESCRIPTION.rst
+++ b/stock_picking_batch_custom/readme/DESCRIPTION.rst
@@ -1,14 +1,11 @@
-Este módulo amplía las operaciones detalladas y añade un resumen por producto
-en los lotes de picking:
+Este módulo añade dos columnas opcionales en las operaciones detalladas de 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.
+ pueda identificar rápidamente el cliente/proveedor asociado.
+- ``product_categ_id`` (Categoría de producto) para permitir ordenación y
+ agrupación por categoría.
-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.
+Ambas 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
+cargar la vista por defecto.
diff --git a/stock_picking_batch_custom/readme/USAGE.rst b/stock_picking_batch_custom/readme/USAGE.rst
index 89f41e8..d652a4e 100644
--- a/stock_picking_batch_custom/readme/USAGE.rst
+++ b/stock_picking_batch_custom/readme/USAGE.rst
@@ -6,9 +6,5 @@ Uso
- **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.
+3. Ordena o agrupa por la columna de categoría según convenga.
diff --git a/stock_picking_batch_custom/security/ir.model.access.csv b/stock_picking_batch_custom/security/ir.model.access.csv
deleted file mode 100644
index 57ebabc..0000000
--- a/stock_picking_batch_custom/security/ir.model.access.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_stock_picking_batch_summary_line_user,stock_picking_batch_summary_line_user,model_stock_picking_batch_summary_line,base.group_user,1,1,1,0
diff --git a/stock_picking_batch_custom/tests/__init__.py b/stock_picking_batch_custom/tests/__init__.py
deleted file mode 100644
index 161ba0b..0000000
--- a/stock_picking_batch_custom/tests/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from . import test_batch_summary # noqa: F401
diff --git a/stock_picking_batch_custom/tests/test_batch_summary.py b/stock_picking_batch_custom/tests/test_batch_summary.py
deleted file mode 100644
index e8139e9..0000000
--- a/stock_picking_batch_custom/tests/test_batch_summary.py
+++ /dev/null
@@ -1,289 +0,0 @@
-# Copyright 2026 Criptomart
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo.tests import tagged
-from odoo.tests.common import TransactionCase
-
-
-@tagged("-at_install", "post_install")
-class TestBatchSummary(TransactionCase):
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
- cls.uom_unit = cls.env.ref("uom.product_uom_unit")
- cls.uom_dozen = cls.env.ref("uom.product_uom_dozen")
- cls.location_src = cls.env.ref("stock.stock_location_stock")
- cls.location_dest = 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": "Test Product",
- "uom_id": cls.uom_unit.id,
- "uom_po_id": cls.uom_unit.id,
- }
- )
-
- cls.batch = cls.env["stock.picking.batch"].create(
- {"name": "Batch Test", "picking_type_id": cls.picking_type.id}
- )
-
- cls.picking1 = cls.env["stock.picking"].create(
- {
- "picking_type_id": cls.picking_type.id,
- "location_id": cls.location_src.id,
- "location_dest_id": cls.location_dest.id,
- "batch_id": cls.batch.id,
- }
- )
- cls.picking2 = cls.env["stock.picking"].create(
- {
- "picking_type_id": cls.picking_type.id,
- "location_id": cls.location_src.id,
- "location_dest_id": cls.location_dest.id,
- "batch_id": cls.batch.id,
- }
- )
-
- # Helpers
- def _add_move(self, picking, qty_demanded, qty_done, uom):
- move = self.env["stock.move"].create(
- {
- "name": self.product.name,
- "product_id": self.product.id,
- "product_uom_qty": qty_demanded,
- "product_uom": uom.id,
- "picking_id": picking.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
- if qty_done:
- self.env["stock.move.line"].create(
- {
- "move_id": move.id,
- "picking_id": picking.id,
- "product_id": self.product.id,
- "product_uom_id": self.product.uom_id.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- "quantity": qty_done,
- }
- )
- return move
-
- def _recompute_batch(self):
- self.batch.invalidate_recordset()
- self.batch._compute_summary_line_ids()
-
- def _create_batch_with_pickings(self):
- batch = self.env["stock.picking.batch"].create(
- {"name": "Batch Flow", "picking_type_id": self.picking_type.id}
- )
- picking = self.env["stock.picking"].create(
- {
- "picking_type_id": self.picking_type.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
- move = self.env["stock.move"].create(
- {
- "name": self.product.name,
- "product_id": self.product.id,
- "product_uom_qty": 2.0,
- "product_uom": self.uom_unit.id,
- "picking_id": picking.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
- self.env["stock.move.line"].create(
- {
- "move_id": move.id,
- "picking_id": picking.id,
- "product_id": self.product.id,
- "product_uom_id": self.uom_unit.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- "quantity": 2.0,
- }
- )
- picking.batch_id = batch.id
- return batch
-
- # Tests
- def test_totals_and_pending_with_conversion(self):
- """Totals aggregate per product with UoM conversion and pending."""
-
- # demand 12 units, done 5 units
- self._add_move(self.picking1, qty_demanded=12, qty_done=5, uom=self.uom_unit)
- # demand 2 dozens (24 units), done 6 units
- self._add_move(self.picking2, qty_demanded=2, qty_done=6, uom=self.uom_dozen)
-
- self._recompute_batch()
-
- self.assertEqual(len(self.batch.summary_line_ids), 1)
- line = self.batch.summary_line_ids
- self.assertEqual(line.product_id, self.product)
- self.assertEqual(line.product_uom_id, self.uom_unit)
- self.assertAlmostEqual(line.qty_demanded, 36.0)
- self.assertAlmostEqual(line.qty_done, 11.0)
- self.assertAlmostEqual(line.qty_pending, 25.0)
-
- def test_collected_flag_preserved_on_recompute(self):
- """Collected stays checked after totals change."""
-
- self._add_move(self.picking1, qty_demanded=1, qty_done=1, uom=self.uom_unit)
- self._recompute_batch()
-
- line = self.batch.summary_line_ids
- line.is_collected = True
-
- # Add more demand/done to trigger an update
- self._add_move(self.picking1, qty_demanded=3, qty_done=2, uom=self.uom_unit)
- self._recompute_batch()
-
- self.assertTrue(self.batch.summary_line_ids.is_collected)
-
- def test_cancelled_moves_are_ignored(self):
- """Cancelled moves do not count in the summary and lines are removed."""
-
- move1 = self._add_move(
- self.picking1, qty_demanded=4, qty_done=2, uom=self.uom_unit
- )
- move2 = self._add_move(
- self.picking2, qty_demanded=6, qty_done=3, uom=self.uom_unit
- )
- self._recompute_batch()
- self.assertEqual(len(self.batch.summary_line_ids), 1)
-
- move1.write({"state": "cancel"})
- move2.write({"state": "cancel"})
- self._recompute_batch()
-
- self.assertFalse(self.batch.summary_line_ids)
-
- def test_no_required_product_error_on_confirm(self):
- """Confirming batch with pickings must not create summary lines without product."""
-
- batch = self._create_batch_with_pickings()
-
- # Trigger compute via confirm flow
- batch.action_confirm()
-
- self.assertTrue(batch.summary_line_ids)
- self.assertFalse(
- batch.summary_line_ids.filtered(lambda line: not line.product_id)
- )
-
- def test_empty_batch_add_pickings_then_confirm(self):
- """Create empty draft batch, add multiple pickings, then confirm."""
-
- # 1. Create empty batch in draft state
- batch = self.env["stock.picking.batch"].create(
- {"name": "Batch Empty Start", "picking_type_id": self.picking_type.id}
- )
- self.assertEqual(batch.state, "draft")
- self.assertFalse(batch.picking_ids)
- self.assertFalse(batch.summary_line_ids)
-
- # 2. Create pickings with moves (without batch assignment)
- product2 = self.env["product.product"].create(
- {
- "name": "Test Product 2",
- "uom_id": self.uom_unit.id,
- "uom_po_id": self.uom_unit.id,
- }
- )
-
- picking_a = self.env["stock.picking"].create(
- {
- "picking_type_id": self.picking_type.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
- self.env["stock.move"].create(
- {
- "name": self.product.name,
- "product_id": self.product.id,
- "product_uom_qty": 5.0,
- "product_uom": self.uom_unit.id,
- "picking_id": picking_a.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
-
- picking_b = self.env["stock.picking"].create(
- {
- "picking_type_id": self.picking_type.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
- self.env["stock.move"].create(
- {
- "name": product2.name,
- "product_id": product2.id,
- "product_uom_qty": 10.0,
- "product_uom": self.uom_unit.id,
- "picking_id": picking_b.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
-
- picking_c = self.env["stock.picking"].create(
- {
- "picking_type_id": self.picking_type.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
- self.env["stock.move"].create(
- {
- "name": self.product.name,
- "product_id": self.product.id,
- "product_uom_qty": 3.0,
- "product_uom": self.uom_unit.id,
- "picking_id": picking_c.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
-
- # 3. Add pickings to the batch
- picking_a.batch_id = batch
- picking_b.batch_id = batch
- picking_c.batch_id = batch
-
- self.assertEqual(len(batch.picking_ids), 3)
-
- # 4. Confirm the batch — this should not raise product_id required error
- batch.action_confirm()
-
- self.assertEqual(batch.state, "in_progress")
-
- # 5. Verify summary lines are correct
- self.assertTrue(batch.summary_line_ids)
- self.assertFalse(
- batch.summary_line_ids.filtered(lambda line: not line.product_id)
- )
-
- # Two products expected
- products_in_summary = batch.summary_line_ids.mapped("product_id")
- self.assertIn(self.product, products_in_summary)
- self.assertIn(product2, products_in_summary)
-
- # Check aggregated quantities
- line_product1 = batch.summary_line_ids.filtered(
- lambda line: line.product_id == self.product
- )
- self.assertAlmostEqual(line_product1.qty_demanded, 8.0) # 5 + 3
-
- line_product2 = batch.summary_line_ids.filtered(
- lambda line: line.product_id == product2
- )
- self.assertAlmostEqual(line_product2.qty_demanded, 10.0)
diff --git a/stock_picking_batch_custom/views/stock_move_line_views.xml b/stock_picking_batch_custom/views/stock_move_line_views.xml
index b857a2c..a000a97 100644
--- a/stock_picking_batch_custom/views/stock_move_line_views.xml
+++ b/stock_picking_batch_custom/views/stock_move_line_views.xml
@@ -11,9 +11,6 @@
-
-
-
diff --git a/stock_picking_batch_custom/views/stock_picking_batch_views.xml b/stock_picking_batch_custom/views/stock_picking_batch_views.xml
deleted file mode 100644
index 43b3ae2..0000000
--- a/stock_picking_batch_custom/views/stock_picking_batch_views.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- stock.picking.batch.summary.line.tree
- stock.picking.batch.summary.line
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- stock.picking.batch.form.summary
- stock.picking.batch
-
-
-
-
-
-
-
-
-
-
diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js
index 3f76c3c..b88bbee 100644
--- a/website_sale_aplicoop/static/src/js/website_sale.js
+++ b/website_sale_aplicoop/static/src/js/website_sale.js
@@ -694,70 +694,6 @@
return;
}
- // Shared handler for add-to-cart to reuse across grid/document listeners
- var handleAddToCart = function (e) {
- var cartBtn = e.target.closest(".add-to-cart-btn");
- if (!cartBtn) return;
-
- e.preventDefault();
- var form = cartBtn.closest(".add-to-cart-form");
- if (!form) return;
-
- var productId = form.getAttribute("data-product-id");
- var productName = form.getAttribute("data-product-name") || "Product";
- var productPrice = parseFloat(form.getAttribute("data-product-price")) || 0;
- var quantityInput = form.querySelector(".product-qty");
- var quantity = quantityInput ? parseFloat(quantityInput.value) : 1;
-
- // Block add-to-cart if product is flagged out of stock (from template)
- var isOutOfStock =
- (form.getAttribute("data-out-of-stock") || "false") === "true" ||
- (cartBtn.getAttribute("data-out-of-stock") || "false") === "true";
-
- // Fallback guards in case cached markup drops the data attribute
- if (!isOutOfStock) {
- var btnTitle = (cartBtn.getAttribute("title") || "").toLowerCase();
- var btnAria = (cartBtn.getAttribute("aria-label") || "").toLowerCase();
- var iconEl = cartBtn.querySelector("i");
- var hasBanIcon = iconEl && iconEl.classList.contains("fa-ban");
-
- if (
- hasBanIcon ||
- btnTitle.includes("out of stock") ||
- btnTitle.includes("sin stock") ||
- btnAria.includes("out of stock") ||
- btnAria.includes("sin stock")
- ) {
- isOutOfStock = true;
- }
- }
- if (isOutOfStock) {
- var labels = self._getLabels();
- self._showNotification(
- labels.out_of_stock || "Product is out of stock",
- "warning"
- );
- return;
- }
-
- console.log("Adding:", {
- productId: productId,
- productName: productName,
- productPrice: productPrice,
- quantity: quantity,
- });
-
- if (quantity > 0) {
- self._addToCart(productId, productName, productPrice, quantity);
- } else {
- var labels2 = self._getLabels();
- self._showNotification(
- labels2.invalid_quantity || "Please enter a valid quantity",
- "warning"
- );
- }
- };
-
// First, adjust quantity steps for all existing inputs
var unitInputs = document.querySelectorAll(".product-qty");
console.log("=== ADJUSTING QUANTITY STEPS (from data-quantity-step) ===");
@@ -843,16 +779,67 @@
}
});
- // Add to cart button (via event delegation on grid)
- productsGrid.addEventListener("click", handleAddToCart);
+ // Add to cart button (via event delegation)
+ productsGrid.addEventListener("click", function (e) {
+ var cartBtn = e.target.closest(".add-to-cart-btn");
+ if (!cartBtn) return;
- // Also attach a document-level delegation as fallback for dynamically
- // inserted products (infinite scroll) in case the grid listener is lost
- // after DOM replacement.
- if (!this._docCartListenerAttached) {
- document.addEventListener("click", handleAddToCart, true);
- this._docCartListenerAttached = true;
- }
+ e.preventDefault();
+ var form = cartBtn.closest(".add-to-cart-form");
+ var productId = form.getAttribute("data-product-id");
+ var productName = form.getAttribute("data-product-name") || "Product";
+ var productPrice = parseFloat(form.getAttribute("data-product-price")) || 0;
+ var quantityInput = form.querySelector(".product-qty");
+ var quantity = quantityInput ? parseFloat(quantityInput.value) : 1;
+
+ // Block add-to-cart if product is flagged out of stock (from template)
+ var isOutOfStock =
+ (form.getAttribute("data-out-of-stock") || "false") === "true" ||
+ (cartBtn.getAttribute("data-out-of-stock") || "false") === "true";
+
+ // Fallback guards in case cached markup drops the data attribute
+ if (!isOutOfStock) {
+ var btnTitle = (cartBtn.getAttribute("title") || "").toLowerCase();
+ var btnAria = (cartBtn.getAttribute("aria-label") || "").toLowerCase();
+ var iconEl = cartBtn.querySelector("i");
+ var hasBanIcon = iconEl && iconEl.classList.contains("fa-ban");
+
+ if (
+ hasBanIcon ||
+ btnTitle.includes("out of stock") ||
+ btnTitle.includes("sin stock") ||
+ btnAria.includes("out of stock") ||
+ btnAria.includes("sin stock")
+ ) {
+ isOutOfStock = true;
+ }
+ }
+ if (isOutOfStock) {
+ var labels = self._getLabels();
+ self._showNotification(
+ labels.out_of_stock || "Product is out of stock",
+ "warning"
+ );
+ return;
+ }
+
+ console.log("Adding:", {
+ productId: productId,
+ productName: productName,
+ productPrice: productPrice,
+ quantity: quantity,
+ });
+
+ if (quantity > 0) {
+ self._addToCart(productId, productName, productPrice, quantity);
+ } else {
+ var labels = self._getLabels();
+ self._showNotification(
+ labels.invalid_quantity || "Please enter a valid quantity",
+ "warning"
+ );
+ }
+ });
},
_addToCart: function (productId, productName, productPrice, quantity) {