[REM] website_sale_aplicoop: remove dead code and orphaned tests

Duplicate _translate_labels fallback, unreachable /eskaera/add-to-cart and
/eskaera/save-cart routes (the frontend cart is localStorage-only and uses
save-order), redundant pickup wrappers, unused pagination/count helpers and
fields, deprecated JS shims, and the already-empty checkout_summary.js and
i18n key/init leftovers.

Also drops 11 tests/*.py never wired into tests/__init__.py: three were
unimplemented placeholders (setUp with no assertions), and the other eight
had real assertions but were bit-rotted against the current schema (e.g.
res.partner.is_supplier no longer exists) — wiring them in surfaced 43
failures unrelated to this cleanup. Verified 0 failed/0 errors of 270 tests
both before and after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-08-16 22:31:13 +02:00
parent 6ba554c91b
commit b3999e2283
24 changed files with 5 additions and 5598 deletions

View file

@ -60,10 +60,6 @@
# Demo: Sale Orders # Demo: Sale Orders
"demo/sale_order_demo.xml", "demo/sale_order_demo.xml",
], ],
"i18n": [
"i18n/es.po",
"i18n/eu_ES.po",
],
"external_dependencies": { "external_dependencies": {
"python": [], "python": [],
}, },
@ -77,7 +73,6 @@
"website_sale_aplicoop/static/src/js/website_sale.js", "website_sale_aplicoop/static/src/js/website_sale.js",
"website_sale_aplicoop/static/src/js/checkout_labels.js", "website_sale_aplicoop/static/src/js/checkout_labels.js",
"website_sale_aplicoop/static/src/js/home_delivery.js", "website_sale_aplicoop/static/src/js/home_delivery.js",
"website_sale_aplicoop/static/src/js/checkout_summary.js",
"website_sale_aplicoop/static/src/js/eskaera_payment.js", "website_sale_aplicoop/static/src/js/eskaera_payment.js",
# Search and pagination # Search and pagination
"website_sale_aplicoop/static/src/js/infinite_scroll.js", "website_sale_aplicoop/static/src/js/infinite_scroll.js",

View file

@ -323,74 +323,6 @@ class AplicoopWebsiteSale(WebsiteSale):
) )
return sale_order return sale_order
def _create_draft_sale_order(
self,
group_order,
current_user,
sale_order_lines,
order_id,
pickup_date=None,
is_delivery=False,
):
"""Create a draft sale.order from prepared lines and propagate group fields.
Returns created sale.order record.
"""
consumer_group_id = self._validate_user_group_access(group_order, current_user)
_logger.info(
"[CONSUMER_GROUP DEBUG] _create_draft_sale_order: "
"group_order=%s, group_order.group_ids=%s, consumer_group_id=%s",
group_order.id,
group_order.group_ids.ids,
consumer_group_id,
)
effective_home_delivery, commitment_date = self._get_effective_delivery_context(
group_order, is_delivery
)
order_vals = {
"partner_id": current_user.partner_id.id,
"order_line": sale_order_lines,
"state": "draft",
"group_order_id": order_id,
"pickup_day": group_order.pickup_day,
"pickup_date": group_order.pickup_date,
"home_delivery": effective_home_delivery,
"consumer_group_id": consumer_group_id,
"commitment_date": commitment_date,
}
# Get salesperson for order creation (portal users need this)
salesperson = self._get_salesperson_for_order(current_user.partner_id)
if salesperson:
order_vals["user_id"] = salesperson.id
_logger.info(
"Creating draft sale.order with salesperson %s (%d)",
salesperson.name,
salesperson.id,
)
# Create order with sudo to avoid permission issues with portal users
sale_order = request.env["sale.order"].sudo().create(order_vals)
# Ensure the order has a name (sequence)
try:
if not sale_order.name or sale_order.name == "New":
sale_order._onchange_partner_id()
if not sale_order.name or sale_order.name == "New":
sale_order.name = "DRAFT-%s" % sale_order.id
except Exception as exc:
# Do not break creation on name generation issues
_logger.warning(
"Failed to generate name for draft sale order %s: %s",
sale_order.id,
exc,
)
return sale_order
def _build_confirmation_message(self, sale_order, group_order, is_delivery): def _build_confirmation_message(self, sale_order, group_order, is_delivery):
"""Build localized confirmation message for confirm_eskaera.""" """Build localized confirmation message for confirm_eskaera."""
# Get pickup day index, localized name and date string using helper # Get pickup day index, localized name and date string using helper
@ -447,28 +379,6 @@ class AplicoopWebsiteSale(WebsiteSale):
request_obj=request, request_obj=request,
) )
def _slot_time_label(self, slot):
return _pickup._slot_time_label(self, slot)
def _format_datetime_to_str(self, dt_val):
return _pickup._format_datetime_to_str(self, dt_val)
def _format_slot_pickup_info(self, group_order, slot):
return _pickup._format_slot_pickup_info(
self,
group_order,
slot,
request_obj=request,
)
def _format_legacy_pickup_info(self, group_order, is_delivery):
return _pickup._format_legacy_pickup_info(
self,
group_order,
is_delivery,
request_obj=request,
)
def _parse_save_cart_request(self): def _parse_save_cart_request(self):
"""Decode and validate the incoming save-cart request. """Decode and validate the incoming save-cart request.
@ -1250,156 +1160,6 @@ class AplicoopWebsiteSale(WebsiteSale):
[("Content-Type", "application/json")], [("Content-Type", "application/json")],
) )
@http.route(
["/eskaera/add-to-cart"],
type="http",
auth="user",
website=True,
methods=["POST"],
csrf=False,
)
def add_to_eskaera_cart(self, **post):
"""Validate and confirm product addition to cart.
The cart is managed in localStorage on the frontend.
This endpoint only validates that the product exists in the order.
"""
import json
try:
# Get JSON data from the request body
data = (
json.loads(request.httprequest.data) if request.httprequest.data else {}
)
order_id = int(data.get("order_id", 0))
product_id = int(data.get("product_id", 0))
quantity = float(data.get("quantity", 1))
group_order = request.env["group.order"].sudo().browse(order_id)
product = request.env["product.product"].sudo().browse(product_id)
# Validate that the order exists and is open
if not group_order.exists() or group_order.state != "open":
_logger.warning(
"add_to_eskaera_cart: Order %d not available (exists=%s, state=%s)",
order_id,
group_order.exists(),
group_order.state if group_order.exists() else "N/A",
)
return request.make_response(
json.dumps({"error": "Order is not available"}),
[("Content-Type", "application/json")],
)
if placed_response := self._build_already_placed_response(
request.env.user.partner_id.id, group_order
):
return placed_response
# Validate that the product is available in this order (use discovery logic)
available_products = group_order._get_products_for_group_order(
group_order.id
)
if product not in available_products:
_logger.warning(
"add_to_eskaera_cart: Product %d not available in order %d",
product_id,
order_id,
)
return request.make_response(
json.dumps({"error": "Product not available in this order"}),
[("Content-Type", "application/json")],
)
# Validate quantity
if quantity <= 0:
return request.make_response(
json.dumps({"error": "Quantity must be greater than 0"}),
[("Content-Type", "application/json")],
)
_logger.info(
"add_to_eskaera_cart: Added product %d (qty=%f) to order %d",
product_id,
quantity,
order_id,
)
# Get price with taxes using pricelist
_logger.info(
"add_to_eskaera_cart: Getting price for product %s (id=%s)",
product.name,
product_id,
)
pricelist = None
# Resolve pricelist using centralized helper
pricelist = self._resolve_pricelist()
if not pricelist:
_logger.error(
"add_to_eskaera_cart: ERROR - No pricelist found! Using list_price for product %s",
product.name,
)
product_variant = (
product.product_variant_ids[0] if product.product_variant_ids else False
)
if product_variant and pricelist:
try:
# Use OCA _get_price method - more robust and complete
price_info = product_variant._get_price(
qty=quantity,
pricelist=pricelist,
fposition=request.website.fiscal_position_id,
)
price_with_tax = price_info.get("value", product.list_price)
_logger.info(
"add_to_eskaera_cart: Product %s - Price: %.2f (original: %.2f, discount: %.1f%%)",
product.name,
price_with_tax,
price_info.get("original_value", 0),
price_info.get("discount", 0),
)
except Exception as e:
_logger.warning(
"add_to_eskaera_cart: Error getting price for product %s: %s. Using list_price=%.2f",
product.name,
str(e),
product.list_price,
)
else:
reason = "no pricelist" if not pricelist else "no variant"
_logger.info(
"add_to_eskaera_cart: Product %s - Using list_price fallback (reason: %s). Price=%.2f",
product.name,
reason,
price_with_tax,
)
response_data = {
"success": True,
"message": request.env._("%s added to cart", product.name),
"product_id": product_id,
"quantity": quantity,
"price": price_with_tax,
}
return request.make_response(
json.dumps(response_data), [("Content-Type", "application/json")]
)
except ValueError as e:
_logger.error("add_to_eskaera_cart: ValueError: %s", str(e))
return request.make_response(
json.dumps({"error": f"Invalid parameters: {str(e)}"}),
[("Content-Type", "application/json")],
)
except Exception as e:
_logger.error("add_to_eskaera_cart: Exception: %s", str(e), exc_info=True)
return request.make_response(json.dumps({"error": f"Error: {str(e)}"}))
@http.route( @http.route(
["/eskaera/<int:order_id>/checkout"], type="http", auth="user", website=True ["/eskaera/<int:order_id>/checkout"], type="http", auth="user", website=True
) )
@ -1797,130 +1557,6 @@ class AplicoopWebsiteSale(WebsiteSale):
[("Content-Type", "application/json")], [("Content-Type", "application/json")],
) )
@http.route(
["/eskaera/save-cart"],
type="http",
auth="user",
website=True,
methods=["POST"],
csrf=False,
)
def save_cart_draft(self, **post):
"""Save cart items as a draft sale.order with pickup date.
This controller delegates validation and heavy lifting to helpers
so the top-level flow remains easy to follow and McCabe-friendly.
"""
try:
_logger.warning("=== SAVE_CART_DRAFT CALLED ===")
try:
(
data,
order_id,
group_order,
current_user,
items,
pickup_date,
is_delivery,
) = self._parse_save_cart_request()
except BadRequestError as e:
return request.make_response(
json.dumps({"error": str(e)}),
[("Content-Type", "application/json")],
status=400,
)
except ForbiddenError as e:
return request.make_response(
json.dumps({"error": str(e)}),
[("Content-Type", "application/json")],
status=403,
)
except GroupOrderUnavailable:
order_id = None
try:
payload = self._decode_json_body()
order_id = (
int(payload.get("order_id"))
if payload.get("order_id")
else None
)
except Exception:
order_id = None
group_order = (
request.env["group.order"].sudo().browse(order_id)
if order_id
else False
)
return self._build_group_order_unavailable_response(group_order)
# Build sale.order lines and create draft using helpers
try:
sale_order_lines = self._process_cart_items(
items, group_order, pricelist=self._resolve_pricelist()
)
except ValueError as e:
return request.make_response(
json.dumps({"error": str(e)}),
[("Content-Type", "application/json")],
status=400,
)
sale_order = self._create_draft_sale_order(
group_order,
current_user,
sale_order_lines,
order_id,
pickup_date,
is_delivery=is_delivery,
)
_logger.info(
"Draft sale.order created: %d (name: %s) for partner %d",
sale_order.id,
sale_order.name,
current_user.partner_id.id,
)
# Compute a readable pickup slot label for the response. Prefer the
# order's stored computed label, otherwise derive from the group
# order using the same helper the confirmation flow uses.
pickup_slot_label = (
sale_order.pickup_slot_label
if getattr(sale_order, "pickup_slot_label", False)
else None
)
if not pickup_slot_label:
try:
pickup_slot_label = self._format_pickup_info(
group_order, is_delivery
)[0]
except Exception:
pickup_slot_label = None
return request.make_response(
json.dumps(
{
"success": True,
"message": request.env._("Cart saved as draft"),
"sale_order_id": sale_order.id,
"pickup_slot_label": pickup_slot_label,
}
),
[("Content-Type", "application/json")],
)
except Exception as e:
import traceback
_logger.error("save_cart_draft: Unexpected error: %s", str(e))
_logger.error(traceback.format_exc())
return request.make_response(
json.dumps({"error": str(e)}),
[("Content-Type", "application/json")],
status=500,
)
@http.route( @http.route(
["/eskaera/load-draft"], ["/eskaera/load-draft"],
type="http", type="http",
@ -2723,107 +2359,6 @@ class AplicoopWebsiteSale(WebsiteSale):
_logger.error(traceback.format_exc()) _logger.error(traceback.format_exc())
return {"success": False, "error": f"Error confirming order: {str(e)}"} return {"success": False, "error": f"Error confirming order: {str(e)}"}
def _translate_labels(self, labels_dict, lang):
"""Manually translate labels based on user language.
This is a fallback translation method for when Odoo's translation system
hasn't loaded translations from .po files properly.
"""
translations = {
"es_ES": {
"Draft Already Exists": "El Borrador Ya Existe",
"A saved draft already exists for the current order period.": "Un borrador guardado ya existe para el período actual del pedido.",
"You have two options:": "Tienes dos opciones:",
"Option 1: Merge with Existing Draft": "Opción 1: Fusionar con Borrador Existente",
"Combine your current cart with the existing draft.": "Combina tu carrito actual con el borrador existente.",
"Existing draft has": "El borrador existente tiene",
"Current cart has": "Tu carrito actual tiene",
"item(s)": "artículo(s)",
"Products will be merged by adding quantities. If a product exists in both, quantities will be combined.": "Los productos se fusionarán sumando cantidades. Si un producto existe en ambos, las cantidades se combinarán.",
"Option 2: Replace with Current Cart": "Opción 2: Reemplazar con Carrito Actual",
"Delete the old draft and save only the current cart items.": "Elimina el borrador anterior y guarda solo los artículos del carrito actual.",
"The existing draft will be permanently deleted.": "El borrador existente se eliminará permanentemente.",
"Merge": "Fusionar",
"Replace": "Reemplazar",
"Cancel": "Cancelar",
# Checkout page labels
"Home Delivery": "Entrega a Domicilio",
"Delivery Information": "Información de Entrega",
"Your order will be delivered the day after pickup between 11:00 - 14:00": "Tu pedido será entregado el día después de la recogida entre las 11:00 - 14:00",
"Important": "Importante",
"Once you confirm this order, you will not be able to modify it. Please review carefully before confirming.": "Una vez confirmes este pedido, no podrás modificarlo. Por favor, revisa cuidadosamente antes de confirmar.",
},
"eu_ES": {
"Draft Already Exists": "Zirriborro Dagoeneko Badago",
"A saved draft already exists for the current order period.": "Gordetako zirriborro bat dagoeneko badago uneko eskaera-aldirako.",
"You have two options:": "Bi aukera dituzu:",
"Option 1: Merge with Existing Draft": "1. Aukera: Existentea Duen Zirriborroarekin Batu",
"Combine your current cart with the existing draft.": "Batu zure gaur-oraingo saskia existentea duen zirriborroarekin.",
"Existing draft has": "Existentea duen zirriborroak du",
"Current cart has": "Zure gaur-oraingo saskiak du",
"item(s)": "artikulu(a)",
"Products will be merged by adding quantities. If a product exists in both, quantities will be combined.": "Produktuak batuko dira kantitateak gehituz. Produktu bat bian badago, kantitateak konbinatuko dira.",
"Option 2: Replace with Current Cart": "2. Aukera: Gaur-oraingo Askiarekin Ordeztu",
"Delete the old draft and save only the current cart items.": "Ezabatu zahar-zirriborroa eta gorde soilik gaur-oraingo saskiaren artikulua.",
"The existing draft will be permanently deleted.": "Existentea duen zirriborroa behin betiko ezabatuko da.",
"Merge": "Batu",
"Replace": "Ordeztu",
"Cancel": "Ezeztatu",
# Checkout page labels
"Home Delivery": "Etxera Bidalketa",
"Delivery Information": "Bidalketaren Informazioa",
"Your order will be delivered the day after pickup between 11:00 - 14:00": "Zure eskaera bidaliko da biltzeko eguaren ondoren 11:00 - 14:00 bitartean",
"Important": "Garrantzitsua",
"Once you confirm this order, you will not be able to modify it. Please review carefully before confirming.": "Behin eskaera hau berretsi ondoren, ezin izango duzu aldatu. Mesedez, arretaz berrikusi berretsi aurretik.",
},
# Also support 'eu' as a variant
"eu": {
"Draft Already Exists": "Zirriborro Dagoeneko Badago",
"A saved draft already exists for the current order period.": "Gordetako zirriborro bat dagoeneko badago uneko eskaera-aldirako.",
"You have two options:": "Bi aukera dituzu:",
"Option 1: Merge with Existing Draft": "1. Aukera: Existentea Duen Zirriborroarekin Batu",
"Combine your current cart with the existing draft.": "Batu zure gaur-oraingo saskia existentea duen zirriborroarekin.",
"Existing draft has": "Existentea duen zirriborroak du",
"Current cart has": "Zure gaur-oraingo saskiak du",
"item(s)": "artikulu(a)",
"Products will be merged by adding quantities. If a product exists in both, quantities will be combined.": "Produktuak batuko dira kantitateak gehituz. Produktu bat bian badago, kantitateak konbinatuko dira.",
"Option 2: Replace with Current Cart": "2. Aukera: Gaur-oraingo Askiarekin Ordeztu",
"Delete the old draft and save only the current cart items.": "Ezabatu zahar-zirriborroa eta gorde soilik gaur-oraingo saskiaren artikulua.",
"The existing draft will be permanently deleted.": "Existentea duen zirriborroa behin betiko ezabatuko da.",
"Merge": "Batu",
"Replace": "Ordeztu",
"Cancel": "Ezeztatu",
# Checkout page labels
"Home Delivery": "Etxera Bidalketa",
"Delivery Information": "Bidalketaren Informazioa",
"Your order will be delivered the day after pickup between 11:00 - 14:00": "Zure eskaera bidaliko da biltzeko eguaren ondoren 11:00 - 14:00 bitartean",
"Important": "Garrantzitsua",
"Once you confirm this order, you will not be able to modify it. Please review carefully before confirming.": "Behin eskaera hau berretsi ondoren, ezin izango duzu aldatu. Mesedez, arretaz berrikusi berretsi aurretik.",
},
}
# Get the translation dictionary for the user's language
# Try exact match first, then try without the region code (e.g., 'eu' from 'eu_ES')
lang_translations = translations.get(lang)
if not lang_translations and "_" in lang:
lang_code = lang.split("_")[0] # Get 'eu' from 'eu_ES'
lang_translations = translations.get(lang_code, {})
if not lang_translations:
lang_translations = {}
# Translate all English labels to the target language
translated = {}
for key, english_label in labels_dict.items():
translated[key] = lang_translations.get(english_label, english_label)
_logger.info(
"[_translate_labels] Language: %s, Translated %d labels",
lang,
len(translated),
)
return translated
@http.route( @http.route(
["/eskaera/labels", "/eskaera/i18n"], ["/eskaera/labels", "/eskaera/i18n"],
type="json", type="json",

View file

@ -149,18 +149,3 @@ def _get_translated_labels(self, lang=None, request_obj=None):
} }
return labels return labels
def _translate_labels(self, labels_dict, lang):
# Minimal fallback translator kept here; prefers env translations
translations = {
"es_ES": {},
}
lang_translations = translations.get(lang, {})
translated = {}
for key, english_label in labels_dict.items():
translated[key] = lang_translations.get(english_label, english_label)
_logger.info(
"[_translate_labels] Language: %s, Translated %d labels", lang, len(translated)
)
return translated

View file

@ -6,6 +6,7 @@ import re
from datetime import timedelta from datetime import timedelta
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import api from odoo import api
from odoo import fields from odoo import fields
from odoo import models from odoo import models
@ -283,23 +284,6 @@ class GroupOrder(models.Model):
help="Image displayed alongside the consumer group order name", help="Image displayed alongside the consumer group order name",
attachment=True, attachment=True,
) )
display_image = fields.Binary(
compute="_compute_display_image",
store=True,
help="Image to display: uses consumer group order image if set, otherwise group image",
attachment=True,
)
@api.depends("image", "group_ids")
def _compute_display_image(self):
"""Use order image if set, otherwise use first group image."""
for record in self:
if record.image:
record.display_image = record.image
elif record.group_ids and record.group_ids[0].image_1920:
record.display_image = record.group_ids[0].image_1920
else:
record.display_image = False
@api.depends("delivery_product_id") @api.depends("delivery_product_id")
def _compute_home_delivery(self): def _compute_home_delivery(self):
@ -700,31 +684,6 @@ class GroupOrder(models.Model):
) )
return demand return demand
def _get_products_paginated(self, order_id, page=1, per_page=20):
"""Get paginated products for a group order.
Args:
order_id: ID of the group order
page: Page number (1-indexed)
per_page: Number of products per page
Returns:
tuple: (products_page, total_count, has_next)
- products_page: recordset of product.product for this page
- total_count: total number of products in order
- has_next: boolean indicating if there are more pages
"""
all_products = self._get_products_for_group_order(order_id)
total_count = len(all_products)
# Calculate pagination
offset = (page - 1) * per_page
products_page = all_products[offset : offset + per_page]
has_next = offset + per_page < total_count
return products_page, total_count, has_next
# === Pickup slots helpers === # === Pickup slots helpers ===
pickup_slot_ids = fields.One2many( pickup_slot_ids = fields.One2many(
"group.order.slot", "group.order.slot",
@ -734,12 +693,6 @@ class GroupOrder(models.Model):
tracking=True, tracking=True,
) )
pickup_slots_count = fields.Integer(
compute="_compute_pickup_slots_count",
store=False,
help="Number of pickup slots configured for this order",
)
next_pickup_slot_id = fields.Many2one( next_pickup_slot_id = fields.Many2one(
"group.order.slot", "group.order.slot",
string="Next Pickup Slot", string="Next Pickup Slot",
@ -755,12 +708,6 @@ class GroupOrder(models.Model):
help="Datetime of the next pickup occurrence for the selected slot", help="Datetime of the next pickup occurrence for the selected slot",
) )
@api.depends("pickup_slot_ids")
def _compute_pickup_slots_count(self):
"""Simple count of configured slots for quick UI badges."""
for record in self:
record.pickup_slots_count = len(record.pickup_slot_ids or [])
@api.depends( @api.depends(
"pickup_slot_ids", "pickup_slot_ids",
"pickup_slot_ids.start_hour", "pickup_slot_ids.start_hour",
@ -779,7 +726,8 @@ class GroupOrder(models.Model):
- If no slots are configured, leave fields empty (fallback handled - If no slots are configured, leave fields empty (fallback handled
by existing pickup_day logic). by existing pickup_day logic).
""" """
from datetime import datetime, time from datetime import datetime
from datetime import time
for record in self: for record in self:
record.next_pickup_slot_id = False record.next_pickup_slot_id = False
@ -1420,7 +1368,8 @@ class GroupOrder(models.Model):
return return
failure_reasons = failure_reasons or {} failure_reasons = failure_reasons or {}
from markupsafe import Markup, escape from markupsafe import Markup
from markupsafe import escape
items = Markup() items = Markup()
for sale_order in failed_sale_orders: for sale_order in failed_sale_orders:

View file

@ -55,20 +55,3 @@ class GroupOrderSlot(models.Model):
sequence = fields.Integer(string="Sequence", default=10) sequence = fields.Integer(string="Sequence", default=10)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
def _get_display_label(self):
"""Return a fallback display label combining weekday and hours.
This is a small helper used by views or when a specific `label` is
not provided.
"""
self.ensure_one()
if self.label:
return self.label
# Fallback: simple numeric representation
sh = "%02d:%02d" % (
int(self.start_hour or 0),
int((self.start_hour or 0) % 1 * 60),
)
eh = "%02d:%02d" % (int(self.end_hour or 0), int((self.end_hour or 0) % 1 * 60))
return f"{self.weekday} {sh}-{eh}"

View file

@ -34,15 +34,3 @@ class ResConfigSettings(models.TransientModel):
help="Products with stock below or equal to this value will show 'Low Stock' ribbon. " help="Products with stock below or equal to this value will show 'Low Stock' ribbon. "
"Products with stock = 0 will show 'Out of Stock' ribbon and cannot be added to cart.", "Products with stock = 0 will show 'Out of Stock' ribbon and cannot be added to cart.",
) )
@staticmethod
def _get_products_per_page_selection(records):
"""Return default page sizes."""
return [
(5, "5"),
(10, "10"),
(15, "15"),
(20, "20"),
(30, "30"),
(50, "50"),
]

View file

@ -1,9 +0,0 @@
/** AGPL-3.0
* NOTE: Checkout summary rendering is now handled by checkout_labels.js
* This file is kept for backwards compatibility but is no longer needed.
* The main renderSummary() logic is in checkout_labels.js
*/
(function () {
"use strict";
// Checkout rendering is handled by checkout_labels.js
})();

View file

@ -226,10 +226,6 @@
}, 50); }, 50);
}, },
renderCheckoutSummary: function () {
// Stub - now handled by global window.renderCheckoutSummary
},
addDeliveryProduct: function () { addDeliveryProduct: function () {
if (!this.deliveryProductId) { if (!this.deliveryProductId) {
console.warn("[HomeDelivery] Delivery product ID not found"); console.warn("[HomeDelivery] Delivery product ID not found");

View file

@ -9,9 +9,6 @@
* OLD: window.getCheckoutLabels() * OLD: window.getCheckoutLabels()
* NEW: i18nManager.getAll() * NEW: i18nManager.getAll()
* *
* OLD: window.formatCurrency(amount)
* NEW: i18nManager.formatCurrency(amount)
*
* Copyright 2025 Criptomart * Copyright 2025 Criptomart
* License AGPL-3.0 or later * License AGPL-3.0 or later
*/ */
@ -35,32 +32,5 @@
return key ? key : {}; return key ? key : {};
}; };
/**
* DEPRECATED - Use i18nManager.getAll() instead
*/
window.getSearchLabels = function () {
if (window.i18nManager && window.i18nManager.initialized) {
return {
searchPlaceholder: window.i18nManager.get("search_products"),
noResults: window.i18nManager.get("no_results"),
};
}
return {
searchPlaceholder: "Search products...",
noResults: "No products found",
};
};
/**
* DEPRECATED - Use i18nManager.formatCurrency(amount) instead
*/
window.formatCurrency = function (amount) {
if (window.i18nManager) {
return window.i18nManager.formatCurrency(amount);
}
// Fallback
return "€" + parseFloat(amount).toFixed(2);
};
console.log("[i18n_helpers] DEPRECATED - Use i18n_manager.js instead"); console.log("[i18n_helpers] DEPRECATED - Use i18n_manager.js instead");
})(); })();

View file

@ -103,39 +103,6 @@
} }
return this.labels; return this.labels;
}, },
/**
* Check if a specific label exists
*/
has: function (key) {
if (!this.initialized) return false;
return key in this.labels;
},
/**
* Format currency to Euro format
*/
formatCurrency: function (amount) {
try {
return new Intl.NumberFormat(document.documentElement.lang || "es_ES", {
style: "currency",
currency: "EUR",
}).format(amount);
} catch (e) {
// Fallback to simple Euro format
return "€" + parseFloat(amount).toFixed(2);
}
},
/**
* Escape HTML to prevent XSS
*/
escapeHtml: function (text) {
if (!text) return "";
var div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
},
}; };
// Auto-initialize on DOM ready // Auto-initialize on DOM ready

View file

@ -1371,160 +1371,6 @@
}; };
}, },
/**
* Update DOM elements with translated labels
*/
_updateDOMLabels: function (labels) {
console.log("[UPDATE_LABELS] Starting DOM update with labels:", labels);
// Map of element ID to label key
var elementLabelMap = {
"label-home-delivery": "home_delivery",
"label-delivery-information": "delivery_information",
"label-important": "important",
"label-confirm-warning": "confirm_order_warning",
};
// Update each element
for (var elementId in elementLabelMap) {
var element = document.getElementById(elementId);
var labelKey = elementLabelMap[elementId];
var translatedText = labels[labelKey];
console.log(
"[UPDATE_LABELS] Element:",
elementId,
"| Exists:",
!!element,
"| Label Key:",
labelKey,
"| Translated:",
translatedText
);
if (element && translatedText) {
var oldText = element.textContent;
element.textContent = translatedText;
console.log(
"[UPDATE_LABELS] ✅ Updated #" +
elementId +
': "' +
oldText +
'" → "' +
translatedText +
'"'
);
} else if (!element) {
console.log("[UPDATE_LABELS] ❌ Element not found: #" + elementId);
} else if (!translatedText) {
console.log(
"[UPDATE_LABELS] ❌ Label not found: " +
labelKey +
" (available keys: " +
Object.keys(labels).join(", ") +
")"
);
}
}
// Update delivery day text if available
if (
window.groupOrderShop &&
window.groupOrderShop.labels &&
window.groupOrderShop.labels.delivery_info_template
) {
var deliveryDayText = document.getElementById("delivery-day-text");
console.log("[UPDATE_LABELS] Delivery day text element exists:", !!deliveryDayText);
if (deliveryDayText) {
// Get delivery data from window.deliveryData first, then fallback to attributes
var pickupDayIndex = "";
var pickupDate = "";
var deliveryNotice = "";
if (window.deliveryData) {
console.log(
"[UPDATE_LABELS] Using window.deliveryData:",
window.deliveryData
);
pickupDayIndex = window.deliveryData.pickupDay || "";
pickupDate = window.deliveryData.pickupDate || "";
deliveryNotice = window.deliveryData.deliveryNotice || "";
} else {
console.log(
"[UPDATE_LABELS] window.deliveryData not found, using data attributes"
);
var wrap = document.getElementById("wrap");
pickupDayIndex = wrap ? wrap.getAttribute("data-pickup-day") : "";
pickupDate = wrap ? wrap.getAttribute("data-pickup-date") : "";
deliveryNotice = wrap ? wrap.getAttribute("data-delivery-notice") : "";
}
// Normalize: convert "undefined" strings and null to empty for processing
if (pickupDayIndex === "undefined" || pickupDayIndex === null)
pickupDayIndex = "";
if (pickupDate === "undefined" || pickupDate === null) pickupDate = "";
if (deliveryNotice === "undefined" || deliveryNotice === null)
deliveryNotice = "";
console.log("[UPDATE_LABELS] Delivery data (final):", {
pickupDayIndex: pickupDayIndex,
pickupDate: pickupDate,
deliveryNotice: deliveryNotice,
});
// Day names mapping
var dayNames = {
0: "Monday",
1: "Tuesday",
2: "Wednesday",
3: "Thursday",
4: "Friday",
5: "Saturday",
6: "Sunday",
};
// Get translated day names if available
if (window.groupOrderShop && window.groupOrderShop.day_names) {
dayNames = window.groupOrderShop.day_names;
}
// Get the day name from index
var pickupDayName =
pickupDayIndex && dayNames[pickupDayIndex]
? dayNames[pickupDayIndex]
: pickupDayIndex;
// Build message from template
var msg = window.groupOrderShop.labels.delivery_info_template;
msg = msg.replace("{pickup_day}", pickupDayName);
msg = msg.replace("{pickup_date}", pickupDate);
console.log("[UPDATE_LABELS] Built delivery message:", msg);
// Build final HTML output
var htmlOutput = msg;
if (deliveryNotice) {
// Replace newlines with <br> tags for HTML display
htmlOutput =
msg.replace(/\n/g, "<br>") +
"<br><br>" +
deliveryNotice.replace(/\n/g, "<br>");
console.log("[UPDATE_LABELS] Final HTML with notice:", htmlOutput);
} else {
htmlOutput = msg.replace(/\n/g, "<br>");
}
deliveryDayText.innerHTML = htmlOutput;
console.log(
"[UPDATE_LABELS] ✅ Updated delivery day text with translated template"
);
}
} else {
console.log("[UPDATE_LABELS] ❌ delivery_info_template label not found");
}
},
_attachLoadMoreListener: function () { _attachLoadMoreListener: function () {
var self = this; var self = this;
var btn = document.getElementById("load-more-btn"); var btn = document.getElementById("load-more-btn");

View file

@ -1,667 +0,0 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""
Test suite for cart/draft persistence in website_sale_aplicoop.
Coverage:
- Save draft order (empty, with items)
- Load draft order
- Draft consistency (prices don't change unexpectedly)
- Product archived in draft (handling)
- Merge inconsistent drafts
- Draft timeline (very old draft, recent draft)
"""
from datetime import datetime
from datetime import timedelta
from odoo.tests.common import TransactionCase
class TestSaveDraftOrder(TransactionCase):
"""Test saving draft orders."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
self.product1 = self.env["product.product"].create(
{
"name": "Product 1",
"type": "consu",
"list_price": 10.0,
"categ_id": self.category.id,
}
)
self.product2 = self.env["product.product"].create(
{
"name": "Product 2",
"type": "consu",
"list_price": 20.0,
"categ_id": self.category.id,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"pickup_date": start_date + timedelta(days=3),
"cutoff_day": "0",
}
)
self.group_order.action_open()
self.group_order.product_ids = [(4, self.product1.id), (4, self.product2.id)]
def test_save_draft_with_items(self):
"""Test saving draft order with products."""
draft_order = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
"order_line": [
(
0,
0,
{
"product_id": self.product1.id,
"product_qty": 2,
"price_unit": self.product1.list_price,
},
),
(
0,
0,
{
"product_id": self.product2.id,
"product_qty": 1,
"price_unit": self.product2.list_price,
},
),
],
}
)
self.assertTrue(draft_order.exists())
self.assertEqual(draft_order.state, "draft")
self.assertEqual(len(draft_order.order_line), 2)
def test_save_draft_empty_order(self):
"""Test saving draft order without items."""
# Edge case: empty draft
empty_draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
"order_line": [],
}
)
# Should be valid (user hasn't added products yet)
self.assertTrue(empty_draft.exists())
self.assertEqual(len(empty_draft.order_line), 0)
def test_save_draft_updates_existing(self):
"""Test that saving draft updates existing draft, not creates new."""
# Create initial draft
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
"order_line": [
(
0,
0,
{
"product_id": self.product1.id,
"product_qty": 1,
},
)
],
}
)
draft_id = draft.id
# Simulate "save" with different quantity
draft.order_line[0].product_qty = 5
# Should be same draft, not new one
updated_draft = self.env["sale.order"].browse(draft_id)
self.assertTrue(updated_draft.exists())
self.assertEqual(updated_draft.order_line[0].product_qty, 5)
def test_save_draft_preserves_group_order_reference(self):
"""Test that group_order_id is preserved when saving."""
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
}
)
# Link must be preserved
self.assertEqual(draft.group_order_id, self.group_order)
def test_save_draft_preserves_pickup_date(self):
"""Test that pickup_date is preserved in draft."""
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"pickup_date": self.group_order.pickup_date,
"state": "draft",
}
)
self.assertEqual(draft.pickup_date, self.group_order.pickup_date)
class TestLoadDraftOrder(TransactionCase):
"""Test loading (retrieving) draft orders."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"list_price": 10.0,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.group_order.action_open()
def test_load_existing_draft(self):
"""Test loading an existing draft order."""
# Create draft
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_qty": 3,
},
)
],
}
)
# Load it
loaded = self.env["sale.order"].search(
[
("id", "=", draft.id),
("partner_id", "=", self.member_partner.id),
("state", "=", "draft"),
]
)
self.assertEqual(len(loaded), 1)
self.assertEqual(loaded[0].order_line[0].product_qty, 3)
def test_load_draft_not_visible_to_other_user(self):
"""Test that draft from one user not accessible to another."""
# Create draft for member_partner
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
}
)
# Create another user/partner
other_partner = self.env["res.partner"].create(
{
"name": "Other Member",
"email": "other@test.com",
}
)
self.env["res.users"].create(
{
"name": "Other User",
"login": "other@test.com",
"partner_id": other_partner.id,
}
)
# Other user should not see original draft
other_drafts = self.env["sale.order"].search(
[
("id", "=", draft.id),
("partner_id", "=", other_partner.id),
]
)
self.assertEqual(len(other_drafts), 0)
def test_load_draft_from_expired_order(self):
"""Test loading draft from closed/expired group order."""
# Close the group order
self.group_order.action_close()
# Create draft before closure (simulated)
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
}
)
# Draft should still be loadable (but should warn)
loaded = self.env["sale.order"].browse(draft.id)
self.assertTrue(loaded.exists())
# Controller should check: group_order.state and warn if closed
class TestDraftConsistency(TransactionCase):
"""Test that draft prices remain consistent across saves."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"list_price": 100.0,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.group_order.action_open()
def test_draft_price_snapshot(self):
"""Test that draft captures price at time of save."""
original_price = self.product.list_price
# Save draft with current price
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_qty": 1,
"price_unit": original_price,
},
)
],
}
)
saved_price = draft.order_line[0].price_unit
# Change product price
self.product.list_price = 150.0
# Draft should still have original price
self.assertEqual(draft.order_line[0].price_unit, saved_price)
self.assertNotEqual(draft.order_line[0].price_unit, self.product.list_price)
def test_draft_quantity_consistency(self):
"""Test that quantities are preserved across saves."""
# Save draft
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_qty": 5,
},
)
],
}
)
# Re-load draft
reloaded = self.env["sale.order"].browse(draft.id)
self.assertEqual(reloaded.order_line[0].product_qty, 5)
class TestProductArchivedInDraft(TransactionCase):
"""Test handling when product in draft gets archived."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"list_price": 10.0,
"active": True,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.group_order.action_open()
def test_load_draft_with_archived_product(self):
"""Test loading draft when product has been archived."""
# Create draft with active product
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_qty": 2,
},
)
],
}
)
# Archive the product
self.product.active = False
# Load draft - should still work (historical data)
loaded = self.env["sale.order"].browse(draft.id)
self.assertTrue(loaded.exists())
# But product may not be editable/accessible
class TestDraftTimeline(TransactionCase):
"""Test very old vs recent drafts."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"list_price": 10.0,
}
)
def test_draft_from_current_week(self):
"""Test draft from current/open group order."""
start_date = datetime.now().date()
current_order = self.env["group.order"].create(
{
"name": "Current Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
current_order.action_open()
draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": current_order.id,
"state": "draft",
}
)
# Should be accessible and valid
self.assertTrue(draft.exists())
self.assertEqual(draft.group_order_id.state, "open")
def test_draft_from_old_order_6_months_ago(self):
"""Test draft from order that was 6 months ago."""
old_start = datetime.now().date() - timedelta(days=180)
old_end = old_start + timedelta(days=7)
old_order = self.env["group.order"].create(
{
"name": "Old Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": old_start,
"end_date": old_end,
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
old_order.action_open()
old_order.action_close()
old_draft = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": old_order.id,
"state": "draft",
}
)
# Should still exist but be inaccessible (order closed)
self.assertTrue(old_draft.exists())
self.assertEqual(old_order.state, "closed")
def test_draft_order_count_for_user(self):
"""Test counting total drafts for a user."""
# Create multiple orders and drafts
orders = []
for i in range(3):
start = datetime.now().date() + timedelta(days=i * 7)
order = self.env["group.order"].create(
{
"name": f"Order {i}",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": start + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
order.action_open()
orders.append(order)
# Create draft for each
for order in orders:
self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": order.id,
"state": "draft",
}
)
# Count drafts for user
user_drafts = self.env["sale.order"].search(
[
("partner_id", "=", self.member_partner.id),
("state", "=", "draft"),
]
)
self.assertEqual(len(user_drafts), 3)

View file

@ -1,506 +0,0 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""
Test suite for edge cases involving dates, times, and calendar calculations.
Coverage:
- Leap year (Feb 29) handling
- Long-duration orders (entire year)
- Pickup day boundary conditions
- Orders with future start dates
- Orders without end dates
- Extreme dates (year 1900, year 2099)
"""
from datetime import date
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo.exceptions import ValidationError # noqa: F401
from odoo.tests.common import TransactionCase
class TestLeapYearHandling(TransactionCase):
"""Test date calculations with leap year (Feb 29)."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
def test_order_spans_leap_day(self):
"""Test order that includes Feb 29 (leap year)."""
# 2024 is a leap year
start = date(2024, 2, 25)
end = date(2024, 3, 3) # Spans Feb 29
order = self.env["group.order"].create(
{
"name": "Leap Year Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "2", # Wednesday (Feb 28 or 29 depending on week)
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# Should correctly calculate pickup date
self.assertTrue(order.pickup_date)
def test_pickup_day_on_feb_29(self):
"""Test setting pickup_day to land on Feb 29."""
# 2024 Feb 29 is a Thursday (day 3)
start = date(2024, 2, 26) # Monday
end = date(2024, 3, 3)
order = self.env["group.order"].create(
{
"name": "Feb 29 Pickup",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "3", # Thursday = Feb 29
"cutoff_day": "0",
}
)
self.assertEqual(order.pickup_date, date(2024, 2, 29))
def test_order_before_leap_day(self):
"""Test order in non-leap year (no Feb 29)."""
# 2023 is NOT a leap year
start = date(2023, 2, 25)
end = date(2023, 3, 3)
order = self.env["group.order"].create(
{
"name": "Non-Leap Year Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "2",
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# Pickup should be Feb 28 (last day of Feb)
self.assertIn(order.pickup_date.month, [2, 3])
class TestLongDurationOrders(TransactionCase):
"""Test orders spanning very long periods."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
def test_order_spans_entire_year(self):
"""Test order running for 365 days."""
start = date(2024, 1, 1)
end = date(2024, 12, 31)
order = self.env["group.order"].create(
{
"name": "Year-Long Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "3", # Same day each week
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# Should handle 52+ weeks correctly
days_diff = (end - start).days
self.assertEqual(days_diff, 365)
def test_order_multiple_years(self):
"""Test order spanning multiple years (2+ years)."""
start = date(2024, 1, 1)
end = date(2026, 12, 31) # 3 years
order = self.env["group.order"].create(
{
"name": "Multi-Year Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "monthly",
"pickup_day": "15",
"cutoff_day": "10",
}
)
self.assertTrue(order.exists())
days_diff = (end - start).days
self.assertGreater(days_diff, 700) # More than 2 years
def test_order_one_day_duration(self):
"""Test order with start_date == end_date (single day)."""
same_day = date(2024, 2, 15)
order = self.env["group.order"].create(
{
"name": "One-Day Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "once",
"start_date": same_day,
"end_date": same_day,
"period": "once",
"pickup_day": "0",
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
class TestPickupDayBoundary(TransactionCase):
"""Test pickup_day calculations at boundaries."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
def test_pickup_day_same_as_start_date(self):
"""Test when pickup_day equals start date (today)."""
today = date.today()
start = today
end = today + timedelta(days=7)
order = self.env["group.order"].create(
{
"name": "Today Pickup",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": str(start.weekday()), # Same as start
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# Pickup should be today
self.assertEqual(order.pickup_date, start)
def test_pickup_day_last_day_of_month(self):
"""Test pickup day on last day of month (Jan 31, Feb 28/29, etc)."""
# Start on Jan 24, pickup on Jan 31
start = date(2024, 1, 24)
end = date(2024, 2, 1)
order = self.env["group.order"].create(
{
"name": "Month-End Pickup",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "once",
"pickup_day": "2", # Wednesday = Jan 31
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
def test_pickup_day_month_boundary(self):
"""Test when pickup crosses month boundary."""
# Start Jan 28, pickup might be in February
start = date(2024, 1, 28)
end = date(2024, 2, 5)
order = self.env["group.order"].create(
{
"name": "Month Boundary Pickup",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "4", # Friday (Feb 2)
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# Pickup should be in Feb
self.assertEqual(order.pickup_date.month, 2)
def test_all_seven_days_as_pickup(self):
"""Test each day of week (0-6) as valid pickup_day."""
start = date(2024, 1, 1) # Monday
end = date(2024, 1, 8)
for day_num in range(7):
order = self.env["group.order"].create(
{
"name": f"Pickup Day {day_num}",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": str(day_num),
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# Each should have valid pickup_date
self.assertTrue(order.pickup_date)
class TestFutureStartDateOrders(TransactionCase):
"""Test orders that start in the future."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
def test_order_starts_tomorrow(self):
"""Test order starting tomorrow."""
today = date.today()
start = today + timedelta(days=1)
end = start + timedelta(days=7)
order = self.env["group.order"].create(
{
"name": "Future Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
self.assertGreater(order.start_date, today)
def test_order_starts_6_months_future(self):
"""Test order starting 6 months from now."""
today = date.today()
start = today + relativedelta(months=6)
end = start + timedelta(days=30)
order = self.env["group.order"].create(
{
"name": "Far Future Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "monthly",
"pickup_day": "15",
"cutoff_day": "10",
}
)
self.assertTrue(order.exists())
class TestExtremeDate(TransactionCase):
"""Test edge cases with very old or very new dates."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
def test_order_year_2000(self):
"""Test order in year 2000 (Y2K edge case)."""
start = date(2000, 1, 1)
end = date(2000, 12, 31)
order = self.env["group.order"].create(
{
"name": "Y2K Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
def test_order_far_future_2099(self):
"""Test order in far future (year 2099)."""
start = date(2099, 1, 1)
end = date(2099, 12, 31)
order = self.env["group.order"].create(
{
"name": "Far Future Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
def test_order_crossing_century(self):
"""Test order spanning century boundary (Dec 1999 to Jan 2000)."""
start = date(1999, 12, 26)
end = date(2000, 1, 2)
order = self.env["group.order"].create(
{
"name": "Century Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "6", # Saturday
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# Should handle date arithmetic correctly across years
self.assertEqual(order.start_date.year, 1999)
self.assertEqual(order.end_date.year, 2000)
class TestOrderWithoutEndDate(TransactionCase):
"""Test orders without explicit end_date (permanent/ongoing)."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
def test_permanent_order_with_null_end_date(self):
"""Test order with end_date = NULL (ongoing order)."""
start = date.today()
self.env["group.order"].create(
{
"name": "Permanent Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": False, # No end date
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
# If supported, should handle gracefully
# Otherwise, may be optional validation
class TestPickupCalculationAccuracy(TransactionCase):
"""Test accuracy of pickup_date calculations."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
def test_pickup_date_calculation_multiple_weeks(self):
"""Test pickup_date calculation over multiple weeks."""
# Week 1: Jan 1-7 (Mon-Sun), pickup Thursday = Jan 4
start = date(2024, 1, 1)
end = date(2024, 1, 22)
order = self.env["group.order"].create(
{
"name": "Multi-Week Pickup",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "weekly",
"pickup_day": "3", # Thursday
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
# First pickup should be first Thursday on or after start
self.assertEqual(order.pickup_date.weekday(), 3)
def test_monthly_order_pickup_date(self):
"""Test pickup_date for monthly orders."""
# Order runs Feb 1 - Mar 31, pickup on 15th
start = date(2024, 2, 1)
end = date(2024, 3, 31)
order = self.env["group.order"].create(
{
"name": "Monthly Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start,
"end_date": end,
"period": "monthly",
"pickup_day": "15",
"cutoff_day": "10",
}
)
self.assertTrue(order.exists())
# First pickup should be Feb 15
self.assertGreaterEqual(order.pickup_date.day, 15)

View file

@ -1,613 +0,0 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""
Test suite for HTTP endpoints in website_sale_aplicoop controllers.
Coverage:
- /eskaera (GET) - View all group orders
- /eskaera/<id> (GET) - View specific group order
- /eskaera/<id>/add-to-cart (POST) - Add product to cart
- /eskaera/<id>/checkout (GET) - Checkout page
- /eskaera/<id>/checkout (POST) - Save cart items
- /eskaera/confirm (POST) - Confirm order
- /eskaera/<id>/confirm/<sale_id> (POST) - Confirm order from portal
- /eskaera/<id>/load-from-history/<sale_id> (POST) - Load draft order
- /eskaera/labels (GET) - Get translated labels
"""
from datetime import datetime
from datetime import timedelta
from odoo.exceptions import AccessError # noqa: F401
from odoo.exceptions import ValidationError # noqa: F401
from odoo.tests.common import HttpCase # noqa: F401
from odoo.tests.common import TransactionCase
class TestEskaearaListEndpoint(TransactionCase):
"""Test /eskaera endpoint (list all group orders)."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
"email": "group@test.com",
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
# Create multiple group orders (some open, some closed)
start_date = datetime.now().date()
self.open_order = self.env["group.order"].create(
{
"name": "Open Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.open_order.action_open()
self.draft_order = self.env["group.order"].create(
{
"name": "Draft Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date - timedelta(days=14),
"end_date": start_date - timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
# Stay in draft
self.closed_order = self.env["group.order"].create(
{
"name": "Closed Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date - timedelta(days=21),
"end_date": start_date - timedelta(days=14),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.closed_order.action_open()
self.closed_order.action_close()
def test_eskaera_list_shows_only_open_and_draft_orders(self):
"""Test that /eskaera shows only open/draft orders, not closed."""
# In controller context, only open and draft should be visible to members
# This is business logic: closed orders are historical
visible_orders = self.env["group.order"].search(
[
("state", "in", ["open", "draft"]),
("group_ids", "in", self.group.id),
]
)
self.assertIn(self.open_order, visible_orders)
self.assertIn(self.draft_order, visible_orders)
self.assertNotIn(self.closed_order, visible_orders)
def test_eskaera_list_filters_by_user_groups(self):
"""Test that user only sees orders from their groups."""
other_group = self.env["res.partner"].create(
{
"name": "Other Group",
"is_company": True,
"email": "other@test.com",
}
)
other_order = self.env["group.order"].create(
{
"name": "Other Group Order",
"group_ids": [(6, 0, [other_group.id])],
"type": "regular",
"start_date": datetime.now().date(),
"end_date": datetime.now().date() + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
other_order.action_open()
# User should not see orders from groups they're not in
user_groups = self.member_partner.group_ids
visible_orders = self.env["group.order"].search(
[
("state", "in", ["open", "draft"]),
("group_ids", "in", user_groups.ids),
]
)
self.assertNotIn(other_order, visible_orders)
class TestAddToCartEndpoint(TransactionCase):
"""Test /eskaera/<id>/add-to-cart endpoint."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
"email": "group@test.com",
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
# Published product
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"list_price": 10.0,
"categ_id": self.category.id,
"sale_ok": True,
"is_published": True,
}
)
# Unpublished product (should not be available)
self.unpublished_product = self.env["product.product"].create(
{
"name": "Unpublished Product",
"type": "consu",
"list_price": 15.0,
"categ_id": self.category.id,
"sale_ok": False,
"is_published": False,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.group_order.action_open()
self.group_order.product_ids = [(4, self.product.id)]
def test_add_to_cart_published_product(self):
"""Test adding published product to cart."""
# Simulate controller logic
cart_line = {
"product_id": self.product.id,
"quantity": 2,
"group_order_id": self.group_order.id,
"partner_id": self.member_partner.id,
}
# Should succeed
self.assertTrue(cart_line["product_id"])
def test_add_to_cart_zero_quantity(self):
"""Test that adding zero quantity is rejected."""
# Edge case: quantity = 0
quantity = 0
# Controller should validate: quantity > 0
self.assertFalse(quantity > 0)
def test_add_to_cart_negative_quantity(self):
"""Test that negative quantity is rejected."""
quantity = -5
# Controller should validate: quantity > 0
self.assertFalse(quantity > 0)
def test_add_to_cart_unpublished_product(self):
"""Test that unpublished products cannot be added."""
# Product must be published and sale_ok=True
self.assertFalse(self.unpublished_product.is_published)
self.assertFalse(self.unpublished_product.sale_ok)
def test_add_to_cart_product_not_in_order(self):
"""Test that products not in the order cannot be added."""
# Create a product NOT associated with group_order
other_product = self.env["product.product"].create(
{
"name": "Other Product",
"type": "consu",
"list_price": 25.0,
}
)
# Controller should check: product in group_order.product_ids
self.assertNotIn(other_product, self.group_order.product_ids)
def test_add_to_cart_order_closed(self):
"""Test that adding to closed order is rejected."""
self.group_order.action_close()
# Controller should check: order.state == 'open'
self.assertEqual(self.group_order.state, "closed")
class TestCheckoutEndpoint(TransactionCase):
"""Test /eskaera/<id>/checkout endpoint."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
"email": "group@test.com",
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"pickup_date": start_date + timedelta(days=3),
"cutoff_day": "0",
}
)
self.group_order.action_open()
def test_checkout_page_loads(self):
"""Test that checkout page renders correctly."""
# Controller should render template with group_order context
self.assertTrue(self.group_order.exists())
def test_checkout_displays_pickup_date(self):
"""Test that checkout shows correct pickup date."""
# Controller should calculate pickup_date from pickup_day
self.assertTrue(self.group_order.pickup_date)
def test_checkout_displays_home_delivery_option(self):
"""Test that checkout shows home delivery option."""
# Controller should pass home_delivery flag to template
self.assertIsNotNone(self.group_order.home_delivery)
def test_checkout_order_without_products(self):
"""Test checkout when no products available."""
# Order with empty product_ids
empty_order = self.env["group.order"].create(
{
"name": "Empty Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": datetime.now().date(),
"end_date": datetime.now().date() + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
empty_order.action_open()
# Should handle gracefully
self.assertEqual(len(empty_order.product_ids), 0)
class TestConfirmOrderEndpoint(TransactionCase):
"""Test /eskaera/confirm endpoint (confirm final order)."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
"email": "group@test.com",
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"list_price": 10.0,
"categ_id": self.category.id,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"pickup_date": start_date + timedelta(days=3),
"cutoff_day": "0",
}
)
self.group_order.action_open()
self.group_order.product_ids = [(4, self.product.id)]
# Create a draft sale order
self.draft_sale = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"pickup_date": self.group_order.pickup_date,
"state": "draft",
}
)
def test_confirm_order_creates_sale_order(self):
"""Test that confirming creates a confirmed sale.order."""
# Controller should change state from draft to sale
self.draft_sale.action_confirm()
self.assertEqual(self.draft_sale.state, "sale")
def test_confirm_empty_order(self):
"""Test confirming order without items fails."""
# Order with no order_lines should fail
empty_sale = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
}
)
# Should validate: must have at least one line
self.assertEqual(len(empty_sale.order_line), 0)
def test_confirm_order_wrong_group(self):
"""Test that user cannot confirm order from different group."""
other_group = self.env["res.partner"].create(
{
"name": "Other Group",
"is_company": True,
}
)
self.env["group.order"].create(
{
"name": "Other Order",
"group_ids": [(6, 0, [other_group.id])],
"type": "regular",
"start_date": datetime.now().date(),
"end_date": datetime.now().date() + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
# User should not be in other_group
self.assertNotIn(self.member_partner, other_group.member_ids)
class TestLoadDraftEndpoint(TransactionCase):
"""Test /eskaera/<id>/load-from-history/<sale_id> endpoint."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
"email": "group@test.com",
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member_partner.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member_partner.id,
}
)
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "consu",
"list_price": 10.0,
"categ_id": self.category.id,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"pickup_date": start_date + timedelta(days=3),
"cutoff_day": "0",
}
)
self.group_order.action_open()
self.group_order.product_ids = [(4, self.product.id)]
def test_load_draft_from_history(self):
"""Test loading a previous draft order."""
# Create old draft sale
old_sale = self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
}
)
# Should be able to load
self.assertTrue(old_sale.exists())
def test_load_draft_not_owned_by_user(self):
"""Test that user cannot load draft from other user."""
other_partner = self.env["res.partner"].create(
{
"name": "Other Member",
"email": "other@test.com",
}
)
other_sale = self.env["sale.order"].create(
{
"partner_id": other_partner.id,
"group_order_id": self.group_order.id,
"state": "draft",
}
)
# User should not be able to load other's draft
self.assertNotEqual(other_sale.partner_id, self.member_partner)
def test_load_draft_expired_order(self):
"""Test loading draft from expired group order."""
old_start = datetime.now().date() - timedelta(days=30)
old_end = datetime.now().date() - timedelta(days=23)
expired_order = self.env["group.order"].create(
{
"name": "Expired Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": old_start,
"end_date": old_end,
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
expired_order.action_open()
expired_order.action_close()
self.env["sale.order"].create(
{
"partner_id": self.member_partner.id,
"group_order_id": expired_order.id,
"state": "draft",
}
)
# Should warn: order expired
self.assertEqual(expired_order.state, "closed")

View file

@ -1,353 +0,0 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""
Test suite for Phase 1 refactoring helper methods.
Tests for extracted helper methods that reduce cyclomatic complexity:
- _resolve_pricelist(): Consolidate pricelist resolution logic
- _validate_confirm_request(): Validate confirm order request
- _validate_draft_request(): Validate draft order request
"""
from datetime import datetime
from datetime import timedelta
from odoo.tests.common import TransactionCase
class TestResolvePricelist(TransactionCase):
"""Test _resolve_pricelist() helper method."""
def setUp(self):
super().setUp()
self.pricelist_aplicoop = self.env["product.pricelist"].create(
{
"name": "Aplicoop Pricelist",
"currency_id": self.env.company.currency_id.id,
}
)
self.pricelist_website = self.env["product.pricelist"].create(
{
"name": "Website Pricelist",
"currency_id": self.env.company.currency_id.id,
}
)
self.website = self.env["website"].get_current_website()
self.website.pricelist_id = self.pricelist_website.id
def test_resolve_pricelist_aplicoop_configured(self):
"""Test pricelist resolution when Aplicoop pricelist is configured."""
# Set Aplicoop pricelist in config
self.env["ir.config_parameter"].sudo().set_param(
"website_sale_aplicoop.pricelist_id", str(self.pricelist_aplicoop.id)
)
# When calling _resolve_pricelist, should return Aplicoop pricelist
# Placeholder: will be implemented with actual controller call
def test_resolve_pricelist_fallback_to_website(self):
"""Test fallback to website pricelist when Aplicoop not configured."""
# Don't set Aplicoop pricelist in config (leave empty)
self.env["ir.config_parameter"].sudo().set_param(
"website_sale_aplicoop.pricelist_id", ""
)
# When calling _resolve_pricelist, should return website pricelist
# Placeholder: will be implemented with actual controller call
def test_resolve_pricelist_fallback_to_first_active(self):
"""Test final fallback to first active pricelist."""
# Remove both configured pricelists
self.env["ir.config_parameter"].sudo().set_param(
"website_sale_aplicoop.pricelist_id", ""
)
self.website.pricelist_id = False
# When calling _resolve_pricelist, should return first active pricelist
# Placeholder: will be implemented with actual controller call
class TestValidateConfirmRequest(TransactionCase):
"""Test _validate_confirm_request() helper method."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member.id,
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "product",
"list_price": 100.0,
}
)
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(4, self.group.id)],
"start_date": datetime.now().date(),
"end_date": datetime.now().date() + timedelta(days=7),
"pickup_day": "3",
"cutoff_day": "0",
"state": "open",
}
)
def test_validate_confirm_valid_request(self):
"""Test validation passes for valid confirm request."""
_ = {
"order_id": str(self.group_order.id),
"items": [
{
"product_id": str(self.product.id),
"quantity": 1.0,
"product_price": 100.0,
}
],
"is_delivery": False,
}
# Validation should pass without raising exception
# Placeholder: will be implemented with actual controller call
def test_validate_confirm_missing_order_id(self):
"""Test validation fails when order_id missing."""
_ = {
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError: "order_id is required"
# Placeholder: will be implemented with actual controller call
def test_validate_confirm_invalid_order_id(self):
"""Test validation fails for invalid order_id format."""
_ = {
"order_id": "invalid",
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError with "Invalid order_id format"
# Placeholder: will be implemented with actual controller call
def test_validate_confirm_nonexistent_order(self):
"""Test validation fails when order doesn't exist."""
_ = {
"order_id": "99999",
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError with "not found"
# Placeholder: will be implemented with actual controller call
def test_validate_confirm_closed_order(self):
"""Test validation fails when order is closed."""
self.group_order.state = "confirmed"
_ = {
"order_id": str(self.group_order.id),
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError with "not available"
# Placeholder: will be implemented with actual controller call
def test_validate_confirm_no_items(self):
"""Test validation fails when no items provided."""
_ = {
"order_id": str(self.group_order.id),
"items": [],
}
# Validation should raise ValueError with "No items in cart"
# Placeholder: will be implemented with actual controller call
def test_validate_confirm_user_no_partner(self):
"""Test validation fails when user has no partner_id."""
_ = self.env["res.users"].create(
{
"name": "User No Partner",
"login": "nopartner@test.com",
"email": "nopartner@test.com",
}
)
_ = {
"order_id": str(self.group_order.id),
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError with "no associated partner"
# Placeholder: will be implemented with actual controller call
class TestValidateDraftRequest(TransactionCase):
"""Test _validate_draft_request() helper method."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member.id,
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "product",
"list_price": 100.0,
}
)
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(4, self.group.id)],
"start_date": datetime.now().date(),
"end_date": datetime.now().date() + timedelta(days=7),
"pickup_day": "3",
"cutoff_day": "0",
"state": "open",
}
)
def test_validate_draft_valid_request(self):
"""Test validation passes for valid draft request."""
_ = {
"order_id": str(self.group_order.id),
"items": [
{
"product_id": str(self.product.id),
"quantity": 1.0,
"product_price": 100.0,
}
],
}
# Validation should pass without raising exception
# Placeholder: will be implemented with actual controller call
def test_validate_draft_missing_order_id(self):
"""Test validation fails when order_id missing."""
_ = {
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError: "order_id is required"
# Placeholder: will be implemented with actual controller call
def test_validate_draft_invalid_order_id(self):
"""Test validation fails for invalid order_id."""
_ = {
"order_id": "invalid",
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError with "Invalid order_id format"
# Placeholder: will be implemented with actual controller call
def test_validate_draft_nonexistent_order(self):
"""Test validation fails when order doesn't exist."""
_ = {
"order_id": "99999",
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError with "not found"
# Placeholder: will be implemented with actual controller call
def test_validate_draft_no_items(self):
"""Test validation fails when no items."""
_ = {
"order_id": str(self.group_order.id),
"items": [],
}
# Validation should raise ValueError with "No items in cart"
# Placeholder: will be implemented with actual controller call
def test_validate_draft_user_no_partner(self):
"""Test validation fails when user has no partner."""
_ = self.env["res.users"].create(
{
"name": "User No Partner",
"login": "nopartner@test.com",
"email": "nopartner@test.com",
}
)
_ = {
"order_id": str(self.group_order.id),
"items": [{"product_id": "1", "quantity": 1.0}],
}
# Validation should raise ValueError with "no associated partner"
# Placeholder: will be implemented with actual controller call
def test_validate_draft_with_merge_action(self):
"""Test validation passes when merge_action is specified."""
_ = {
"order_id": str(self.group_order.id),
"items": [{"product_id": "1", "quantity": 1.0}],
"merge_action": "merge",
"existing_draft_id": "123",
}
# Validation should pass and return merge_action and existing_draft_id
# Placeholder: will be implemented with actual controller call
def test_validate_draft_with_replace_action(self):
"""Test validation passes when replace_action is specified."""
_ = {
"order_id": str(self.group_order.id),
"items": [{"product_id": "1", "quantity": 1.0}],
"merge_action": "replace",
"existing_draft_id": "123",
}
# Validation should pass and return merge_action and existing_draft_id
# Placeholder: will be implemented with actual controller call

View file

@ -1,286 +0,0 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""
Test suite for Phase 2 refactoring of eskaera_shop() method.
Tests for refactored eskaera_shop using extracted helpers:
- Usage of _resolve_pricelist() instead of inline 3-tier fallback
- Extracted category filtering logic
- Price calculation with pricelist
- Search and category filter functionality
"""
from datetime import datetime
from datetime import timedelta
from odoo.tests.common import TransactionCase
class TestEskaeraShopobjInit(TransactionCase):
"""Test eskaera_shop() initial validation and setup."""
def setUp(self):
super().setUp()
self.pricelist = self.env["product.pricelist"].create(
{
"name": "Test Pricelist",
"currency_id": self.env.company.currency_id.id,
}
)
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
self.member = self.env["res.partner"].create(
{
"name": "Group Member",
"email": "member@test.com",
}
)
self.group.member_ids = [(4, self.member.id)]
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "testuser@test.com",
"email": "testuser@test.com",
"partner_id": self.member.id,
}
)
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
self.product = self.env["product.product"].create(
{
"name": "Test Product",
"type": "product",
"list_price": 100.0,
"categ_id": self.category.id,
}
)
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(4, self.group.id)],
"start_date": datetime.now().date(),
"end_date": datetime.now().date() + timedelta(days=7),
"pickup_day": "3",
"cutoff_day": "0",
"state": "open",
"category_ids": [(4, self.category.id)],
}
)
def test_eskaera_shop_order_not_found(self):
"""Test that eskaera_shop redirects when order doesn't exist."""
# Nonexistent order_id should redirect to /eskaera
# Placeholder: will be tested via HttpCase with request.Client
def test_eskaera_shop_order_not_open(self):
"""Test that eskaera_shop redirects when order is not open."""
self.group_order.state = "confirmed"
# Should redirect to /eskaera
# Placeholder: will be tested via HttpCase with request.Client
def test_eskaera_shop_uses_resolve_pricelist(self):
"""Test that eskaera_shop uses _resolve_pricelist() helper."""
# Configure Aplicoop pricelist
self.env["ir.config_parameter"].sudo().set_param(
"website_sale_aplicoop.pricelist_id", str(self.pricelist.id)
)
# When eskaera_shop is called, should use _resolve_pricelist()
# Placeholder: will verify via mock or direct method call
class TestEskaeraShopcategoryHierarchy(TransactionCase):
"""Test eskaera_shop category hierarchy building."""
def setUp(self):
super().setUp()
self.parent_category = self.env["product.category"].create(
{
"name": "Parent Category",
}
)
self.child_category = self.env["product.category"].create(
{
"name": "Child Category",
"parent_id": self.parent_category.id,
}
)
self.product1 = self.env["product.product"].create(
{
"name": "Product in Parent",
"type": "product",
"list_price": 100.0,
"categ_id": self.parent_category.id,
}
)
self.product2 = self.env["product.product"].create(
{
"name": "Product in Child",
"type": "product",
"list_price": 200.0,
"categ_id": self.child_category.id,
}
)
def test_category_hierarchy_includes_parents(self):
"""Test that available_categories includes parent categories."""
# When products have categories, category hierarchy should include parents
# Placeholder: verify category tree structure
def test_category_filter_includes_descendants(self):
"""Test that category filter includes child categories."""
# When filtering by parent category, should include products from children
# Placeholder: verify filtered products
class TestEskaeraShopriceCalculation(TransactionCase):
"""Test eskaera_shop price calculation with pricelist."""
def setUp(self):
super().setUp()
self.pricelist = self.env["product.pricelist"].create(
{
"name": "Test Pricelist",
"currency_id": self.env.company.currency_id.id,
}
)
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
self.product_no_tax = self.env["product.product"].create(
{
"name": "Product No Tax",
"type": "product",
"list_price": 100.0,
"categ_id": self.category.id,
"taxes_id": False,
}
)
# Create tax
self.tax = self.env["account.tax"].create(
{
"name": "Test Tax",
"type_tax_use": "sale",
"amount": 21.0,
"amount_type": "percent",
}
)
self.product_with_tax = self.env["product.product"].create(
{
"name": "Product With Tax",
"type": "product",
"list_price": 100.0,
"categ_id": self.category.id,
"taxes_id": [(4, self.tax.id)],
}
)
def test_price_calculation_uses_pricelist(self):
"""Test that product prices are calculated using configured pricelist."""
# Configure Aplicoop pricelist
self.env["ir.config_parameter"].sudo().set_param(
"website_sale_aplicoop.pricelist_id", str(self.pricelist.id)
)
# When eskaera_shop renders, should calculate prices via pricelist
# Placeholder: verify price_info dict populated
def test_price_info_structure(self):
"""Test that product_price_info has correct structure."""
# product_price_info should have: price, list_price, has_discounted_price, discount, tax_included
# Placeholder: verify dict structure
class TestEskaeraShoosearch(TransactionCase):
"""Test eskaera_shop search functionality."""
def setUp(self):
super().setUp()
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
self.product1 = self.env["product.product"].create(
{
"name": "Apple Juice",
"type": "product",
"list_price": 10.0,
"categ_id": self.category.id,
}
)
self.product2 = self.env["product.product"].create(
{
"name": "Orange Juice",
"type": "product",
"list_price": 12.0,
"categ_id": self.category.id,
"description": "Fresh orange juice from Spain",
}
)
self.product3 = self.env["product.product"].create(
{
"name": "Water",
"type": "product",
"list_price": 2.0,
"categ_id": self.category.id,
}
)
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"start_date": datetime.now().date(),
"end_date": datetime.now().date() + timedelta(days=7),
"pickup_day": "3",
"cutoff_day": "0",
"state": "open",
"category_ids": [(4, self.category.id)],
}
)
def test_search_filters_by_name(self):
"""Test that search query filters products by name."""
# When search='apple', should return only Apple Juice
# Placeholder: verify filtered products
def test_search_filters_by_description(self):
"""Test that search query filters products by description."""
# When search='spain', should return Orange Juice (matches description)
# Placeholder: verify filtered products
def test_search_case_insensitive(self):
"""Test that search is case insensitive."""
# search='APPLE' should match 'Apple Juice'
# Placeholder: verify filtered products
def test_search_empty_returns_all(self):
"""Test that empty search returns all products."""
# When search='', should return all products
# Placeholder: verify all products returned

View file

@ -1,83 +0,0 @@
# Copyright 2026
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from datetime import datetime
from datetime import timedelta
from odoo.tests import tagged
from odoo.tests.common import HttpCase
@tagged("post_install", "-at_install")
class TestPortalAccess(HttpCase):
"""Verifica que un usuario portal pueda acceder a la página de un pedido (eskaera)."""
def setUp(self):
super().setUp()
# Create a consumer group and a member partner
self.group = self.env["res.partner"].create(
{
"name": "Portal Test Group",
"is_company": True,
"email": "portal-group@test.com",
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Portal Member",
"email": "portal-member@test.com",
}
)
# Add member to the group
self.group.member_ids = [(4, self.member_partner.id)]
# Create a portal user (password = login for HttpCase.authenticate convenience)
login = "portal.user@test.com"
self.portal_user = self.env["res.users"].create(
{
"name": "Portal User",
"login": login,
"password": login,
"partner_id": self.member_partner.id,
# Add portal group
"groups_id": [(4, self.env.ref("base.group_portal").id)],
}
)
# Create and open a group.order belonging to the same company
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Portal Access Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.group_order.action_open()
def test_portal_user_can_view_eskaera_page(self):
"""El endpoint /eskaera/<id> debe ser accesible por un usuario portal que pertenezca a la compañía."""
# Authenticate as portal user
self.authenticate(self.portal_user.login, self.portal_user.login)
# Request the eskaera page
response = self.url_open(
f"/eskaera/{self.group_order.id}", allow_redirects=True
)
# Should return 200 OK and not redirect to login
self.assertEqual(response.status_code, 200)
# Simple sanity: page should contain the group order name
content = (
response.get_data(as_text=True)
if hasattr(response, "get_data")
else getattr(response, "text", "")
)
self.assertIn(self.group_order.name, content)

View file

@ -1,85 +0,0 @@
# Copyright 2026
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from datetime import datetime
from datetime import timedelta
from odoo.tests import tagged
from odoo.tests.common import HttpCase
@tagged("post_install", "-at_install")
class TestPortalGetRoutes(HttpCase):
"""Comprueba que las rutas GET principales devuelvan 200 para un usuario portal."""
def setUp(self):
super().setUp()
# Create a consumer group and a member partner
self.group = self.env["res.partner"].create(
{
"name": "Portal Routes Group",
"is_company": True,
"email": "routes-group@test.com",
}
)
self.member_partner = self.env["res.partner"].create(
{"name": "Routes Member", "email": "routes-member@test.com"}
)
self.group.member_ids = [(4, self.member_partner.id)]
# Create a portal user (password = login for HttpCase.authenticate convenience)
login = "portal.routes@test.com"
self.portal_user = self.env["res.users"].create(
{
"name": "Portal Routes User",
"login": login,
"password": login,
"partner_id": self.member_partner.id,
"groups_id": [(4, self.env.ref("base.group_portal").id)],
}
)
# Create and open a minimal group.order
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Routes Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.group_order.action_open()
def test_portal_get_routes_return_200(self):
"""Verifica que las rutas principales GET devuelvan 200 para usuario portal."""
# Authenticate as portal user
self.authenticate(self.portal_user.login, self.portal_user.login)
routes = [
"/eskaera",
f"/eskaera/{self.group_order.id}",
f"/eskaera/{self.group_order.id}/checkout",
f"/eskaera/{self.group_order.id}/load-page?page=1",
"/eskaera/labels",
]
for route in routes:
response = self.url_open(route, allow_redirects=True)
status = getattr(response, "status_code", None) or getattr(
response, "status", None
)
# HttpCase returns werkzeug response-like objects; ensure we check 200
try:
code = int(status)
except Exception:
# Fallback: check content exists
code = 200 if response.get_data(as_text=True) else 500
self.assertEqual(code, 200, msg=f"Ruta {route} devolvió {code}")

View file

@ -1,101 +0,0 @@
# Copyright 2026
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from datetime import datetime
from datetime import timedelta
from odoo.tests import tagged
from odoo.tests.common import HttpCase
@tagged("post_install", "-at_install")
class TestPortalProductUoMAccess(HttpCase):
"""Verifica que un usuario portal pueda acceder a la página de tienda (eskaera)
y que la lectura de UoM para display no provoque AccessError.
"""
def setUp(self):
super().setUp()
# Grupo / partner / usuario portal (reusa patrón del otro test)
self.group = self.env["res.partner"].create(
{"name": "Portal UoM Group", "is_company": True}
)
self.member_partner = self.env["res.partner"].create(
{"name": "Portal UoM Member"}
)
self.group.member_ids = [(4, self.member_partner.id)]
login = "portal.uom@test.com"
self.portal_user = self.env["res.users"].create(
{
"name": "Portal UoM User",
"login": login,
"password": login,
"partner_id": self.member_partner.id,
"groups_id": [(4, self.env.ref("base.group_portal").id)],
}
)
# Crear una categoría de UoM y una UoM personalizada (posible restringida)
uom_cat = self.env["uom.uom.categ"].create({"name": "Test UoM Cat"})
self.uom = self.env["uom.uom"].create(
{
"name": "Test UoM",
"uom_type": "reference",
"factor_inv": 1.0,
"category_id": uom_cat.id,
}
)
# Crear producto y asignar la UoM creada
self.product = self.env["product.product"].create(
{
"name": "Producto UoM Test",
"type": "consu",
"list_price": 12.5,
"uom_id": self.uom.id,
"active": True,
}
)
# Publicar el template para que aparezca en la tienda
self.product.product_tmpl_id.write({"is_published": True, "sale_ok": True})
# Crear order y añadir producto
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Portal UoM Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
"product_ids": [(6, 0, [self.product.id])],
}
)
self.group_order.action_open()
def test_portal_user_can_view_shop_with_uom(self):
# Authenticate as portal user
self.authenticate(self.portal_user.login, self.portal_user.login)
# Request the eskaera page which renders product cards (and reads uom)
response = self.url_open(
f"/eskaera/{self.group_order.id}", allow_redirects=True
)
# Debe retornar 200 OK
self.assertEqual(response.status_code, 200)
content = (
response.get_data(as_text=True)
if hasattr(response, "get_data")
else getattr(response, "text", "")
)
# Página debe contener el nombre del producto y la categoría UoM (display-safe)
self.assertIn(self.product.name, content)
self.assertIn("Test UoM Cat", content)

View file

@ -1,425 +0,0 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""
Test suite for price calculations WITH taxes included.
This test verifies that the _compute_price_with_taxes method correctly
calculates prices including taxes for display in the online shop.
"""
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged("post_install", "-at_install")
class TestPriceWithTaxesIncluded(TransactionCase):
"""Test that prices displayed include taxes."""
def setUp(self):
super().setUp()
# Create test company
self.company = self.env["res.company"].create(
{
"name": "Test Company Tax Included",
}
)
# Get or create default tax group
tax_group = self.env["account.tax.group"].search(
[("company_id", "=", self.company.id)], limit=1
)
if not tax_group:
tax_group = self.env["account.tax.group"].create(
{
"name": "IVA",
"company_id": self.company.id,
}
)
# Get default country (Spain)
country_es = self.env.ref("base.es")
# Create tax (21% IVA) - price_include=False (default)
self.tax_21 = self.env["account.tax"].create(
{
"name": "IVA 21%",
"amount": 21.0,
"amount_type": "percent",
"type_tax_use": "sale",
"price_include": False, # Explicit: tax NOT included in price
"company_id": self.company.id,
"country_id": country_es.id,
"tax_group_id": tax_group.id,
}
)
# Create tax (10% IVA reducido)
self.tax_10 = self.env["account.tax"].create(
{
"name": "IVA 10%",
"amount": 10.0,
"amount_type": "percent",
"type_tax_use": "sale",
"price_include": False,
"company_id": self.company.id,
"country_id": country_es.id,
"tax_group_id": tax_group.id,
}
)
# Create tax with price_include=True for comparison
self.tax_21_included = self.env["account.tax"].create(
{
"name": "IVA 21% Incluido",
"amount": 21.0,
"amount_type": "percent",
"type_tax_use": "sale",
"price_include": True, # Tax IS included in price
"company_id": self.company.id,
"country_id": country_es.id,
"tax_group_id": tax_group.id,
}
)
# Create product category
self.category = self.env["product.category"].create(
{
"name": "Test Category Tax Included",
}
)
# Create test products with different tax configurations
self.product_21 = self.env["product.product"].create(
{
"name": "Product With 21% Tax",
"list_price": 100.0,
"categ_id": self.category.id,
"taxes_id": [(6, 0, [self.tax_21.id])],
"company_id": self.company.id,
}
)
self.product_10 = self.env["product.product"].create(
{
"name": "Product With 10% Tax",
"list_price": 100.0,
"categ_id": self.category.id,
"taxes_id": [(6, 0, [self.tax_10.id])],
"company_id": self.company.id,
}
)
self.product_no_tax = self.env["product.product"].create(
{
"name": "Product Without Tax",
"list_price": 100.0,
"categ_id": self.category.id,
"taxes_id": False,
"company_id": self.company.id,
}
)
self.product_tax_included = self.env["product.product"].create(
{
"name": "Product With Tax Included",
"list_price": 121.0, # 100 + 21% = 121
"categ_id": self.category.id,
"taxes_id": [(6, 0, [self.tax_21_included.id])],
"company_id": self.company.id,
}
)
# Create pricelist
self.pricelist = self.env["product.pricelist"].create(
{
"name": "Test Pricelist",
"company_id": self.company.id,
}
)
def test_price_with_21_percent_tax(self):
"""Test that 21% tax is correctly added to base price."""
# Base price: 100.0
# Expected with 21% tax: 121.0
taxes = self.product_21.taxes_id.filtered(
lambda t: t.company_id == self.company
)
base_price = 100.0
tax_result = taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=self.product_21,
)
price_with_tax = tax_result["total_included"]
self.assertAlmostEqual(
price_with_tax, 121.0, places=2, msg="100 + 21% should equal 121.0"
)
def test_price_with_10_percent_tax(self):
"""Test that 10% tax is correctly added to base price."""
# Base price: 100.0
# Expected with 10% tax: 110.0
taxes = self.product_10.taxes_id.filtered(
lambda t: t.company_id == self.company
)
base_price = 100.0
tax_result = taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=self.product_10,
)
price_with_tax = tax_result["total_included"]
self.assertAlmostEqual(
price_with_tax, 110.0, places=2, msg="100 + 10% should equal 110.0"
)
def test_price_without_tax(self):
"""Test that product without tax returns base price unchanged."""
# Base price: 100.0
# Expected with no tax: 100.0
taxes = self.product_no_tax.taxes_id.filtered(
lambda t: t.company_id == self.company
)
# No taxes, so tax_result would be empty
self.assertFalse(taxes, "Product should have no taxes")
# Without taxes, price should remain base price
base_price = 100.0
expected_price = 100.0
self.assertEqual(
base_price,
expected_price,
msg="Product without tax should have unchanged price",
)
def test_oca_get_price_returns_base_without_tax(self):
"""Test that OCA _get_price returns base price WITHOUT taxes by default."""
# This verifies our understanding of OCA behavior
price_info = self.product_21._get_price(
qty=1.0,
pricelist=self.pricelist,
fposition=False,
)
# OCA should return base price (100.0) WITHOUT tax
self.assertAlmostEqual(
price_info["value"],
100.0,
places=2,
msg="OCA _get_price should return base price without tax",
)
# tax_included should be False for price_include=False taxes
self.assertFalse(
price_info.get("tax_included", False),
msg="tax_included should be False when price_include=False",
)
def test_oca_get_price_with_included_tax(self):
"""Test OCA behavior with price_include=True tax."""
price_info = self.product_tax_included._get_price(
qty=1.0,
pricelist=self.pricelist,
fposition=False,
)
# With price_include=True, the price should already include tax
# list_price is 121.0 (100 + 21%)
self.assertAlmostEqual(
price_info["value"],
121.0,
places=2,
msg="Price with included tax should be 121.0",
)
# tax_included should be True
self.assertTrue(
price_info.get("tax_included", False),
msg="tax_included should be True when price_include=True",
)
def test_compute_all_with_multiple_taxes(self):
"""Test tax calculation with multiple taxes."""
# Create product with both 21% and 10% taxes
product_multi = self.env["product.product"].create(
{
"name": "Product With Multiple Taxes",
"list_price": 100.0,
"categ_id": self.category.id,
"taxes_id": [(6, 0, [self.tax_21.id, self.tax_10.id])],
"company_id": self.company.id,
}
)
taxes = product_multi.taxes_id.filtered(lambda t: t.company_id == self.company)
base_price = 100.0
tax_result = taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=product_multi,
)
price_with_taxes = tax_result["total_included"]
# 100 + 21% + 10% = 100 + 21 + 10 = 131.0
self.assertAlmostEqual(
price_with_taxes, 131.0, places=2, msg="100 + 21% + 10% should equal 131.0"
)
def test_compute_all_with_fiscal_position(self):
"""Test tax calculation with fiscal position mapping."""
# Create fiscal position that maps 21% to 10%
fiscal_position = self.env["account.fiscal.position"].create(
{
"name": "Test Fiscal Position",
"company_id": self.company.id,
}
)
self.env["account.fiscal.position.tax"].create(
{
"position_id": fiscal_position.id,
"tax_src_id": self.tax_21.id,
"tax_dest_id": self.tax_10.id,
}
)
# Get taxes and apply fiscal position
taxes = self.product_21.taxes_id.filtered(
lambda t: t.company_id == self.company
)
mapped_taxes = fiscal_position.map_tax(taxes)
# Should be mapped to 10% tax
self.assertEqual(len(mapped_taxes), 1)
self.assertEqual(mapped_taxes[0].id, self.tax_10.id)
base_price = 100.0
tax_result = mapped_taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=self.product_21,
)
price_with_tax = tax_result["total_included"]
# Should be 110.0 (10% instead of 21%)
self.assertAlmostEqual(
price_with_tax, 110.0, places=2, msg="Fiscal position should map to 10% tax"
)
def test_tax_amount_details(self):
"""Test that compute_all provides detailed tax breakdown."""
taxes = self.product_21.taxes_id.filtered(
lambda t: t.company_id == self.company
)
base_price = 100.0
tax_result = taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=self.product_21,
)
# Verify structure of tax_result
self.assertIn("total_included", tax_result)
self.assertIn("total_excluded", tax_result)
self.assertIn("taxes", tax_result)
# total_excluded should be base price
self.assertAlmostEqual(tax_result["total_excluded"], 100.0, places=2)
# total_included should be base + tax
self.assertAlmostEqual(tax_result["total_included"], 121.0, places=2)
# taxes should contain tax details
self.assertEqual(len(tax_result["taxes"]), 1)
tax_detail = tax_result["taxes"][0]
self.assertAlmostEqual(tax_detail["amount"], 21.0, places=2)
def test_zero_price_with_tax(self):
"""Test tax calculation on free product."""
free_product = self.env["product.product"].create(
{
"name": "Free Product With Tax",
"list_price": 0.0,
"categ_id": self.category.id,
"taxes_id": [(6, 0, [self.tax_21.id])],
"company_id": self.company.id,
}
)
taxes = free_product.taxes_id.filtered(lambda t: t.company_id == self.company)
base_price = 0.0
tax_result = taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=free_product,
)
price_with_tax = tax_result["total_included"]
# 0 + 21% = 0
self.assertAlmostEqual(
price_with_tax,
0.0,
places=2,
msg="Free product with tax should still be free",
)
def test_high_precision_price_with_tax(self):
"""Test tax calculation with high precision prices."""
precise_product = self.env["product.product"].create(
{
"name": "Precise Price Product",
"list_price": 99.99,
"categ_id": self.category.id,
"taxes_id": [(6, 0, [self.tax_21.id])],
"company_id": self.company.id,
}
)
taxes = precise_product.taxes_id.filtered(
lambda t: t.company_id == self.company
)
base_price = 99.99
tax_result = taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=precise_product,
)
price_with_tax = tax_result["total_included"]
# 99.99 + 21% = 120.9879 ≈ 120.99
expected = 99.99 * 1.21
self.assertAlmostEqual(
price_with_tax,
expected,
places=2,
msg=f"Expected {expected}, got {price_with_tax}",
)

File diff suppressed because it is too large Load diff

View file

@ -1,367 +0,0 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""
Test suite for validations and constraints in website_sale_aplicoop.
Coverage:
- group.order constraint: same company for all groups
- group.order constraint: start_date < end_date
- group.order computed field: image_1920 fallback logic
- group.order computed field: product count
- res.partner validation: user without partner_id
- group.order state transitions: illegal transitions
"""
from datetime import datetime
from datetime import timedelta
from odoo.exceptions import UserError
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
class TestGroupOrderValidations(TransactionCase):
"""Test constraints and validations for group.order model."""
def setUp(self):
super().setUp()
self.company1 = self.env.company
self.company2 = self.env["res.company"].create(
{
"name": "Company 2",
}
)
self.group_c1 = self.env["res.partner"].create(
{
"name": "Group Company 1",
"is_company": True,
"company_id": self.company1.id,
}
)
self.group_c2 = self.env["res.partner"].create(
{
"name": "Group Company 2",
"is_company": True,
"company_id": self.company2.id,
}
)
def test_group_order_same_company_constraint(self):
"""Test that all groups in an order must be from same company."""
start_date = datetime.now().date()
# Creating order with groups from different companies should fail
with self.assertRaises(ValidationError):
self.env["group.order"].create(
{
"name": "Multi-Company Order",
"group_ids": [(6, 0, [self.group_c1.id, self.group_c2.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
def test_group_order_same_company_mixed_single(self):
"""Test that single company group is valid."""
start_date = datetime.now().date()
# Single company should pass
order = self.env["group.order"].create(
{
"name": "Single Company Order",
"group_ids": [(6, 0, [self.group_c1.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
def test_group_order_date_validation_start_after_end(self):
"""Test that start_date must be before end_date."""
start_date = datetime.now().date()
end_date = start_date - timedelta(days=1) # End before start
with self.assertRaises(ValidationError):
self.env["group.order"].create(
{
"name": "Bad Dates Order",
"group_ids": [(6, 0, [self.group_c1.id])],
"type": "regular",
"start_date": start_date,
"end_date": end_date,
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
def test_group_order_date_validation_same_date(self):
"""Test that start_date == end_date is allowed (single-day order)."""
same_date = datetime.now().date()
order = self.env["group.order"].create(
{
"name": "Same Day Order",
"group_ids": [(6, 0, [self.group_c1.id])],
"type": "regular",
"start_date": same_date,
"end_date": same_date,
"period": "once",
"pickup_day": "0",
"cutoff_day": "0",
}
)
self.assertTrue(order.exists())
class TestGroupOrderImageFallback(TransactionCase):
"""Test image_1920 computed field fallback logic."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
def test_image_fallback_order_image_first(self):
"""Test that order image takes priority over group image."""
# Set both order and group image
test_image = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
self.group_order.image_1920 = test_image
self.group.image_1920 = test_image
# Order image should be returned
computed_image = self.group_order.image_1920
self.assertEqual(computed_image, test_image)
def test_image_fallback_group_image_when_no_order_image(self):
"""Test fallback to group image when order has no image."""
test_image = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
# Only set group image
self.group_order.image_1920 = False
self.group.image_1920 = test_image
# Group image should be returned as fallback
# Note: This requires the computed field logic to be tested
# after field recalculation
def test_image_fallback_none_when_no_images(self):
"""Test that None is returned when no image available."""
# No images set
self.group_order.image_1920 = False
self.group.image_1920 = False
# Should be empty/False
computed_image = self.group_order.image_1920
self.assertFalse(computed_image)
class TestGroupOrderProductCount(TransactionCase):
"""Test product_count computed field."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.product1 = self.env["product.product"].create(
{
"name": "Product 1",
"type": "consu",
"list_price": 10.0,
}
)
self.product2 = self.env["product.product"].create(
{
"name": "Product 2",
"type": "consu",
"list_price": 20.0,
}
)
def test_product_count_initial_zero(self):
"""Test that new order has zero products."""
self.assertEqual(self.group_order.product_count, 0)
def test_product_count_increments_on_add(self):
"""Test that product_count increases when adding products."""
self.group_order.product_ids = [(4, self.product1.id)]
self.assertEqual(self.group_order.product_count, 1)
self.group_order.product_ids = [(4, self.product2.id)]
self.assertEqual(self.group_order.product_count, 2)
def test_product_count_decrements_on_remove(self):
"""Test that product_count decreases when removing products."""
self.group_order.product_ids = [(6, 0, [self.product1.id, self.product2.id])]
self.assertEqual(self.group_order.product_count, 2)
self.group_order.product_ids = [(3, self.product1.id)]
self.assertEqual(self.group_order.product_count, 1)
def test_product_count_all_removed(self):
"""Test that product_count is zero when all removed."""
self.group_order.product_ids = [(6, 0, [self.product1.id, self.product2.id])]
self.group_order.product_ids = [(6, 0, [])]
self.assertEqual(self.group_order.product_count, 0)
class TestStateTransitions(TransactionCase):
"""Test group.order state transition validation."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
start_date = datetime.now().date()
self.order = self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
def test_illegal_transition_draft_to_closed(self):
"""Test that Draft -> Closed transition is not allowed."""
# Should not allow skipping Open state
self.assertEqual(self.order.state, "draft")
# Calling action_close() without action_open() should fail
with self.assertRaises((ValidationError, UserError)):
self.order.action_close()
def test_illegal_transition_cancelled_to_open(self):
"""Test that Cancelled -> Open transition is not allowed."""
self.order.action_cancel()
self.assertEqual(self.order.state, "cancelled")
# Should not allow re-opening cancelled order
with self.assertRaises((ValidationError, UserError)):
self.order.action_open()
def test_legal_transition_draft_open_closed(self):
"""Test that Draft -> Open -> Closed is allowed."""
self.assertEqual(self.order.state, "draft")
self.order.action_open()
self.assertEqual(self.order.state, "open")
self.order.action_close()
self.assertEqual(self.order.state, "closed")
def test_transition_draft_to_cancelled(self):
"""Test that Draft -> Cancelled is allowed."""
self.assertEqual(self.order.state, "draft")
self.order.action_cancel()
self.assertEqual(self.order.state, "cancelled")
def test_transition_open_to_cancelled(self):
"""Test that Open -> Cancelled is allowed (emergency stop)."""
self.order.action_open()
self.assertEqual(self.order.state, "open")
self.order.action_cancel()
self.assertEqual(self.order.state, "cancelled")
class TestUserPartnerValidation(TransactionCase):
"""Test validation when user has no partner_id."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
# Create user without partner (edge case)
self.user_no_partner = self.env["res.users"].create(
{
"name": "User No Partner",
"login": "noparnter@test.com",
"partner_id": False, # Explicitly no partner
}
)
def test_user_without_partner_cannot_access_order(self):
"""Test that user without partner_id has no access to orders."""
start_date = datetime.now().date()
self.env["group.order"].create(
{
"name": "Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
# User without partner should not have access
# This should be validated in controller
self.assertFalse(self.user_no_partner.partner_id)

View file

@ -107,12 +107,6 @@
</script> </script>
</t> </t>
</template> </template>
<template id="confirm_message_snippet" name="Confirm Message Snippet">
<t>Thank you! Your order has been confirmed.</t>
</template>
<template id="confirm_pickup_label_snippet" name="Confirm Pickup Label Snippet">
<t>Pickup Day</t>
</template>
<template id="eskaera_order_card_meta" name="Order Card Metadata"> <template id="eskaera_order_card_meta" name="Order Card Metadata">
<div class="card-meta-compact mt-3"> <div class="card-meta-compact mt-3">
<div class="meta-grid"> <div class="meta-grid">