From b3999e2283b71526fb3db86b14cc4bab4f936d31 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Sun, 16 Aug 2026 22:31:13 +0200 Subject: [PATCH] [REM] website_sale_aplicoop: remove dead code and orphaned tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- website_sale_aplicoop/__manifest__.py | 5 - .../controllers/website_sale.py | 465 ------ .../controllers/website_sale_i18n.py | 15 - website_sale_aplicoop/i18n/__init__.py | 0 website_sale_aplicoop/models/group_order.py | 61 +- .../models/group_order_slot.py | 17 - .../models/res_config_settings.py | 12 - .../static/src/js/checkout_summary.js | 9 - .../static/src/js/home_delivery.js | 4 - .../static/src/js/i18n_helpers.js | 30 - .../static/src/js/i18n_manager.js | 33 - .../static/src/js/website_sale.js | 154 -- .../tests/test_draft_persistence.py | 667 --------- .../tests/test_edge_cases.py | 506 ------- website_sale_aplicoop/tests/test_endpoints.py | 613 -------- .../tests/test_helper_methods_phase1.py | 353 ----- .../tests/test_phase2_eskaera_shop.py | 286 ---- .../tests/test_portal_access.py | 83 -- .../tests/test_portal_get_routes.py | 85 -- .../tests/test_portal_product_uom_access.py | 101 -- .../tests/test_price_with_taxes_included.py | 425 ------ .../tests/test_product_discovery.py | 1306 ----------------- .../tests/test_validations.py | 367 ----- .../views/website_templates.xml | 6 - 24 files changed, 5 insertions(+), 5598 deletions(-) delete mode 100644 website_sale_aplicoop/i18n/__init__.py delete mode 100644 website_sale_aplicoop/static/src/js/checkout_summary.js delete mode 100644 website_sale_aplicoop/tests/test_draft_persistence.py delete mode 100644 website_sale_aplicoop/tests/test_edge_cases.py delete mode 100644 website_sale_aplicoop/tests/test_endpoints.py delete mode 100644 website_sale_aplicoop/tests/test_helper_methods_phase1.py delete mode 100644 website_sale_aplicoop/tests/test_phase2_eskaera_shop.py delete mode 100644 website_sale_aplicoop/tests/test_portal_access.py delete mode 100644 website_sale_aplicoop/tests/test_portal_get_routes.py delete mode 100644 website_sale_aplicoop/tests/test_portal_product_uom_access.py delete mode 100644 website_sale_aplicoop/tests/test_price_with_taxes_included.py delete mode 100644 website_sale_aplicoop/tests/test_product_discovery.py delete mode 100644 website_sale_aplicoop/tests/test_validations.py diff --git a/website_sale_aplicoop/__manifest__.py b/website_sale_aplicoop/__manifest__.py index 42c9d8c..ddf73ea 100644 --- a/website_sale_aplicoop/__manifest__.py +++ b/website_sale_aplicoop/__manifest__.py @@ -60,10 +60,6 @@ # Demo: Sale Orders "demo/sale_order_demo.xml", ], - "i18n": [ - "i18n/es.po", - "i18n/eu_ES.po", - ], "external_dependencies": { "python": [], }, @@ -77,7 +73,6 @@ "website_sale_aplicoop/static/src/js/website_sale.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/checkout_summary.js", "website_sale_aplicoop/static/src/js/eskaera_payment.js", # Search and pagination "website_sale_aplicoop/static/src/js/infinite_scroll.js", diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py index e77ca45..4670f14 100644 --- a/website_sale_aplicoop/controllers/website_sale.py +++ b/website_sale_aplicoop/controllers/website_sale.py @@ -323,74 +323,6 @@ class AplicoopWebsiteSale(WebsiteSale): ) 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): """Build localized confirmation message for confirm_eskaera.""" # Get pickup day index, localized name and date string using helper @@ -447,28 +379,6 @@ class AplicoopWebsiteSale(WebsiteSale): 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): """Decode and validate the incoming save-cart request. @@ -1250,156 +1160,6 @@ class AplicoopWebsiteSale(WebsiteSale): [("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( ["/eskaera//checkout"], type="http", auth="user", website=True ) @@ -1797,130 +1557,6 @@ class AplicoopWebsiteSale(WebsiteSale): [("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( ["/eskaera/load-draft"], type="http", @@ -2723,107 +2359,6 @@ class AplicoopWebsiteSale(WebsiteSale): _logger.error(traceback.format_exc()) 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( ["/eskaera/labels", "/eskaera/i18n"], type="json", diff --git a/website_sale_aplicoop/controllers/website_sale_i18n.py b/website_sale_aplicoop/controllers/website_sale_i18n.py index b58cfc0..7cfd99a 100644 --- a/website_sale_aplicoop/controllers/website_sale_i18n.py +++ b/website_sale_aplicoop/controllers/website_sale_i18n.py @@ -149,18 +149,3 @@ def _get_translated_labels(self, lang=None, request_obj=None): } 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 diff --git a/website_sale_aplicoop/i18n/__init__.py b/website_sale_aplicoop/i18n/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/website_sale_aplicoop/models/group_order.py b/website_sale_aplicoop/models/group_order.py index a390094..142eeaf 100644 --- a/website_sale_aplicoop/models/group_order.py +++ b/website_sale_aplicoop/models/group_order.py @@ -6,6 +6,7 @@ import re from datetime import timedelta from dateutil.relativedelta import relativedelta + from odoo import api from odoo import fields from odoo import models @@ -283,23 +284,6 @@ class GroupOrder(models.Model): help="Image displayed alongside the consumer group order name", 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") def _compute_home_delivery(self): @@ -700,31 +684,6 @@ class GroupOrder(models.Model): ) 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_slot_ids = fields.One2many( "group.order.slot", @@ -734,12 +693,6 @@ class GroupOrder(models.Model): 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( "group.order.slot", string="Next Pickup Slot", @@ -755,12 +708,6 @@ class GroupOrder(models.Model): 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( "pickup_slot_ids", "pickup_slot_ids.start_hour", @@ -779,7 +726,8 @@ class GroupOrder(models.Model): - If no slots are configured, leave fields empty (fallback handled by existing pickup_day logic). """ - from datetime import datetime, time + from datetime import datetime + from datetime import time for record in self: record.next_pickup_slot_id = False @@ -1420,7 +1368,8 @@ class GroupOrder(models.Model): return failure_reasons = failure_reasons or {} - from markupsafe import Markup, escape + from markupsafe import Markup + from markupsafe import escape items = Markup() for sale_order in failed_sale_orders: diff --git a/website_sale_aplicoop/models/group_order_slot.py b/website_sale_aplicoop/models/group_order_slot.py index e9f736b..fd67725 100644 --- a/website_sale_aplicoop/models/group_order_slot.py +++ b/website_sale_aplicoop/models/group_order_slot.py @@ -55,20 +55,3 @@ class GroupOrderSlot(models.Model): sequence = fields.Integer(string="Sequence", default=10) 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}" diff --git a/website_sale_aplicoop/models/res_config_settings.py b/website_sale_aplicoop/models/res_config_settings.py index d7f7ef8..03aaca1 100644 --- a/website_sale_aplicoop/models/res_config_settings.py +++ b/website_sale_aplicoop/models/res_config_settings.py @@ -34,15 +34,3 @@ class ResConfigSettings(models.TransientModel): 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.", ) - - @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"), - ] diff --git a/website_sale_aplicoop/static/src/js/checkout_summary.js b/website_sale_aplicoop/static/src/js/checkout_summary.js deleted file mode 100644 index 836ec67..0000000 --- a/website_sale_aplicoop/static/src/js/checkout_summary.js +++ /dev/null @@ -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 -})(); diff --git a/website_sale_aplicoop/static/src/js/home_delivery.js b/website_sale_aplicoop/static/src/js/home_delivery.js index 2388e42..795dc2c 100644 --- a/website_sale_aplicoop/static/src/js/home_delivery.js +++ b/website_sale_aplicoop/static/src/js/home_delivery.js @@ -226,10 +226,6 @@ }, 50); }, - renderCheckoutSummary: function () { - // Stub - now handled by global window.renderCheckoutSummary - }, - addDeliveryProduct: function () { if (!this.deliveryProductId) { console.warn("[HomeDelivery] Delivery product ID not found"); diff --git a/website_sale_aplicoop/static/src/js/i18n_helpers.js b/website_sale_aplicoop/static/src/js/i18n_helpers.js index 4462bf5..e68ccd6 100644 --- a/website_sale_aplicoop/static/src/js/i18n_helpers.js +++ b/website_sale_aplicoop/static/src/js/i18n_helpers.js @@ -9,9 +9,6 @@ * OLD: window.getCheckoutLabels() * NEW: i18nManager.getAll() * - * OLD: window.formatCurrency(amount) - * NEW: i18nManager.formatCurrency(amount) - * * Copyright 2025 Criptomart * License AGPL-3.0 or later */ @@ -35,32 +32,5 @@ 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"); })(); diff --git a/website_sale_aplicoop/static/src/js/i18n_manager.js b/website_sale_aplicoop/static/src/js/i18n_manager.js index cc13201..aa9ef61 100644 --- a/website_sale_aplicoop/static/src/js/i18n_manager.js +++ b/website_sale_aplicoop/static/src/js/i18n_manager.js @@ -103,39 +103,6 @@ } 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 diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js index b011773..be8970e 100644 --- a/website_sale_aplicoop/static/src/js/website_sale.js +++ b/website_sale_aplicoop/static/src/js/website_sale.js @@ -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
tags for HTML display - htmlOutput = - msg.replace(/\n/g, "
") + - "

" + - deliveryNotice.replace(/\n/g, "
"); - console.log("[UPDATE_LABELS] Final HTML with notice:", htmlOutput); - } else { - htmlOutput = msg.replace(/\n/g, "
"); - } - - 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 () { var self = this; var btn = document.getElementById("load-more-btn"); diff --git a/website_sale_aplicoop/tests/test_draft_persistence.py b/website_sale_aplicoop/tests/test_draft_persistence.py deleted file mode 100644 index e775c75..0000000 --- a/website_sale_aplicoop/tests/test_draft_persistence.py +++ /dev/null @@ -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) diff --git a/website_sale_aplicoop/tests/test_edge_cases.py b/website_sale_aplicoop/tests/test_edge_cases.py deleted file mode 100644 index 28409f6..0000000 --- a/website_sale_aplicoop/tests/test_edge_cases.py +++ /dev/null @@ -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) diff --git a/website_sale_aplicoop/tests/test_endpoints.py b/website_sale_aplicoop/tests/test_endpoints.py deleted file mode 100644 index 767b180..0000000 --- a/website_sale_aplicoop/tests/test_endpoints.py +++ /dev/null @@ -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/ (GET) - View specific group order -- /eskaera//add-to-cart (POST) - Add product to cart -- /eskaera//checkout (GET) - Checkout page -- /eskaera//checkout (POST) - Save cart items -- /eskaera/confirm (POST) - Confirm order -- /eskaera//confirm/ (POST) - Confirm order from portal -- /eskaera//load-from-history/ (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//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//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//load-from-history/ 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") diff --git a/website_sale_aplicoop/tests/test_helper_methods_phase1.py b/website_sale_aplicoop/tests/test_helper_methods_phase1.py deleted file mode 100644 index 9284bb2..0000000 --- a/website_sale_aplicoop/tests/test_helper_methods_phase1.py +++ /dev/null @@ -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 diff --git a/website_sale_aplicoop/tests/test_phase2_eskaera_shop.py b/website_sale_aplicoop/tests/test_phase2_eskaera_shop.py deleted file mode 100644 index d4b8ada..0000000 --- a/website_sale_aplicoop/tests/test_phase2_eskaera_shop.py +++ /dev/null @@ -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 diff --git a/website_sale_aplicoop/tests/test_portal_access.py b/website_sale_aplicoop/tests/test_portal_access.py deleted file mode 100644 index 4131914..0000000 --- a/website_sale_aplicoop/tests/test_portal_access.py +++ /dev/null @@ -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/ 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) diff --git a/website_sale_aplicoop/tests/test_portal_get_routes.py b/website_sale_aplicoop/tests/test_portal_get_routes.py deleted file mode 100644 index 6036621..0000000 --- a/website_sale_aplicoop/tests/test_portal_get_routes.py +++ /dev/null @@ -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}") diff --git a/website_sale_aplicoop/tests/test_portal_product_uom_access.py b/website_sale_aplicoop/tests/test_portal_product_uom_access.py deleted file mode 100644 index 3cbb5dd..0000000 --- a/website_sale_aplicoop/tests/test_portal_product_uom_access.py +++ /dev/null @@ -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) diff --git a/website_sale_aplicoop/tests/test_price_with_taxes_included.py b/website_sale_aplicoop/tests/test_price_with_taxes_included.py deleted file mode 100644 index 5bdee16..0000000 --- a/website_sale_aplicoop/tests/test_price_with_taxes_included.py +++ /dev/null @@ -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}", - ) diff --git a/website_sale_aplicoop/tests/test_product_discovery.py b/website_sale_aplicoop/tests/test_product_discovery.py deleted file mode 100644 index 0f5c3c9..0000000 --- a/website_sale_aplicoop/tests/test_product_discovery.py +++ /dev/null @@ -1,1306 +0,0 @@ -# Copyright 2025 Criptomart -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) - -""" -Test suite for product discovery logic in website_sale_aplicoop. - -The discovery mechanism uses 3 sources: -1. product_ids: Directly linked products -2. category_ids: Products from linked categories (recursive) -3. supplier_ids: Products from linked suppliers - -Coverage: -- Correct union of all 3 sources (no duplicates) -- Deep category hierarchies (nested categories) -- Empty sources (empty categories/suppliers) -- Product filters (is_published, sale_ok) -- Ordering and deduplication -""" - -from datetime import datetime -from datetime import timedelta - -from odoo.tests.common import TransactionCase - - -class TestProductDiscoveryUnion(TransactionCase): - """Test that product discovery returns correct union of 3 sources.""" - - def setUp(self): - super().setUp() - self.group = self.env["res.partner"].create( - { - "name": "Test Group", - "is_company": True, - } - ) - - # Create a supplier - self.supplier = self.env["res.partner"].create( - { - "name": "Test Supplier", - "is_supplier": True, - } - ) - - # Create categories - self.category1 = self.env["product.category"].create( - { - "name": "Category 1", - } - ) - - self.category2 = self.env["product.category"].create( - { - "name": "Category 2", - } - ) - - # Create products - # Direct product - self.direct_product = self.env["product.product"].create( - { - "name": "Direct Product", - "type": "consu", - "list_price": 10.0, - "is_published": True, - "sale_ok": True, - } - ) - - # Category 1 product - self.cat1_product = self.env["product.product"].create( - { - "name": "Category 1 Product", - "type": "consu", - "list_price": 20.0, - "categ_id": self.category1.id, - "is_published": True, - "sale_ok": True, - } - ) - - # Category 2 product - self.cat2_product = self.env["product.product"].create( - { - "name": "Category 2 Product", - "type": "consu", - "list_price": 30.0, - "categ_id": self.category2.id, - "is_published": True, - "sale_ok": True, - } - ) - - # Supplier product - self.supplier_product = self.env["product.product"].create( - { - "name": "Supplier Product", - "type": "consu", - "list_price": 40.0, - "categ_id": self.category1.id, # Also in category - "seller_ids": [ - ( - 0, - 0, - { - "partner_id": self.supplier.id, - "product_name": "Supplier Product", - }, - ) - ], - "is_published": True, - "sale_ok": 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_discovery_from_direct_products(self): - """Test discovery returns directly linked products.""" - self.group_order.product_ids = [(4, self.direct_product.id)] - - discovered = self.group_order.product_ids - self.assertIn(self.direct_product, discovered) - - def test_discovery_from_categories(self): - """Test discovery includes products from linked categories.""" - self.group_order.category_ids = [(4, self.category1.id)] - - # Computed placeholder to ensure discovery logic is exercised during test setup - _ = self.group_order.product_ids - # Should include cat1_product and supplier_product (both in category1) - # Note: depends on how discovery is computed - - def test_discovery_from_suppliers(self): - """Test discovery includes products from linked suppliers.""" - self.group_order.supplier_ids = [(4, self.supplier.id)] - - # Should include supplier_product - # Note: depends on how supplier link is implemented - - def test_discovery_union_no_duplicates(self): - """Test that union doesn't include same product twice.""" - # Add supplier_product via: - # 1. Direct link - # 2. Category link (cat1) - # 3. Supplier link - - self.group_order.product_ids = [(4, self.supplier_product.id)] - self.group_order.category_ids = [(4, self.category1.id)] - self.group_order.supplier_ids = [(4, self.supplier.id)] - - discovered = self.group_order.product_ids - - # Count occurrences of supplier_product - count = sum(1 for p in discovered if p == self.supplier_product) - # Should appear only once - self.assertEqual(count, 1) - - def test_discovery_filters_unpublished(self): - """Test that unpublished products are excluded from discovery.""" - unpublished = self.env["product.product"].create( - { - "name": "Unpublished Product", - "type": "consu", - "list_price": 50.0, - "categ_id": self.category1.id, - "is_published": False, - "sale_ok": True, - } - ) - - self.group_order.category_ids = [(4, self.category1.id)] - discovered = self.group_order.product_ids - - # Unpublished should not be in discovered - self.assertNotIn(unpublished, discovered) - - def test_discovery_filters_not_for_sale(self): - """Test that non-sellable products are excluded.""" - not_for_sale = self.env["product.product"].create( - { - "name": "Not For Sale", - "type": "consu", - "list_price": 60.0, - "categ_id": self.category1.id, - "is_published": True, - "sale_ok": False, - } - ) - - self.group_order.category_ids = [(4, self.category1.id)] - discovered = self.group_order.product_ids - - # Not for sale should not be in discovered - self.assertNotIn(not_for_sale, discovered) - - -class TestDeepCategoryHierarchies(TransactionCase): - """Test product discovery with nested category structures.""" - - def setUp(self): - super().setUp() - self.group = self.env["res.partner"].create( - { - "name": "Test Group", - "is_company": True, - } - ) - - # Create nested category structure: - # Root -> L1 -> L2 -> L3 -> L4 - self.cat_l1 = self.env["product.category"].create( - { - "name": "Level 1", - } - ) - - self.cat_l2 = self.env["product.category"].create( - { - "name": "Level 2", - "parent_id": self.cat_l1.id, - } - ) - - self.cat_l3 = self.env["product.category"].create( - { - "name": "Level 3", - "parent_id": self.cat_l2.id, - } - ) - - self.cat_l4 = self.env["product.category"].create( - { - "name": "Level 4", - "parent_id": self.cat_l3.id, - } - ) - - self.cat_l5 = self.env["product.category"].create( - { - "name": "Level 5", - "parent_id": self.cat_l4.id, - } - ) - - # Create products at each level - self.product_l2 = self.env["product.product"].create( - { - "name": "Product L2", - "type": "consu", - "list_price": 10.0, - "categ_id": self.cat_l2.id, - "is_published": True, - "sale_ok": True, - } - ) - - self.product_l4 = self.env["product.product"].create( - { - "name": "Product L4", - "type": "consu", - "list_price": 20.0, - "categ_id": self.cat_l4.id, - "is_published": True, - "sale_ok": True, - } - ) - - self.product_l5 = self.env["product.product"].create( - { - "name": "Product L5", - "type": "consu", - "list_price": 30.0, - "categ_id": self.cat_l5.id, - "is_published": True, - "sale_ok": 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_discovery_root_category_includes_all_descendants(self): - """Test that linking root category discovers all nested products.""" - self.group_order.category_ids = [(4, self.cat_l1.id)] - - discovered = self.group_order.product_ids - - # Should include products from L2, L4, L5 (all descendants) - self.assertIn(self.product_l2, discovered) - self.assertIn(self.product_l4, discovered) - self.assertIn(self.product_l5, discovered) - - def test_discovery_mid_level_category_includes_descendants(self): - """Test discovery from middle of hierarchy.""" - self.group_order.category_ids = [(4, self.cat_l3.id)] - - discovered = self.group_order.product_ids - - # Should include L4 and L5 (descendants of L3) - self.assertIn(self.product_l4, discovered) - self.assertIn(self.product_l5, discovered) - - # Should not include L2 (ancestor) - self.assertNotIn(self.product_l2, discovered) - - def test_discovery_leaf_category_only_own_products(self): - """Test discovery from leaf (deepest) category.""" - self.group_order.category_ids = [(4, self.cat_l5.id)] - - discovered = self.group_order.product_ids - - # Should only include products directly in L5 - self.assertIn(self.product_l5, discovered) - self.assertNotIn(self.product_l4, discovered) - - def test_discovery_circular_category_reference(self): - """Test handling of circular category references (edge case).""" - # Create circular reference (if allowed): L1 -> L2 -> L1 - # This should be prevented by Odoo constraints - # or handled gracefully in discovery logic - - # Attempt to create circular ref may fail - try: - self.cat_l1.parent_id = self.cat_l5.id # Creates loop - except Exception as exc: - # Expected: Odoo should prevent circular refs. Log for visibility. - import logging - - logging.getLogger(__name__).info( - "Expected exception creating circular category: %s", str(exc) - ) - - -class TestEmptySourcesDiscovery(TransactionCase): - """Test discovery behavior with empty/null sources.""" - - def setUp(self): - super().setUp() - self.group = self.env["res.partner"].create( - { - "name": "Test Group", - "is_company": True, - } - ) - - self.category = self.env["product.category"].create( - { - "name": "Empty Category", - } - ) - # No products in this category - - self.supplier = self.env["res.partner"].create( - { - "name": "Supplier No Products", - "is_supplier": True, - } - ) - # No products from this supplier - - 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_discovery_empty_category(self): - """Test discovery from empty category.""" - self.group_order.category_ids = [(4, self.category.id)] - - discovered = self.group_order.product_ids - - # Should return empty list - self.assertEqual(len(discovered), 0) - - def test_discovery_empty_supplier(self): - """Test discovery from supplier with no products.""" - self.group_order.supplier_ids = [(4, self.supplier.id)] - - discovered = self.group_order.product_ids - - # Should return empty list - self.assertEqual(len(discovered), 0) - - def test_discovery_all_sources_empty(self): - """Test when all 3 sources are empty.""" - # No direct products, empty category, empty supplier - self.group_order.product_ids = [(6, 0, [])] - self.group_order.category_ids = [(4, self.category.id)] - self.group_order.supplier_ids = [(4, self.supplier.id)] - - discovered = self.group_order.product_ids - - # Should return empty - self.assertEqual(len(discovered), 0) - - -class TestProductDiscoveryOrdering(TransactionCase): - """Test that discovered products are returned in consistent order.""" - - def setUp(self): - super().setUp() - self.group = self.env["res.partner"].create( - { - "name": "Test Group", - "is_company": True, - } - ) - - self.category = self.env["product.category"].create( - { - "name": "Test Category", - } - ) - - # Create products with specific names - self.products = [] - for i in range(5): - product = self.env["product.product"].create( - { - "name": f"Product {chr(65 + i)}", # A, B, C, D, E - "type": "consu", - "list_price": (i + 1) * 10.0, - "categ_id": self.category.id, - "is_published": True, - "sale_ok": True, - } - ) - self.products.append(product) - - 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_discovery_consistent_ordering(self): - """Test that repeated calls return same order.""" - self.group_order.category_ids = [(4, self.category.id)] - - discovered1 = list(self.group_order.product_ids) - discovered2 = list(self.group_order.product_ids) - - # Order should be consistent - self.assertEqual([p.id for p in discovered1], [p.id for p in discovered2]) - - def test_discovery_alphabetical_or_price_order(self): - """Test that products are ordered predictably.""" - self.group_order.category_ids = [(4, self.category.id)] - - discovered = list(self.group_order.product_ids) - - # Should be in some consistent order (name, price, ID, etc) - # Verify they're the same products, regardless of order - self.assertEqual(len(discovered), 5) - discovered_ids = {p.id for p in discovered} - expected_ids = {p.id for p in self.products} - self.assertEqual(discovered_ids, expected_ids) - - -class TestProductBlacklist(TransactionCase): - """Test blacklist (excluded_product_ids) functionality. - - The blacklist must have absolute priority over all inclusion sources: - - Direct product_ids - - Products from category_ids - - Products from supplier_ids - - If a product is in excluded_product_ids, it should NEVER appear in - the discovered products, regardless of how it was included. - """ - - def setUp(self): - super().setUp() - self.group = self.env["res.partner"].create( - { - "name": "Test Group", - "is_company": True, - } - ) - - # Create a supplier - self.supplier = self.env["res.partner"].create( - { - "name": "Test Supplier", - "is_company": True, - "supplier_rank": 1, - } - ) - - # Create category - self.category = self.env["product.category"].create( - { - "name": "Test Category", - } - ) - - # Create products - # 1. Direct product (will be added to product_ids) - self.direct_product = self.env["product.product"].create( - { - "name": "Direct Product", - "type": "consu", - "list_price": 10.0, - "is_published": True, - "sale_ok": True, - } - ) - - # 2. Category product (will be included via category_ids) - self.category_product = self.env["product.product"].create( - { - "name": "Category Product", - "type": "consu", - "list_price": 20.0, - "categ_id": self.category.id, - "is_published": True, - "sale_ok": True, - } - ) - - # 3. Supplier product (will be included via supplier_ids) - product_tmpl = self.env["product.template"].create( - { - "name": "Supplier Product", - "type": "consu", - "list_price": 30.0, - "is_published": True, - "sale_ok": True, - } - ) - self.supplier_product = product_tmpl.product_variant_ids[0] - self.env["product.supplierinfo"].create( - { - "partner_id": self.supplier.id, - "product_tmpl_id": product_tmpl.id, - "price": 25.0, - } - ) - - # 4. Multi-source product (in all three sources) - multi_tmpl = self.env["product.template"].create( - { - "name": "Multi Source Product", - "type": "consu", - "list_price": 40.0, - "categ_id": self.category.id, - "is_published": True, - "sale_ok": True, - } - ) - self.multi_product = multi_tmpl.product_variant_ids[0] - self.env["product.supplierinfo"].create( - { - "partner_id": self.supplier.id, - "product_tmpl_id": multi_tmpl.id, - "price": 35.0, - } - ) - - start_date = datetime.now().date() - self.group_order = self.env["group.order"].create( - { - "name": "Test Blacklist 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_blacklist_excludes_direct_product(self): - """Test that excluded_product_ids filters out directly linked products.""" - # Add product directly - self.group_order.product_ids = [(4, self.direct_product.id)] - - # Product should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.direct_product, products) - - # Now add to blacklist - self.group_order.excluded_product_ids = [(4, self.direct_product.id)] - - # Product should NOT be discoverable anymore - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.direct_product, products) - - def test_blacklist_excludes_category_product(self): - """Test that excluded_product_ids filters out products from categories.""" - # Add category (includes category_product) - self.group_order.category_ids = [(4, self.category.id)] - - # Product should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.category_product, products) - - # Now add to blacklist - self.group_order.excluded_product_ids = [(4, self.category_product.id)] - - # Product should NOT be discoverable anymore - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.category_product, products) - - def test_blacklist_excludes_supplier_product(self): - """Test that excluded_product_ids filters out products from suppliers.""" - # Add supplier (includes supplier_product) - self.group_order.supplier_ids = [(4, self.supplier.id)] - - # Product should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.supplier_product, products) - - # Now add to blacklist - self.group_order.excluded_product_ids = [(4, self.supplier_product.id)] - - # Product should NOT be discoverable anymore - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.supplier_product, products) - - def test_blacklist_priority_over_all_sources(self): - """Test that blacklist has absolute priority even for multi-source products.""" - # Add multi_product via all three sources - self.group_order.product_ids = [(4, self.multi_product.id)] - self.group_order.category_ids = [(4, self.category.id)] - self.group_order.supplier_ids = [(4, self.supplier.id)] - - # Product should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.multi_product, products) - - # Now add to blacklist - self.group_order.excluded_product_ids = [(4, self.multi_product.id)] - - # Product should NOT be discoverable anymore, despite being in all sources - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.multi_product, products) - - def test_empty_blacklist_no_effect(self): - """Test that empty excluded_product_ids doesn't affect discovery.""" - # Add products via various sources - self.group_order.product_ids = [(4, self.direct_product.id)] - self.group_order.category_ids = [(4, self.category.id)] - self.group_order.supplier_ids = [(4, self.supplier.id)] - - # All products should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.direct_product, products) - self.assertIn(self.category_product, products) - self.assertIn(self.supplier_product, products) - - # Excluded list is empty - should have no effect - self.assertEqual(len(self.group_order.excluded_product_ids), 0) - - def test_blacklist_multiple_products(self): - """Test excluding multiple products at once.""" - # Add all products - self.group_order.product_ids = [(4, self.direct_product.id)] - self.group_order.category_ids = [(4, self.category.id)] - self.group_order.supplier_ids = [(4, self.supplier.id)] - - # Exclude two products - self.group_order.excluded_product_ids = [ - (4, self.direct_product.id), - (4, self.category_product.id), - ] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # These two should NOT be in results - self.assertNotIn(self.direct_product, products) - self.assertNotIn(self.category_product, products) - - # But supplier_product should still be there - self.assertIn(self.supplier_product, products) - - def test_blacklist_available_products_count(self): - """Test that available_products_count reflects blacklist.""" - # Add products - self.group_order.product_ids = [(4, self.direct_product.id)] - self.group_order.category_ids = [(4, self.category.id)] - - # Count should include direct + category products - initial_count = self.group_order.available_products_count - self.assertGreater(initial_count, 0) - - # Exclude one product - self.group_order.excluded_product_ids = [(4, self.category_product.id)] - - # Count should decrease by 1 - new_count = self.group_order.available_products_count - self.assertEqual(new_count, initial_count - 1) - - -class TestSupplierBlacklist(TransactionCase): - """Test supplier blacklist (excluded_supplier_ids) functionality. - - The supplier blacklist filters out products whose main_seller_id - (from product_main_seller addon) is in the excluded suppliers list. - - Blacklist has absolute priority over inclusion sources. - """ - - def setUp(self): - super().setUp() - self.group = self.env["res.partner"].create( - { - "name": "Test Group", - "is_company": True, - } - ) - - # Create suppliers - self.supplier_A = self.env["res.partner"].create( - { - "name": "Supplier A", - "is_company": True, - "supplier_rank": 1, - } - ) - - self.supplier_B = self.env["res.partner"].create( - { - "name": "Supplier B", - "is_company": True, - "supplier_rank": 1, - } - ) - - self.supplier_C = self.env["res.partner"].create( - { - "name": "Supplier C", - "is_company": True, - "supplier_rank": 1, - } - ) - - # Create category - self.category = self.env["product.category"].create( - { - "name": "Test Category", - } - ) - - # Create products with different main sellers - # Product 1: main seller = Supplier A - tmpl_1 = self.env["product.template"].create( - { - "name": "Product from Supplier A", - "type": "consu", - "list_price": 10.0, - "categ_id": self.category.id, - "is_published": True, - "sale_ok": True, - "main_seller_id": self.supplier_A.id, - } - ) - self.product_A = tmpl_1.product_variant_ids[0] - - # Product 2: main seller = Supplier B - tmpl_2 = self.env["product.template"].create( - { - "name": "Product from Supplier B", - "type": "consu", - "list_price": 20.0, - "categ_id": self.category.id, - "is_published": True, - "sale_ok": True, - "main_seller_id": self.supplier_B.id, - } - ) - self.product_B = tmpl_2.product_variant_ids[0] - - # Product 3: main seller = Supplier C - tmpl_3 = self.env["product.template"].create( - { - "name": "Product from Supplier C", - "type": "consu", - "list_price": 30.0, - "categ_id": self.category.id, - "is_published": True, - "sale_ok": True, - "main_seller_id": self.supplier_C.id, - } - ) - self.product_C = tmpl_3.product_variant_ids[0] - - # Product 4: no main seller - tmpl_4 = self.env["product.template"].create( - { - "name": "Product without main seller", - "type": "consu", - "list_price": 40.0, - "categ_id": self.category.id, - "is_published": True, - "sale_ok": True, - } - ) - self.product_no_seller = tmpl_4.product_variant_ids[0] - - start_date = datetime.now().date() - self.group_order = self.env["group.order"].create( - { - "name": "Test Supplier Blacklist 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_supplier_blacklist_excludes_by_main_seller(self): - """Test that supplier blacklist excludes products by main_seller_id.""" - # Add all products via category - self.group_order.category_ids = [(4, self.category.id)] - - # All products should be discoverable initially - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.product_A, products) - self.assertIn(self.product_B, products) - self.assertIn(self.product_C, products) - self.assertIn(self.product_no_seller, products) - - # Exclude Supplier A - self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)] - - # Product A should NOT be discoverable anymore - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.product_A, products) - self.assertIn(self.product_B, products) - self.assertIn(self.product_C, products) - self.assertIn(self.product_no_seller, products) - - def test_supplier_blacklist_multiple_suppliers(self): - """Test excluding multiple suppliers at once.""" - # Add all products via category - self.group_order.category_ids = [(4, self.category.id)] - - # Exclude Suppliers A and B - self.group_order.excluded_supplier_ids = [ - (4, self.supplier_A.id), - (4, self.supplier_B.id), - ] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Products A and B should NOT be in results - self.assertNotIn(self.product_A, products) - self.assertNotIn(self.product_B, products) - - # But product C and no-seller product should be there - self.assertIn(self.product_C, products) - self.assertIn(self.product_no_seller, products) - - def test_supplier_blacklist_does_not_affect_no_main_seller(self): - """Test that products without main_seller_id are not affected by supplier blacklist.""" - # Add all products via category - self.group_order.category_ids = [(4, self.category.id)] - - # Exclude all suppliers - self.group_order.excluded_supplier_ids = [ - (4, self.supplier_A.id), - (4, self.supplier_B.id), - (4, self.supplier_C.id), - ] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Products with main sellers should NOT be in results - self.assertNotIn(self.product_A, products) - self.assertNotIn(self.product_B, products) - self.assertNotIn(self.product_C, products) - - # But product without main seller should still be there - self.assertIn(self.product_no_seller, products) - - def test_supplier_blacklist_with_direct_product_inclusion(self): - """Test that supplier blacklist affects even directly included products.""" - # Add product A directly - self.group_order.product_ids = [(4, self.product_A.id)] - - # Product should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.product_A, products) - - # Exclude Supplier A - self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)] - - # Product A should NOT be discoverable anymore, even though directly included - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.product_A, products) - - def test_supplier_blacklist_with_supplier_inclusion(self): - """Test that supplier blacklist has priority over supplier inclusion.""" - # Add Supplier A to included suppliers - self.group_order.supplier_ids = [(4, self.supplier_A.id)] - - # Also add Supplier A to excluded suppliers (blacklist has priority) - self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Product A should NOT be discoverable (blacklist wins) - self.assertNotIn(self.product_A, products) - - def test_empty_supplier_blacklist_no_effect(self): - """Test that empty excluded_supplier_ids doesn't affect discovery.""" - # Add products via category - self.group_order.category_ids = [(4, self.category.id)] - - # All products should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.product_A, products) - self.assertIn(self.product_B, products) - self.assertIn(self.product_C, products) - - # Excluded supplier list is empty - should have no effect - self.assertEqual(len(self.group_order.excluded_supplier_ids), 0) - - def test_supplier_and_product_blacklist_combined(self): - """Test that both product and supplier blacklists work together.""" - # Add all products via category - self.group_order.category_ids = [(4, self.category.id)] - - # Exclude Supplier A (affects product_A) - self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)] - - # Exclude product B directly - self.group_order.excluded_product_ids = [(4, self.product_B.id)] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Products A and B should NOT be in results - self.assertNotIn(self.product_A, products) - self.assertNotIn(self.product_B, products) - - # But products C and no-seller should be there - self.assertIn(self.product_C, products) - self.assertIn(self.product_no_seller, products) - - def test_supplier_blacklist_available_products_count(self): - """Test that available_products_count reflects supplier blacklist.""" - # Add products - self.group_order.category_ids = [(4, self.category.id)] - - # Count should include all 4 products - initial_count = self.group_order.available_products_count - self.assertEqual(initial_count, 4) - - # Exclude Supplier A - self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)] - - # Count should decrease by 1 (product_A excluded) - new_count = self.group_order.available_products_count - self.assertEqual(new_count, 3) - - -class TestCategoryBlacklist(TransactionCase): - """Test category blacklist (excluded_category_ids) functionality. - - The category blacklist filters out products in the excluded categories - AND all their subcategories (recursive). - - Blacklist has absolute priority over inclusion sources. - """ - - def setUp(self): - super().setUp() - self.group = self.env["res.partner"].create( - { - "name": "Test Group", - "is_company": True, - } - ) - - # Create category hierarchy: - # Parent Category - # ├── Child Category A - # │ └── Grandchild Category A1 - # └── Child Category B - self.parent_category = self.env["product.category"].create( - { - "name": "Parent Category", - } - ) - - self.child_category_A = self.env["product.category"].create( - { - "name": "Child Category A", - "parent_id": self.parent_category.id, - } - ) - - self.grandchild_category_A1 = self.env["product.category"].create( - { - "name": "Grandchild Category A1", - "parent_id": self.child_category_A.id, - } - ) - - self.child_category_B = self.env["product.category"].create( - { - "name": "Child Category B", - "parent_id": self.parent_category.id, - } - ) - - self.other_category = self.env["product.category"].create( - { - "name": "Other Category (not in hierarchy)", - } - ) - - # Create products in different categories - # Product in parent category - self.product_parent = self.env["product.product"].create( - { - "name": "Product in Parent Category", - "type": "consu", - "list_price": 10.0, - "categ_id": self.parent_category.id, - "is_published": True, - "sale_ok": True, - } - ) - - # Product in Child A - self.product_child_A = self.env["product.product"].create( - { - "name": "Product in Child A", - "type": "consu", - "list_price": 20.0, - "categ_id": self.child_category_A.id, - "is_published": True, - "sale_ok": True, - } - ) - - # Product in Grandchild A1 - self.product_grandchild_A1 = self.env["product.product"].create( - { - "name": "Product in Grandchild A1", - "type": "consu", - "list_price": 30.0, - "categ_id": self.grandchild_category_A1.id, - "is_published": True, - "sale_ok": True, - } - ) - - # Product in Child B - self.product_child_B = self.env["product.product"].create( - { - "name": "Product in Child B", - "type": "consu", - "list_price": 40.0, - "categ_id": self.child_category_B.id, - "is_published": True, - "sale_ok": True, - } - ) - - # Product in Other Category - self.product_other = self.env["product.product"].create( - { - "name": "Product in Other Category", - "type": "consu", - "list_price": 50.0, - "categ_id": self.other_category.id, - "is_published": True, - "sale_ok": True, - } - ) - - start_date = datetime.now().date() - self.group_order = self.env["group.order"].create( - { - "name": "Test Category Blacklist 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_category_blacklist_excludes_single_category(self): - """Test that category blacklist excludes products in that category.""" - # Add parent category to inclusion (includes all products in hierarchy) - self.group_order.category_ids = [(4, self.parent_category.id)] - - # All products should be discoverable initially - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.product_parent, products) - self.assertIn(self.product_child_A, products) - self.assertIn(self.product_grandchild_A1, products) - self.assertIn(self.product_child_B, products) - - # Exclude Child Category B - self.group_order.excluded_category_ids = [(4, self.child_category_B.id)] - - # Product in Child B should NOT be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.product_child_B, products) - # But others should still be there - self.assertIn(self.product_parent, products) - self.assertIn(self.product_child_A, products) - self.assertIn(self.product_grandchild_A1, products) - - def test_category_blacklist_excludes_with_subcategories(self): - """Test that excluding a category also excludes all its subcategories (recursive).""" - # Add parent category to inclusion - self.group_order.category_ids = [(4, self.parent_category.id)] - - # Exclude Child Category A (should also exclude Grandchild A1) - self.group_order.excluded_category_ids = [(4, self.child_category_A.id)] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Products in Child A and Grandchild A1 should NOT be discoverable - self.assertNotIn(self.product_child_A, products) - self.assertNotIn(self.product_grandchild_A1, products) # Recursive exclusion - - # But products in parent and Child B should still be there - self.assertIn(self.product_parent, products) - self.assertIn(self.product_child_B, products) - - def test_category_blacklist_excludes_parent_excludes_all_children(self): - """Test that excluding parent category excludes ALL descendants.""" - # Add parent category to inclusion (includes all) - self.group_order.category_ids = [(4, self.parent_category.id)] - - # Exclude the parent category (should exclude ALL products in hierarchy) - self.group_order.excluded_category_ids = [(4, self.parent_category.id)] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # ALL products in the hierarchy should be excluded - self.assertNotIn(self.product_parent, products) - self.assertNotIn(self.product_child_A, products) - self.assertNotIn(self.product_grandchild_A1, products) - self.assertNotIn(self.product_child_B, products) - - # Result should be empty (no products available) - self.assertEqual(len(products), 0) - - def test_category_blacklist_with_direct_product_inclusion(self): - """Test that category blacklist affects even directly included products.""" - # Add product directly - self.group_order.product_ids = [(4, self.product_child_A.id)] - - # Product should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.product_child_A, products) - - # Exclude the category - self.group_order.excluded_category_ids = [(4, self.child_category_A.id)] - - # Product should NOT be discoverable anymore, even though directly included - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertNotIn(self.product_child_A, products) - - def test_category_blacklist_does_not_affect_other_categories(self): - """Test that excluding a category doesn't affect products in unrelated categories.""" - # Add all categories to inclusion - self.group_order.category_ids = [ - (4, self.parent_category.id), - (4, self.other_category.id), - ] - - # Exclude parent category - self.group_order.excluded_category_ids = [(4, self.parent_category.id)] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Products in parent hierarchy should NOT be there - self.assertNotIn(self.product_parent, products) - self.assertNotIn(self.product_child_A, products) - self.assertNotIn(self.product_child_B, products) - - # But product in other category should still be there - self.assertIn(self.product_other, products) - - def test_empty_category_blacklist_no_effect(self): - """Test that empty excluded_category_ids doesn't affect discovery.""" - # Add parent category - self.group_order.category_ids = [(4, self.parent_category.id)] - - # All products should be discoverable - products = self.group_order._get_products_for_group_order(self.group_order.id) - self.assertIn(self.product_parent, products) - self.assertIn(self.product_child_A, products) - self.assertIn(self.product_grandchild_A1, products) - self.assertIn(self.product_child_B, products) - - # Excluded category list is empty - should have no effect - self.assertEqual(len(self.group_order.excluded_category_ids), 0) - - def test_multiple_category_exclusions(self): - """Test excluding multiple categories at once.""" - # Add parent category - self.group_order.category_ids = [(4, self.parent_category.id)] - - # Exclude both child categories - self.group_order.excluded_category_ids = [ - (4, self.child_category_A.id), - (4, self.child_category_B.id), - ] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Products in both child categories (+ grandchild) should NOT be there - self.assertNotIn(self.product_child_A, products) - self.assertNotIn(self.product_grandchild_A1, products) - self.assertNotIn(self.product_child_B, products) - - # But product in parent category should still be there - self.assertIn(self.product_parent, products) - - def test_category_blacklist_combined_with_other_blacklists(self): - """Test that category blacklist works together with product and supplier blacklists.""" - # Add parent category - self.group_order.category_ids = [(4, self.parent_category.id)] - - # Exclude Child A category (affects product_child_A and product_grandchild_A1) - self.group_order.excluded_category_ids = [(4, self.child_category_A.id)] - - # Also exclude product_child_B directly - self.group_order.excluded_product_ids = [(4, self.product_child_B.id)] - - products = self.group_order._get_products_for_group_order(self.group_order.id) - - # Products excluded by category blacklist - self.assertNotIn(self.product_child_A, products) - self.assertNotIn(self.product_grandchild_A1, products) - - # Product excluded by product blacklist - self.assertNotIn(self.product_child_B, products) - - # Only product_parent should be available - self.assertIn(self.product_parent, products) - self.assertEqual(len(products), 1) - - def test_category_blacklist_available_products_count(self): - """Test that available_products_count reflects category blacklist.""" - # Add parent category (4 products in hierarchy) - self.group_order.category_ids = [(4, self.parent_category.id)] - - # Count should include all 4 products - initial_count = self.group_order.available_products_count - self.assertEqual(initial_count, 4) - - # Exclude Child Category A (should exclude 2 products: child_A + grandchild_A1) - self.group_order.excluded_category_ids = [(4, self.child_category_A.id)] - - # Count should decrease by 2 - new_count = self.group_order.available_products_count - self.assertEqual(new_count, 2) diff --git a/website_sale_aplicoop/tests/test_validations.py b/website_sale_aplicoop/tests/test_validations.py deleted file mode 100644 index 70a63d2..0000000 --- a/website_sale_aplicoop/tests/test_validations.py +++ /dev/null @@ -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) diff --git a/website_sale_aplicoop/views/website_templates.xml b/website_sale_aplicoop/views/website_templates.xml index d832ffe..dfae385 100644 --- a/website_sale_aplicoop/views/website_templates.xml +++ b/website_sale_aplicoop/views/website_templates.xml @@ -107,12 +107,6 @@ - -