Compare commits
No commits in common. "6a59d9f6ad279beb580a29c5a751268c3b4ef150" and "cb32fb6c0d00996f0b10297f70fda3e6c63c946d" have entirely different histories.
6a59d9f6ad
...
cb32fb6c0d
77 changed files with 930 additions and 1864 deletions
13
.github/copilot-instructions.md
vendored
13
.github/copilot-instructions.md
vendored
|
|
@ -189,11 +189,9 @@ addons-cm/
|
||||||
├── membership_monthly_invoicing/ # Factura mensual de membresía por socio (cron)
|
├── membership_monthly_invoicing/ # Factura mensual de membresía por socio (cron)
|
||||||
├── membership_expiry_reminder/ # Email recordatorio de renovación próxima
|
├── membership_expiry_reminder/ # Email recordatorio de renovación próxima
|
||||||
├── # --- Logística y contabilidad ---
|
├── # --- Logística y contabilidad ---
|
||||||
├── stock_picking_batch_collect/ # Batch picking: columnas extra + vista operario (Basket Assembly)
|
├── stock_picking_batch_custom/ # Batch picking: columnas extra + vista operario (Basket Assembly)
|
||||||
├── account_banking_mandate_batch/ # Crear/validar mandatos SEPA en bloque desde contactos
|
├── 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)
|
|
||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
|
|
@ -235,8 +233,7 @@ addons-cm/
|
||||||
|
|
||||||
**Logística y contabilidad**
|
**Logística y contabilidad**
|
||||||
|
|
||||||
- [stock_picking_batch_collect](../stock_picking_batch_collect/README.rst) - Columnas extra, resumen por producto y vista operario para batch picking
|
- [stock_picking_batch_custom](../stock_picking_batch_custom/README.rst) - Columnas extra 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
|
- [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)
|
- [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)
|
||||||
|
|
||||||
|
|
@ -761,10 +758,6 @@ 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_pricelist_total_margin`, `product_origin_char`, `l10n_es_edi_tbai_reagyp_recibidas`.
|
||||||
`product_origin` (OCA) sustituido por `product_origin_char` (custom).
|
`product_origin` (OCA) sustituido por `product_origin_char` (custom).
|
||||||
- **2026-06-04**: `product_sale_price_from_pricelist` aplica descuentos de proveedor desde `supplierinfo`.
|
- **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),
|
- **2026-06**: `stock_picking_batch_custom` — vista operario a pantalla completa (Basket Assembly),
|
||||||
ordenación de operaciones por categoría/producto/partner.
|
ordenación de operaciones por categoría/producto/partner.
|
||||||
- **2026-02-18**: Refactor `product_main_seller` - Remover alias innecesario `default_supplier_id`
|
- **2026-02-18**: Refactor `product_main_seller` - Remover alias innecesario `default_supplier_id`
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -27,9 +27,8 @@ 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_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_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 |
|
| [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_collect](stock_picking_batch_collect/) | 18.0.2.0.0 | Montaje de cestas en lotes: columnas extra, resumen por producto y vista operario |
|
| [stock_picking_batch_custom](stock_picking_batch_custom/) | 18.0.1.0.0 | Columnas extra en operaciones detalladas de lotes: partner, categoría, recogido |
|
||||||
| [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.9.0 | Sistema de pedidos colaborativos para grupos de consumo (reemplazo de Aplicoop) |
|
||||||
| [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
|
## Dependencias entre addons custom
|
||||||
|
|
||||||
|
|
@ -41,12 +40,6 @@ website_sale_aplicoop
|
||||||
└── product_main_seller
|
└── product_main_seller
|
||||||
└── product_price_category_supplier
|
└── product_price_category_supplier
|
||||||
└── product_price_category
|
└── 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_readonly
|
||||||
└── account_invoice_triple_discount
|
└── account_invoice_triple_discount
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from . import models
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# 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",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from . import stock_move
|
|
||||||
from . import stock_move_line
|
|
||||||
from . import stock_picking
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
No configuration is required: the behaviour applies to every transfer as soon
|
|
||||||
as the module is installed.
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
* `Criptomart <https://criptomart.net>`_:
|
|
||||||
|
|
||||||
* Analysis of the reservation engine and implementation
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
**Authors:**
|
|
||||||
|
|
||||||
* Criptomart
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from . import test_manual_quantity
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
# 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"])
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<!--
|
|
||||||
The web client only saves the fields present in the arch, so `picked`
|
|
||||||
has to be loaded wherever a quantity can be typed for
|
|
||||||
`_onchange_quantity_manual` to reach the server.
|
|
||||||
-->
|
|
||||||
<record id="view_stock_move_line_operation_tree_manual_quantity" model="ir.ui.view">
|
|
||||||
<field name="name">stock.move.line.operations.manual.quantity</field>
|
|
||||||
<field name="model">stock.move.line</field>
|
|
||||||
<field name="inherit_id" ref="stock.view_stock_move_line_operation_tree"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='quantity']" position="after">
|
|
||||||
<field name="picked" column_invisible="True"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="view_stock_move_line_detailed_operation_tree_manual_quantity" model="ir.ui.view">
|
|
||||||
<field name="name">stock.move.line.detailed.operations.manual.quantity</field>
|
|
||||||
<field name="model">stock.move.line</field>
|
|
||||||
<field name="inherit_id" ref="stock.view_stock_move_line_detailed_operation_tree"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='quantity']" position="after">
|
|
||||||
<field name="picked" column_invisible="True"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</odoo>
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
===========================
|
|
||||||
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
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
from . import models # noqa: F401
|
|
||||||
from .hooks import pre_init_hook # noqa: F401
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# Copyright 2026 Criptomart
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
{ # noqa: B018
|
|
||||||
"name": "Stock Picking Batch Collect",
|
|
||||||
"version": "18.0.2.0.0",
|
|
||||||
"category": "Warehouse",
|
|
||||||
"summary": "Collect batch operations: operator view, extra columns and product summary",
|
|
||||||
"author": "Odoo Community Association (OCA), Criptomart",
|
|
||||||
"maintainers": ["Criptomart"],
|
|
||||||
"website": "https://github.com/Criptomart",
|
|
||||||
"license": "AGPL-3",
|
|
||||||
"depends": [
|
|
||||||
# A quantity typed by the operator is the weighed one: it must not be
|
|
||||||
# re-reserved by the scheduler.
|
|
||||||
"stock_move_manual_quantity",
|
|
||||||
"stock_picking_batch",
|
|
||||||
],
|
|
||||||
"data": [
|
|
||||||
"security/ir.model.access.csv",
|
|
||||||
"views/res_config_settings_views.xml",
|
|
||||||
"views/stock_picking_batch_views.xml",
|
|
||||||
],
|
|
||||||
"assets": {
|
|
||||||
"web.assets_backend": [
|
|
||||||
"stock_picking_batch_collect/static/src/css/stock_picking_batch.css",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
# Adopts the data of the former `stock_picking_batch_custom` when upgrading
|
|
||||||
# a database where that module was installed. See `hooks.py`.
|
|
||||||
"pre_init_hook": "pre_init_hook",
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
# 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,))
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
# 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"
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
# 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"
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
Uso
|
|
||||||
===
|
|
||||||
|
|
||||||
1. Accede a **Inventory > Operations > Batch Transfers** y abre un lote.
|
|
||||||
2. Pestaña **Detailed Operations**: usa el selector de columnas para 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.
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
from . import test_batch_summary # noqa: F401
|
|
||||||
from . import test_collected_picked # noqa: F401
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
# 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)
|
|
||||||
66
stock_picking_batch_custom/README.rst
Normal file
66
stock_picking_batch_custom/README.rst
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
============================
|
||||||
|
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
|
||||||
1
stock_picking_batch_custom/__init__.py
Normal file
1
stock_picking_batch_custom/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models # noqa: F401
|
||||||
29
stock_picking_batch_custom/__manifest__.py
Normal file
29
stock_picking_batch_custom/__manifest__.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Copyright 2026 Criptomart
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
{ # noqa: B018
|
||||||
|
"name": "Stock Picking Batch Custom",
|
||||||
|
"version": "18.0.1.0.0",
|
||||||
|
"category": "Warehouse",
|
||||||
|
"summary": "Extra columns for batch detailed operations",
|
||||||
|
"author": "Odoo Community Association (OCA), Criptomart",
|
||||||
|
"maintainers": ["Criptomart"],
|
||||||
|
"website": "https://github.com/Criptomart",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"depends": [
|
||||||
|
"stock_picking_batch",
|
||||||
|
# Ensure our related fields to sale/picking (home_delivery, pickup_slot_label)
|
||||||
|
# are available by depending on the Aplicoop website_sale extension.
|
||||||
|
"website_sale_aplicoop",
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
"security/ir.model.access.csv",
|
||||||
|
"views/res_config_settings_views.xml",
|
||||||
|
"views/stock_move_line_views.xml",
|
||||||
|
"views/stock_picking_batch_views.xml",
|
||||||
|
],
|
||||||
|
"assets": {
|
||||||
|
"web.assets_backend": [
|
||||||
|
"stock_picking_batch_custom/static/src/css/stock_picking_batch.css",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
235
stock_picking_batch_custom/i18n/es.po
Normal file
235
stock_picking_batch_custom/i18n/es.po
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
# 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"
|
||||||
235
stock_picking_batch_custom/i18n/eu.po
Normal file
235
stock_picking_batch_custom/i18n/eu.po
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
# 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"
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from . import res_company # noqa: F401
|
from . import res_company # noqa: F401
|
||||||
from . import res_config_settings # noqa: F401
|
from . import res_config_settings # noqa: F401
|
||||||
from . import stock_backorder_confirmation # noqa: F401
|
|
||||||
from . import stock_move_line # noqa: F401
|
from . import stock_move_line # noqa: F401
|
||||||
|
from . import stock_backorder_confirmation # noqa: F401
|
||||||
from . import stock_picking # noqa: F401
|
from . import stock_picking # noqa: F401
|
||||||
from . import stock_picking_batch # noqa: F401
|
from . import stock_picking_batch # noqa: F401
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright 2026 Criptomart
|
# Copyright 2026 Criptomart
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import api
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
@ -35,17 +36,22 @@ class StockMoveLine(models.Model):
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def write(self, vals):
|
home_delivery = fields.Boolean(
|
||||||
res = super().write(vals)
|
related="picking_id.home_delivery",
|
||||||
if vals.get("is_collected"):
|
readonly=True,
|
||||||
# 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
|
consumer_group_id = fields.Many2one(
|
||||||
lines._freeze_manual_quantity(align_demand=False)
|
comodel_name="res.partner",
|
||||||
return res
|
compute="_compute_consumer_group_id",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("picking_id")
|
||||||
|
def _compute_consumer_group_id(self):
|
||||||
|
for line in self:
|
||||||
|
picking = line.picking_id
|
||||||
|
if picking:
|
||||||
|
line.consumer_group_id = picking.batch_consumer_group_id
|
||||||
|
else:
|
||||||
|
line.consumer_group_id = False
|
||||||
|
|
@ -1,12 +1,28 @@
|
||||||
# Copyright 2026 Criptomart
|
# Copyright 2026 Criptomart
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
class StockPicking(models.Model):
|
class StockPicking(models.Model):
|
||||||
_inherit = "stock.picking"
|
_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):
|
def _pre_action_done_hook(self):
|
||||||
"""Run collected checks only after Odoo resolves backorders.
|
"""Run collected checks only after Odoo resolves backorders.
|
||||||
|
|
||||||
|
|
@ -231,7 +231,7 @@ class StockPickingBatch(models.Model):
|
||||||
"views": [
|
"views": [
|
||||||
(
|
(
|
||||||
self.env.ref(
|
self.env.ref(
|
||||||
"stock_picking_batch_collect.view_move_line_batch_operator"
|
"stock_picking_batch_custom.view_move_line_batch_operator"
|
||||||
).id,
|
).id,
|
||||||
"list",
|
"list",
|
||||||
)
|
)
|
||||||
8
stock_picking_batch_custom/readme/CONFIGURE.rst
Normal file
8
stock_picking_batch_custom/readme/CONFIGURE.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
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.
|
||||||
14
stock_picking_batch_custom/readme/DESCRIPTION.rst
Normal file
14
stock_picking_batch_custom/readme/DESCRIPTION.rst
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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.
|
||||||
|
|
@ -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_collect
|
docker-compose run --rm odoo odoo -d odoo --stop-after-init -u stock_picking_batch_custom
|
||||||
14
stock_picking_batch_custom/readme/USAGE.rst
Normal file
14
stock_picking_batch_custom/readme/USAGE.rst
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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.
|
||||||
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
|
@ -1,3 +1,13 @@
|
||||||
|
/* 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 */
|
/* Widen the quantity column in detailed operations so it is easier to tap */
|
||||||
/* Note: arch class lands on the root <div> of the list controller, not <table> */
|
/* Note: arch class lands on the root <div> of the list controller, not <table> */
|
||||||
.o_batch_move_line_list th[data-name="quantity"],
|
.o_batch_move_line_list th[data-name="quantity"],
|
||||||
1
stock_picking_batch_custom/tests/__init__.py
Normal file
1
stock_picking_batch_custom/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import test_batch_summary # noqa: F401
|
||||||
36
stock_picking_batch_custom/views/stock_move_line_views.xml
Normal file
36
stock_picking_batch_custom/views/stock_move_line_views.xml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_move_line_tree_inherit_batch_custom" model="ir.ui.view">
|
||||||
|
<field name="name">stock.move.line.list.batch.custom</field>
|
||||||
|
<field name="model">stock.move.line</field>
|
||||||
|
<field name="inherit_id" ref="stock_picking_batch.view_move_line_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='picking_id']" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='lot_id']" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='lot_name']" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='location_id']" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='location_dest_id']" position="attributes">
|
||||||
|
<attribute name="optional">hide</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='product_id']" position="after">
|
||||||
|
<field name="product_categ_id" optional="hide"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='picking_id']" position="after">
|
||||||
|
<field name="picking_partner_id" optional="hide"/>
|
||||||
|
<field name="consumer_group_id" optional="show"/>
|
||||||
|
<field name="home_delivery" optional="show" widget="boolean_toggle"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='quantity']" position="after">
|
||||||
|
<field name="is_collected" optional="show" widget="boolean_toggle"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -30,9 +30,9 @@
|
||||||
context="{'display_default_code': False}"/>
|
context="{'display_default_code': False}"/>
|
||||||
<field name="reference" readonly="1" optional="hide"/>
|
<field name="reference" readonly="1" optional="hide"/>
|
||||||
<field name="picking_partner_id" readonly="1" optional="show"/>
|
<field name="picking_partner_id" readonly="1" optional="show"/>
|
||||||
|
<field name="home_delivery" readonly="1" widget="boolean_toggle" optional="show"/>
|
||||||
|
<field name="consumer_group_id" readonly="1" optional="show"/>
|
||||||
<field name="quantity" string="Cant."/>
|
<field name="quantity" string="Cant."/>
|
||||||
<!-- Loaded so `_onchange_quantity_manual` reaches the server. -->
|
|
||||||
<field name="picked" column_invisible="True"/>
|
|
||||||
<field name="is_collected" widget="boolean_toggle"/>
|
<field name="is_collected" widget="boolean_toggle"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
|
|
@ -52,12 +52,23 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//notebook/page[@name='page_detailed_operations']" position="after">
|
<xpath expr="//notebook/page[@name='page_detailed_operations']" position="after">
|
||||||
<page string="Product Summary" name="page_product_summary">
|
<page string="Product Summary" name="page_product_summary">
|
||||||
<field name="summary_line_ids" context="{'tree_view_ref': 'stock_picking_batch_collect.view_stock_picking_batch_summary_line_tree'}"/>
|
<field name="summary_line_ids" context="{'tree_view_ref': 'stock_picking_batch_custom.view_stock_picking_batch_summary_line_tree'}"/>
|
||||||
</page>
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="view_stock_picking_batch_picking_tree_consumer_group" model="ir.ui.view">
|
||||||
|
<field name="name">stock.picking.batch.picking.tree.consumer.group</field>
|
||||||
|
<field name="model">stock.picking</field>
|
||||||
|
<field name="inherit_id" ref="stock_picking_batch.stock_picking_view_batch_tree_ref"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='partner_id']" position="after">
|
||||||
|
<field name="batch_consumer_group_id" optional="show"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="view_move_line_tree_inherit_batch_custom" model="ir.ui.view">
|
<record id="view_move_line_tree_inherit_batch_custom" model="ir.ui.view">
|
||||||
<field name="name">stock.move.line.list.batch.custom</field>
|
<field name="name">stock.move.line.list.batch.custom</field>
|
||||||
<field name="model">stock.move.line</field>
|
<field name="model">stock.move.line</field>
|
||||||
|
|
@ -76,12 +87,10 @@
|
||||||
<xpath expr="//field[@name='product_id']" position="after">
|
<xpath expr="//field[@name='product_id']" position="after">
|
||||||
<field name="picking_partner_id" readonly="1" optional="show"/>
|
<field name="picking_partner_id" readonly="1" optional="show"/>
|
||||||
<field name="product_default_code" readonly="1" optional="hide"/>
|
<field name="product_default_code" readonly="1" optional="hide"/>
|
||||||
|
<field name="home_delivery" readonly="1" widget="boolean_toggle" optional="show"/>
|
||||||
|
<field name="consumer_group_id" readonly="1" optional="show"/>
|
||||||
<field name="is_collected" widget="boolean_toggle" optional="show"/>
|
<field name="is_collected" widget="boolean_toggle" optional="show"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='quantity']" position="after">
|
|
||||||
<!-- Loaded so `_onchange_quantity_manual` reaches the server. -->
|
|
||||||
<field name="picked" column_invisible="True"/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='product_uom_id']" position="attributes">
|
<xpath expr="//field[@name='product_uom_id']" position="attributes">
|
||||||
<attribute name="optional">hide</attribute>
|
<attribute name="optional">hide</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
@ -100,4 +109,5 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- assets: moved to manifest 'assets' declaration -->
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
================
|
|
||||||
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
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# 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",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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``.
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
Contribuidores
|
|
||||||
==============
|
|
||||||
|
|
||||||
* Criptomart
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
Créditos
|
|
||||||
========
|
|
||||||
|
|
||||||
Autor
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Criptomart
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
|
|
@ -135,36 +135,22 @@ La página inicial renderiza la primera página server-side. El botón "Cargar m
|
||||||
|
|
||||||
| Cron | Frecuencia | Acción |
|
| Cron | Frecuencia | Acción |
|
||||||
| ---- | ---------- | ------ |
|
| ---- | ---------- | ------ |
|
||||||
| `_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_confirm_group_orders` | Diario (medianoche) | Confirma sale.orders pasado el cutoff; crea lotes de picking agrupados por consumer_group + pickup_date |
|
||||||
| `_cron_update_dates` | Diario | Recalcula `delivery_date` y `pickup_date` en órdenes activas |
|
| `_cron_update_dates` | Diario | Recalcula `delivery_date` y `pickup_date` en órdenes activas |
|
||||||
|
|
||||||
## Dependencias
|
## Dependencias
|
||||||
|
|
||||||
Según `__manifest__.py`:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
website_sale_aplicoop
|
website_sale_aplicoop
|
||||||
├── website_sale (Odoo core)
|
├── website_sale (Odoo core)
|
||||||
├── website_sale_stock (Odoo core)
|
|
||||||
├── payment (Odoo core)
|
|
||||||
├── product (Odoo core)
|
|
||||||
├── sale (Odoo core)
|
├── sale (Odoo core)
|
||||||
├── stock (Odoo core)
|
├── product_main_seller (OCA)
|
||||||
├── account (Odoo core)
|
├── product_sale_price_from_pricelist (custom)
|
||||||
├── stock_picking_batch (Odoo core)
|
│ └── product_pricelist_total_margin (custom)
|
||||||
├── stock_picking_batch_collect (custom)
|
│ └── product_price_category (OCA)
|
||||||
│ ├── stock_move_manual_quantity (custom)
|
└── stock_picking_batch_custom (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
|
## Instalación y actualización
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{ # noqa: B018
|
{ # noqa: B018
|
||||||
"name": "Website Sale - Aplicoop",
|
"name": "Website Sale - Aplicoop",
|
||||||
"version": "18.0.1.17.0",
|
"version": "18.0.1.16.0",
|
||||||
"category": "Website/Sale",
|
"category": "Website/Sale",
|
||||||
"summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders",
|
"summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders",
|
||||||
"author": "Odoo Community Association (OCA), Criptomart",
|
"author": "Odoo Community Association (OCA), Criptomart",
|
||||||
|
|
@ -18,9 +18,6 @@
|
||||||
"sale",
|
"sale",
|
||||||
"stock",
|
"stock",
|
||||||
"stock_picking_batch",
|
"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",
|
"account",
|
||||||
"product_get_price_helper",
|
"product_get_price_helper",
|
||||||
"l10n_es_partner",
|
"l10n_es_partner",
|
||||||
|
|
@ -46,7 +43,6 @@
|
||||||
"views/product_template_views.xml",
|
"views/product_template_views.xml",
|
||||||
"views/sale_order_views.xml",
|
"views/sale_order_views.xml",
|
||||||
"views/stock_picking_views.xml",
|
"views/stock_picking_views.xml",
|
||||||
"views/stock_picking_batch_views.xml",
|
|
||||||
"views/portal_templates.xml",
|
"views/portal_templates.xml",
|
||||||
"views/load_from_history_templates.xml",
|
"views/load_from_history_templates.xml",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -890,6 +890,13 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
product_display_info,
|
product_display_info,
|
||||||
filtered_products_dict,
|
filtered_products_dict,
|
||||||
) = self._prepare_products_maps(products, pricelist)
|
) = 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
|
# 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)
|
products, product_max_qty = self._prepare_draft_stock_data(products)
|
||||||
|
|
||||||
|
|
@ -934,6 +941,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"placed_order_url": placed_order_url,
|
"placed_order_url": placed_order_url,
|
||||||
"products": products,
|
"products": products,
|
||||||
"filtered_product_tags": filtered_products_dict,
|
"filtered_product_tags": filtered_products_dict,
|
||||||
|
"any_product_has_tags": any_product_has_tags,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"available_categories": available_categories,
|
"available_categories": available_categories,
|
||||||
"category_hierarchy": category_hierarchy,
|
"category_hierarchy": category_hierarchy,
|
||||||
|
|
@ -1045,6 +1053,11 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"product": product,
|
"product": product,
|
||||||
"published_tags": published_tags,
|
"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 = {}
|
product_display_info = {}
|
||||||
for product in products_page:
|
for product in products_page:
|
||||||
product_display_info[product.id] = self._prepare_product_display_info(
|
product_display_info[product.id] = self._prepare_product_display_info(
|
||||||
|
|
@ -1062,6 +1075,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"group_order": group_order,
|
"group_order": group_order,
|
||||||
"products": products_page,
|
"products": products_page,
|
||||||
"filtered_product_tags": filtered_products_dict,
|
"filtered_product_tags": filtered_products_dict,
|
||||||
|
"any_product_has_tags": any_product_has_tags,
|
||||||
"product_supplier_info": product_supplier_info,
|
"product_supplier_info": product_supplier_info,
|
||||||
"product_price_info": product_price_info,
|
"product_price_info": product_price_info,
|
||||||
"product_display_info": product_display_info,
|
"product_display_info": product_display_info,
|
||||||
|
|
@ -1171,6 +1185,11 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
}
|
}
|
||||||
for product in products_page
|
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
|
# Inject draft demand context for ribbons/stock flags
|
||||||
products_page, product_max_qty = self._prepare_draft_stock_data(products_page)
|
products_page, product_max_qty = self._prepare_draft_stock_data(products_page)
|
||||||
|
|
||||||
|
|
@ -1184,6 +1203,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"group_order": group_order,
|
"group_order": group_order,
|
||||||
"products": products_page,
|
"products": products_page,
|
||||||
"filtered_product_tags": filtered_products_dict,
|
"filtered_product_tags": filtered_products_dict,
|
||||||
|
"any_product_has_tags": any_product_has_tags,
|
||||||
"product_supplier_info": product_supplier_info,
|
"product_supplier_info": product_supplier_info,
|
||||||
"product_price_info": product_price_info,
|
"product_price_info": product_price_info,
|
||||||
"product_display_info": product_display_info,
|
"product_display_info": product_display_info,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
from . import group_order # noqa: F401
|
from . import group_order # noqa: F401
|
||||||
from . import group_order_slot # noqa: F401
|
from . import group_order_slot # noqa: F401
|
||||||
from . import js_translations # noqa: F401
|
|
||||||
from . import payment_transaction # noqa: F401
|
from . import payment_transaction # noqa: F401
|
||||||
from . import product_category_extension # noqa: F401
|
from . import product_category_extension # noqa: F401
|
||||||
from . import product_extension # noqa: F401
|
from . import product_extension # noqa: F401
|
||||||
from . import res_config_settings # noqa: F401
|
from . import res_config_settings # noqa: F401
|
||||||
from . import res_partner_extension # noqa: F401
|
from . import res_partner_extension # noqa: F401
|
||||||
from . import sale_order_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 stock_picking_extension # noqa: F401
|
||||||
from . import website # noqa: F401
|
from . import website # noqa: F401
|
||||||
|
from . import js_translations # noqa: F401
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# 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,
|
|
||||||
)
|
|
||||||
|
|
@ -152,6 +152,7 @@
|
||||||
|
|
||||||
/* ========== PRODUCT CARD MEDIA ========== */
|
/* ========== PRODUCT CARD MEDIA ========== */
|
||||||
--ac-card-media-ratio: 4 / 3;
|
--ac-card-media-ratio: 4 / 3;
|
||||||
|
--ac-card-media-height: clamp(5rem, 4rem + 6vw, 8.5rem);
|
||||||
|
|
||||||
/* ========== Z-INDEX ========== */
|
/* ========== Z-INDEX ========== */
|
||||||
--ac-z-dropdown: 1000;
|
--ac-z-dropdown: 1000;
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,13 @@
|
||||||
* Markup (eskaera_shop_products):
|
* Markup (eskaera_shop_products):
|
||||||
* .product-card-wrapper.product-card
|
* .product-card-wrapper.product-card
|
||||||
* └ .card
|
* └ .card
|
||||||
* ├ .product-media
|
* ├ img.product-img-cover | .product-img-placeholder
|
||||||
* │ ├ img.product-img-cover | .product-img-placeholder
|
* ├ .card-body (title, tags, supplier, origin, price)
|
||||||
* │ └ 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
|
* └ form.add-to-cart-form → components/quantity-control.css
|
||||||
*
|
*
|
||||||
* The wrapper carries the chrome (border, radius, elevation); the inner
|
* The wrapper carries the chrome (border, radius, elevation); the inner
|
||||||
* Bootstrap .card is neutralised through its own custom properties so the two
|
* Bootstrap .card is neutralised through its own custom properties so the two
|
||||||
* boxes stop drawing a double frame.
|
* 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 {
|
.product-card {
|
||||||
|
|
@ -51,23 +41,14 @@
|
||||||
|
|
||||||
/* ---------- Media ---------- */
|
/* ---------- Media ---------- */
|
||||||
|
|
||||||
.product-media {
|
|
||||||
position: relative;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .product-image,
|
.product-card .product-image,
|
||||||
.product-img-cover,
|
.product-img-cover,
|
||||||
.product-img-fixed,
|
.product-img-fixed,
|
||||||
.product-img-placeholder {
|
.product-img-placeholder {
|
||||||
display: block;
|
display: block;
|
||||||
|
flex-shrink: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/* 4:3, not square: a square photo ate too much vertical space on a
|
height: var(--ac-card-media-height);
|
||||||
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;
|
border-radius: var(--ac-radius-md) var(--ac-radius-md) 0 0;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
background-color: var(--ac-surface-muted);
|
background-color: var(--ac-surface-muted);
|
||||||
|
|
@ -80,68 +61,6 @@
|
||||||
color: var(--ac-text-muted);
|
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 ---------- */
|
/* ---------- Body ---------- */
|
||||||
|
|
||||||
.product-card .card-body {
|
.product-card .card-body {
|
||||||
|
|
@ -188,6 +107,10 @@
|
||||||
align-content: center;
|
align-content: center;
|
||||||
gap: var(--ac-space-3xs);
|
gap: var(--ac-space-3xs);
|
||||||
justify-content: center;
|
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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -208,12 +131,14 @@
|
||||||
|
|
||||||
/* ---------- Supplier & origin ---------- */
|
/* ---------- Supplier & origin ---------- */
|
||||||
|
|
||||||
/* Only ever rendered inside .product-info-panel now (see
|
/* Both rows are reserved even when empty (rendered aria-hidden — see
|
||||||
eskaera_shop_products), so there is no longer a cross-card alignment to
|
eskaera_shop_products) and clamped to a fixed line count, so the price row
|
||||||
protect — no reserved space, no forced line clamp. */
|
below lines up across every card in the grid row regardless of which
|
||||||
|
products actually have a supplier name or an origin. */
|
||||||
.product-card .product-supplier,
|
.product-card .product-supplier,
|
||||||
.product-card .product-origin {
|
.product-card .product-origin {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
font-size: var(--ac-text-xs);
|
font-size: var(--ac-text-xs);
|
||||||
font-weight: var(--ac-weight-normal);
|
font-weight: var(--ac-weight-normal);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -221,11 +146,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card .product-supplier {
|
.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;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card .product-origin {
|
.product-card .product-origin {
|
||||||
overflow: hidden;
|
min-height: calc(1em * var(--ac-leading-tight));
|
||||||
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,17 @@
|
||||||
*
|
*
|
||||||
* The grid used to pin an exact column count at seven breakpoints (1 → 6). It
|
* 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
|
* now derives the count from the space available, which keeps the card width
|
||||||
* stable no matter how wide the sidebar or the container is.
|
* 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.
|
||||||
*
|
*
|
||||||
* Two columns from the smallest phones: with the compact "cromo" card (4:3
|
* 9rem is the card's minimum width; at the widest container (1320px minus the
|
||||||
* photo, no inline tags/supplier/origin — see product-card.css) a single
|
* category sidebar) it still resolves to six columns, as before.
|
||||||
* 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 {
|
.products-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: 1fr;
|
||||||
gap: var(--ac-space-md);
|
gap: var(--ac-space-md);
|
||||||
margin-bottom: var(--ac-space-xl);
|
margin-bottom: var(--ac-space-xl);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1246,23 +1246,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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)
|
// Add to cart button (via event delegation on grid)
|
||||||
productsGrid.addEventListener("click", handleAddToCart);
|
productsGrid.addEventListener("click", handleAddToCart);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,39 @@ class TestMultiCompanyGroupOrder(TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
# Crear dos compañías. `stock_picking_batch_collect` es dependencia del
|
# Crear dos compañías
|
||||||
# módulo, así que los campos de restricción de lote siempre existen.
|
|
||||||
company_model = self.env["res.company"]
|
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):
|
def _company_vals(name):
|
||||||
return {
|
vals = {"name": name}
|
||||||
"name": name,
|
if "batch_summary_restriction_scope" in company_model._fields:
|
||||||
"batch_summary_restriction_scope": "processed",
|
vals["batch_summary_restriction_scope"] = "processed"
|
||||||
"batch_detailed_restriction_scope": "processed",
|
if "batch_detailed_restriction_scope" in company_model._fields:
|
||||||
}
|
vals["batch_detailed_restriction_scope"] = "processed"
|
||||||
|
return vals
|
||||||
|
|
||||||
self.company1 = company_model.create(_company_vals("Company 1"))
|
self.company1 = company_model.create(_company_vals("Company 1"))
|
||||||
self.company2 = company_model.create(_company_vals("Company 2"))
|
self.company2 = company_model.create(_company_vals("Company 2"))
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,39 @@ class TestGroupOrderRecordRules(TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
# Crear dos compañías. `stock_picking_batch_collect` es dependencia del
|
# Crear dos compañías
|
||||||
# módulo, así que los campos de restricción de lote siempre existen.
|
|
||||||
company_model = self.env["res.company"]
|
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):
|
def _company_vals(name):
|
||||||
return {
|
vals = {"name": name}
|
||||||
"name": name,
|
if "batch_summary_restriction_scope" in company_model._fields:
|
||||||
"batch_summary_restriction_scope": "processed",
|
vals["batch_summary_restriction_scope"] = "processed"
|
||||||
"batch_detailed_restriction_scope": "processed",
|
if "batch_detailed_restriction_scope" in company_model._fields:
|
||||||
}
|
vals["batch_detailed_restriction_scope"] = "processed"
|
||||||
|
return vals
|
||||||
|
|
||||||
self.company1 = company_model.create(_company_vals("Company 1"))
|
self.company1 = company_model.create(_company_vals("Company 1"))
|
||||||
self.company2 = company_model.create(_company_vals("Company 2"))
|
self.company2 = company_model.create(_company_vals("Company 2"))
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<!-- Consumer group columns on the batch operator screen (Basket Assembly) -->
|
|
||||||
<record id="view_move_line_batch_operator_consumer_group" model="ir.ui.view">
|
|
||||||
<field name="name">stock.move.line.batch.operator.consumer.group</field>
|
|
||||||
<field name="model">stock.move.line</field>
|
|
||||||
<field name="inherit_id" ref="stock_picking_batch_collect.view_move_line_batch_operator"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='picking_partner_id']" position="after">
|
|
||||||
<field name="home_delivery" readonly="1" widget="boolean_toggle" optional="show"/>
|
|
||||||
<field name="consumer_group_id" readonly="1" optional="show"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Consumer group columns on the batch detailed operations list -->
|
|
||||||
<record id="view_move_line_tree_batch_consumer_group" model="ir.ui.view">
|
|
||||||
<field name="name">stock.move.line.list.batch.consumer.group</field>
|
|
||||||
<field name="model">stock.move.line</field>
|
|
||||||
<field name="inherit_id" ref="stock_picking_batch_collect.view_move_line_tree_inherit_batch_custom"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='product_default_code']" position="after">
|
|
||||||
<field name="home_delivery" readonly="1" widget="boolean_toggle" optional="show"/>
|
|
||||||
<field name="consumer_group_id" readonly="1" optional="show"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Consumer group on the transfers list of a batch -->
|
|
||||||
<record id="view_stock_picking_batch_picking_tree_consumer_group" model="ir.ui.view">
|
|
||||||
<field name="name">stock.picking.batch.picking.tree.consumer.group</field>
|
|
||||||
<field name="model">stock.picking</field>
|
|
||||||
<field name="inherit_id" ref="stock_picking_batch.stock_picking_view_batch_tree_ref"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='partner_id']" position="after">
|
|
||||||
<field name="consumer_group_id" optional="show"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</odoo>
|
|
||||||
|
|
@ -274,15 +274,56 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- One flex row for cart/tags/category/search/products: Bootstrap's
|
<div class="mb-3 eskaera-filters" id="realtimeSearch-filters">
|
||||||
order utilities need every reordered item to be a sibling of the
|
<div class="row g-2">
|
||||||
same flex container, so the four blocks that used to live in two
|
<div class="col-md-7">
|
||||||
separate rows (filters above, cart+products below) are now columns
|
<div class="search-input-wrapper">
|
||||||
here. Unprefixed order-* puts the cart first on mobile (right below
|
<label class="visually-hidden" for="realtime-search-input">Search products</label>
|
||||||
the header, above the filters); order-lg-* restores the desktop
|
<input type="search" id="realtime-search-input" class="form-control eskaera-search-input" placeholder="Search products..." autocomplete="off" aria-describedby="realtime-search-status" />
|
||||||
layout: search+category as one row, then tags, then cart+products. -->
|
<button type="button" id="clear-search-btn" class="search-clear-btn" aria-label="Clear search" title="Clear search">
|
||||||
<div class="row g-2 eskaera-filters" id="realtimeSearch-filters">
|
×
|
||||||
<div class="col-12 col-lg-3 order-1 order-lg-4 mb-4 mb-lg-0">
|
</button>
|
||||||
|
</div>
|
||||||
|
<p id="realtime-search-status" class="visually-hidden" aria-live="polite" aria-atomic="true" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<select name="category" id="realtime-category-select" class="form-select">
|
||||||
|
<option value="">Browse Product Categories</option>
|
||||||
|
<t t-call="website_sale_aplicoop.category_hierarchy_options">
|
||||||
|
<t t-set="categories" t-value="category_hierarchy" />
|
||||||
|
<t t-set="depth" t-value="0" />
|
||||||
|
</t>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<t t-if="available_tags">
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div id="tag-filter-container" class="tag-filter-badges">
|
||||||
|
<t t-foreach="available_tags" t-as="tag">
|
||||||
|
<!-- Toggle buttons: aria-pressed carries the filter
|
||||||
|
state, which realtime_search.js keeps in sync
|
||||||
|
with the .is-selected / .is-dimmed classes.
|
||||||
|
The record colour rides in a custom property so
|
||||||
|
the stylesheet keeps owning the badge. -->
|
||||||
|
<t t-if="tag['color']">
|
||||||
|
<button type="button" class="badge tag-filter-badge" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" t-att-data-tag-color="tag['color']" t-attf-style="--ac-tag-color: {{ tag['color'] }};" data-toggle="tag-filter" aria-pressed="false">
|
||||||
|
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
||||||
|
</button>
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
|
<button type="button" class="badge tag-filter-badge tag-use-theme-color" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" data-tag-color="" data-toggle="tag-filter" aria-pressed="false">
|
||||||
|
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
||||||
|
</button>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-lg-3 mb-4 mb-lg-0">
|
||||||
<div class="card sticky-top cart-sticky-position" aria-label="Cart Summary">
|
<div class="card sticky-top cart-sticky-position" aria-label="Cart Summary">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center gap-1">
|
<div class="card-header d-flex justify-content-between align-items-center gap-1">
|
||||||
<h6 class="mb-0 cart-title-sm" id="cart-title">My Cart</h6>
|
<h6 class="mb-0 cart-title-sm" id="cart-title">My Cart</h6>
|
||||||
|
|
@ -321,49 +362,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<t t-if="available_tags">
|
<div class="col-lg-9">
|
||||||
<div class="col-12 order-2 order-lg-3">
|
|
||||||
<div id="tag-filter-container" class="tag-filter-badges">
|
|
||||||
<t t-foreach="available_tags" t-as="tag">
|
|
||||||
<!-- Toggle buttons: aria-pressed carries the filter
|
|
||||||
state, which realtime_search.js keeps in sync
|
|
||||||
with the .is-selected / .is-dimmed classes.
|
|
||||||
The record colour rides in a custom property so
|
|
||||||
the stylesheet keeps owning the badge. -->
|
|
||||||
<t t-if="tag['color']">
|
|
||||||
<button type="button" class="badge tag-filter-badge" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" t-att-data-tag-color="tag['color']" t-attf-style="--ac-tag-color: {{ tag['color'] }};" data-toggle="tag-filter" aria-pressed="false">
|
|
||||||
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
|
||||||
</button>
|
|
||||||
</t>
|
|
||||||
<t t-else="">
|
|
||||||
<button type="button" class="badge tag-filter-badge tag-use-theme-color" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" data-tag-color="" data-toggle="tag-filter" aria-pressed="false">
|
|
||||||
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
|
||||||
</button>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<div class="col-12 col-lg-5 order-3 order-lg-2">
|
|
||||||
<select name="category" id="realtime-category-select" class="form-select">
|
|
||||||
<option value="">Browse Product Categories</option>
|
|
||||||
<t t-call="website_sale_aplicoop.category_hierarchy_options">
|
|
||||||
<t t-set="categories" t-value="category_hierarchy" />
|
|
||||||
<t t-set="depth" t-value="0" />
|
|
||||||
</t>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-7 order-4 order-lg-1">
|
|
||||||
<div class="search-input-wrapper">
|
|
||||||
<label class="visually-hidden" for="realtime-search-input">Search products</label>
|
|
||||||
<input type="search" id="realtime-search-input" class="form-control eskaera-search-input" placeholder="Search products..." autocomplete="off" aria-describedby="realtime-search-status" />
|
|
||||||
<button type="button" id="clear-search-btn" class="search-clear-btn" aria-label="Clear search" title="Clear search">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p id="realtime-search-status" class="visually-hidden" aria-live="polite" aria-atomic="true" />
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-9 order-5 order-lg-5">
|
|
||||||
<div class="oe_structure oe_empty" data-name="Before Products Filter" />
|
<div class="oe_structure oe_empty" data-name="Before Products Filter" />
|
||||||
<t t-if="products">
|
<t t-if="products">
|
||||||
<div class="products-grid" id="products-grid">
|
<div class="products-grid" id="products-grid">
|
||||||
|
|
@ -764,13 +763,8 @@
|
||||||
</template>
|
</template>
|
||||||
<template id="eskaera_shop_products" name="Eskaera Shop Products">
|
<template id="eskaera_shop_products" name="Eskaera Shop Products">
|
||||||
<t t-foreach="products" t-as="product">
|
<t t-foreach="products" t-as="product">
|
||||||
<t t-set="has_tags" t-value="bool(product.product_tag_ids)" />
|
|
||||||
<t t-set="supplier_text" t-value="product_supplier_info.get(product.id)" />
|
|
||||||
<t t-set="has_origin" t-value="bool(product.origin_text)" />
|
|
||||||
<t t-set="has_info" t-value="has_tags or bool(supplier_text) or has_origin" />
|
|
||||||
<div class="product-card-wrapper product-card" t-attf-data-product-name="{{ product.name }}" t-attf-data-category-id="{{ product.categ_id.id if product.categ_id else '' }}" t-attf-data-product-tags="{{ ','.join(str(t.id) for t in product.product_tag_ids) if product.product_tag_ids else '' }}">
|
<div class="product-card-wrapper product-card" t-attf-data-product-name="{{ product.name }}" t-attf-data-category-id="{{ product.categ_id.id if product.categ_id else '' }}" t-attf-data-product-tags="{{ ','.join(str(t.id) for t in product.product_tag_ids) if product.product_tag_ids else '' }}">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="product-media">
|
|
||||||
<t t-if="product.image_128">
|
<t t-if="product.image_128">
|
||||||
<img t-attf-src="data:image/png;base64,{{ product.image_128.decode() }}" class="card-img-top product-img-cover" t-attf-alt="{{ product.name }}" />
|
<img t-attf-src="data:image/png;base64,{{ product.image_128.decode() }}" class="card-img-top product-img-cover" t-attf-alt="{{ product.name }}" />
|
||||||
</t>
|
</t>
|
||||||
|
|
@ -779,16 +773,6 @@
|
||||||
<i class="fa fa-image fa-3x text-muted" aria-hidden="true" />
|
<i class="fa fa-image fa-3x text-muted" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="has_info">
|
|
||||||
<!-- Origin/supplier/tags used to sit unconditionally in the card
|
|
||||||
body; on the compact "cromo" tile there is no room to show
|
|
||||||
them inline, so they move behind this toggle (see
|
|
||||||
.product-info-panel below). -->
|
|
||||||
<button type="button" class="product-info-toggle" aria-expanded="false" t-attf-aria-controls="product-info-{{ product.id }}" aria-label="Show origin, supplier and tags" title="Origin, supplier and tags">
|
|
||||||
<i class="fa fa-info" aria-hidden="true" t-translation="off" />
|
|
||||||
</button>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
<t t-if="product.dynamic_ribbon_id">
|
<t t-if="product.dynamic_ribbon_id">
|
||||||
<t t-set="ribbon" t-value="product.dynamic_ribbon_id" />
|
<t t-set="ribbon" t-value="product.dynamic_ribbon_id" />
|
||||||
<span t-attf-class="o_ribbon {{ ribbon._get_position_class() }} z-1" t-attf-style="color: {{ ribbon.text_color }}; background-color: {{ ribbon.bg_color }};" t-esc="ribbon.name" />
|
<span t-attf-class="o_ribbon {{ ribbon._get_position_class() }} z-1" t-attf-style="color: {{ ribbon.text_color }}; background-color: {{ ribbon.bg_color }};" t-esc="ribbon.name" />
|
||||||
|
|
@ -799,9 +783,16 @@
|
||||||
</t>
|
</t>
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<h6 class="card-title" t-esc="product.name" />
|
<h6 class="card-title" t-esc="product.name" />
|
||||||
<t t-if="has_info">
|
<!-- Tags/supplier/origin always occupy their slot (empty and
|
||||||
<div t-attf-id="product-info-{{ product.id }}" class="product-info-panel" hidden="hidden">
|
aria-hidden when the product has no value) so that the price
|
||||||
<t t-if="has_tags">
|
row below lines up across every card in the grid row,
|
||||||
|
regardless of which optional fields a given product has. The
|
||||||
|
tags row itself is only reserved when at least one product in
|
||||||
|
this batch actually has a tag (any_product_has_tags, computed
|
||||||
|
controller-side) — otherwise it would add a permanent empty
|
||||||
|
gap to every card for a feature nobody in the list uses. -->
|
||||||
|
<t t-if="any_product_has_tags">
|
||||||
|
<t t-if="product.product_tag_ids">
|
||||||
<div class="product-tags">
|
<div class="product-tags">
|
||||||
<t t-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)" t-as="tag">
|
<t t-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)" t-as="tag">
|
||||||
<t t-if="tag.color">
|
<t t-if="tag.color">
|
||||||
|
|
@ -815,12 +806,21 @@
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="supplier_text">
|
<t t-else="">
|
||||||
|
<div class="product-tags" aria-hidden="true" />
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<t t-if="product_supplier_info.get(product.id)">
|
||||||
<p class="product-supplier">
|
<p class="product-supplier">
|
||||||
<small t-esc="supplier_text" />
|
<small>
|
||||||
|
<t t-esc="product_supplier_info[product.id]" />
|
||||||
|
</small>
|
||||||
</p>
|
</p>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="has_origin">
|
<t t-else="">
|
||||||
|
<p class="product-supplier" aria-hidden="true" />
|
||||||
|
</t>
|
||||||
|
<t t-if="product.origin_text">
|
||||||
<p class="product-origin">
|
<p class="product-origin">
|
||||||
<small>
|
<small>
|
||||||
<i class="fa fa-map-marker" aria-hidden="true" />
|
<i class="fa fa-map-marker" aria-hidden="true" />
|
||||||
|
|
@ -828,7 +828,8 @@
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
<t t-else="">
|
||||||
|
<p class="product-origin" aria-hidden="true" />
|
||||||
</t>
|
</t>
|
||||||
<t t-set="price_info" t-value="product_price_info.get(product.id, {})" />
|
<t t-set="price_info" t-value="product_price_info.get(product.id, {})" />
|
||||||
<t t-set="display_price" t-value="product_display_info.get(product.id, {}).get('display_price', 0.0)" />
|
<t t-set="display_price" t-value="product_display_info.get(product.id, {}).get('display_price', 0.0)" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue