[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:
parent
6ba554c91b
commit
b3999e2283
24 changed files with 5 additions and 5598 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
})();
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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}")
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
@ -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)
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue