[REF] Code quality improvements and structure fixes
- Add mypy.ini configuration to exclude migration scripts - Rename migration files to proper snake_case (post-migration.py → post_migration.py) - Add __init__.py to migration directories for proper Python package structure - Add new portal access tests for website_sale_aplicoop - Code formatting improvements (black, isort) - Update copilot instructions and project configuration Related to previous code quality refactoring work.
This commit is contained in:
parent
380d05785f
commit
cf9ea887c1
30 changed files with 1129 additions and 1102 deletions
13
.github/copilot-instructions.md
vendored
13
.github/copilot-instructions.md
vendored
|
|
@ -1,3 +1,16 @@
|
||||||
|
# ⚠️ Addons OCA Originales y OCB (Odoo)
|
||||||
|
|
||||||
|
No modificar el directorio de fuentes de OCB (`ocb/`) ni los siguientes addons OCA originales:
|
||||||
|
|
||||||
|
- `product_main_seller`
|
||||||
|
- `product_origin`
|
||||||
|
- `account_invoice_triple_discount`
|
||||||
|
- `product_get_price_helper`
|
||||||
|
- `product_price_category`
|
||||||
|
- `purchase_triple_discount`
|
||||||
|
|
||||||
|
Estos módulos y el core de Odoo (OCB) solo están para referencia y herencia de nuestros addons custom. Cualquier cambio debe hacerse en los addons propios, nunca en los OCA originales ni en el core OCB.
|
||||||
|
|
||||||
# AI Agent Skills & Prompt Guidance
|
# AI Agent Skills & Prompt Guidance
|
||||||
|
|
||||||
Para máxima productividad y calidad, los agentes AI deben seguir estas pautas y consultar los archivos de skills detallados:
|
Para máxima productividad y calidad, los agentes AI deben seguir estas pautas y consultar los archivos de skills detallados:
|
||||||
|
|
|
||||||
5
mypy.ini
Normal file
5
mypy.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
[mypy]
|
||||||
|
# Exclude migration scripts (post-migrate.py etc.) from mypy checks to avoid
|
||||||
|
# duplicate module name errors when multiple addons include scripts with the
|
||||||
|
# same filename.
|
||||||
|
exclude = .*/migrations/.*
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
# Copyright 2026 Your Company
|
# Copyright 2026 Your Company
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import _
|
|
||||||
from odoo import api
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
@ -41,7 +39,7 @@ class ResPartner(models.Model):
|
||||||
# Return action to open wizard modal
|
# Return action to open wizard modal
|
||||||
return {
|
return {
|
||||||
"type": "ir.actions.act_window",
|
"type": "ir.actions.act_window",
|
||||||
"name": _("Update Product Price Category"),
|
"name": self.env._("Update Product Price Category"),
|
||||||
"res_model": "wizard.update.product.category",
|
"res_model": "wizard.update.product.category",
|
||||||
"res_id": wizard.id,
|
"res_id": wizard.id,
|
||||||
"view_mode": "form",
|
"view_mode": "form",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
# Copyright 2026 Your Company
|
# Copyright 2026 Your Company
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import _
|
|
||||||
from odoo import api
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
@ -53,8 +51,8 @@ class WizardUpdateProductCategory(models.TransientModel):
|
||||||
"type": "ir.actions.client",
|
"type": "ir.actions.client",
|
||||||
"tag": "display_notification",
|
"tag": "display_notification",
|
||||||
"params": {
|
"params": {
|
||||||
"title": _("No Products"),
|
"title": self.env._("No Products"),
|
||||||
"message": _("No products found with this supplier."),
|
"message": self.env._("No products found with this supplier."),
|
||||||
"type": "warning",
|
"type": "warning",
|
||||||
"sticky": False,
|
"sticky": False,
|
||||||
},
|
},
|
||||||
|
|
@ -67,9 +65,12 @@ class WizardUpdateProductCategory(models.TransientModel):
|
||||||
"type": "ir.actions.client",
|
"type": "ir.actions.client",
|
||||||
"tag": "display_notification",
|
"tag": "display_notification",
|
||||||
"params": {
|
"params": {
|
||||||
"title": _("Success"),
|
"title": self.env._("Success"),
|
||||||
"message": _('%d products updated with category "%s".')
|
"message": self.env._(
|
||||||
% (len(products), self.price_category_id.display_name),
|
"%(count)d products updated with category %(category)s",
|
||||||
|
count=len(products),
|
||||||
|
category=self.price_category_id.display_name,
|
||||||
|
),
|
||||||
"type": "success",
|
"type": "success",
|
||||||
"sticky": False,
|
"sticky": False,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
"""Make migrations folder a package so mypy maps module names correctly.
|
||||||
|
|
||||||
|
Empty on purpose.
|
||||||
|
"""
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from odoo import api
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,10 @@ known_odoo = ["odoo"]
|
||||||
known_odoo_addons = ["odoo.addons"]
|
known_odoo_addons = ["odoo.addons"]
|
||||||
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "ODOO", "ODOO_ADDONS", "FIRSTPARTY", "LOCALFOLDER"]
|
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "ODOO", "ODOO_ADDONS", "FIRSTPARTY", "LOCALFOLDER"]
|
||||||
default_section = "THIRDPARTY"
|
default_section = "THIRDPARTY"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
# Excluir carpetas de migraciones y archivos de post-migrate.py que usan guiones
|
||||||
|
# (evita errores de "Duplicate module" en mypy cuando múltiples addons contienen
|
||||||
|
# archivos con el mismo nombre como `post-migrate.py`). Usamos una expresión
|
||||||
|
# regular que coincide con cualquier ruta que contenga `/migrations/`.
|
||||||
|
exclude = "(?i).*/migrations/.*"
|
||||||
|
|
|
||||||
108
test_prices.py
108
test_prices.py
|
|
@ -4,15 +4,18 @@ Script de prueba para verificar que los precios incluyen impuestos.
|
||||||
Se ejecuta dentro del contenedor de Odoo.
|
Se ejecuta dentro del contenedor de Odoo.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Agregar path de Odoo
|
# Agregar path de Odoo
|
||||||
sys.path.insert(0, "/usr/lib/python3/dist-packages")
|
sys.path.insert(0, "/usr/lib/python3/dist-packages")
|
||||||
|
|
||||||
import odoo
|
import odoo # noqa: E402
|
||||||
from odoo import SUPERUSER_ID
|
from odoo import SUPERUSER_ID # noqa: E402
|
||||||
from odoo import api
|
from odoo import api # noqa: E402
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Configurar Odoo
|
# Configurar Odoo
|
||||||
odoo.tools.config["db_host"] = os.environ.get("HOST", "db")
|
odoo.tools.config["db_host"] = os.environ.get("HOST", "db")
|
||||||
|
|
@ -20,9 +23,9 @@ odoo.tools.config["db_port"] = int(os.environ.get("PORT", 5432))
|
||||||
odoo.tools.config["db_user"] = os.environ.get("USER", "odoo")
|
odoo.tools.config["db_user"] = os.environ.get("USER", "odoo")
|
||||||
odoo.tools.config["db_password"] = os.environ.get("PASSWORD", "odoo")
|
odoo.tools.config["db_password"] = os.environ.get("PASSWORD", "odoo")
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
logger.info("\n" + "=" * 60)
|
||||||
print("TEST: Precios con impuestos incluidos")
|
logger.info("TEST: Precios con impuestos incluidos")
|
||||||
print("=" * 60 + "\n")
|
logger.info("=" * 60 + "\n")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db_name = "odoo"
|
db_name = "odoo"
|
||||||
|
|
@ -31,26 +34,26 @@ try:
|
||||||
with registry.cursor() as cr:
|
with registry.cursor() as cr:
|
||||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
|
||||||
print(f"✓ Conectado a BD: {db_name}")
|
logger.info(f"✓ Conectado a BD: {db_name}")
|
||||||
print(f" Usuario: {env.user.name}")
|
logger.info(f" Usuario: {env.user.name}")
|
||||||
print(f" Compañía: {env.company.name}\n")
|
logger.info(f" Compañía: {env.company.name}\n")
|
||||||
|
|
||||||
# Test 1: Verificar módulo
|
# Test 1: Verificar módulo
|
||||||
print("TEST 1: Verificar módulo instalado")
|
logger.info("TEST 1: Verificar módulo instalado")
|
||||||
print("-" * 60)
|
logger.info("-" * 60)
|
||||||
module = env["ir.module.module"].search(
|
module = env["ir.module.module"].search(
|
||||||
[("name", "=", "website_sale_aplicoop")], limit=1
|
[("name", "=", "website_sale_aplicoop")], limit=1
|
||||||
)
|
)
|
||||||
|
|
||||||
if module and module.state == "installed":
|
if module and module.state == "installed":
|
||||||
print(f"✓ Módulo website_sale_aplicoop instalado")
|
logger.info("✓ Módulo website_sale_aplicoop instalado")
|
||||||
else:
|
else:
|
||||||
print(f"✗ Módulo NO instalado")
|
logger.error("✗ Módulo NO instalado")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Test 2: Verificar método nuevo
|
# Test 2: Verificar método nuevo
|
||||||
print("\nTEST 2: Verificar método _compute_price_with_taxes")
|
logger.info("\nTEST 2: Verificar método _compute_price_with_taxes")
|
||||||
print("-" * 60)
|
logger.info("-" * 60)
|
||||||
try:
|
try:
|
||||||
from odoo.addons.website_sale_aplicoop.controllers.website_sale import (
|
from odoo.addons.website_sale_aplicoop.controllers.website_sale import (
|
||||||
AplicoopWebsiteSale,
|
AplicoopWebsiteSale,
|
||||||
|
|
@ -59,20 +62,20 @@ try:
|
||||||
controller = AplicoopWebsiteSale()
|
controller = AplicoopWebsiteSale()
|
||||||
|
|
||||||
if hasattr(controller, "_compute_price_with_taxes"):
|
if hasattr(controller, "_compute_price_with_taxes"):
|
||||||
print("✓ Método _compute_price_with_taxes existe")
|
logger.info("✓ Método _compute_price_with_taxes existe")
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
sig = inspect.signature(controller._compute_price_with_taxes)
|
sig = inspect.signature(controller._compute_price_with_taxes)
|
||||||
print(f" Firma: {sig}")
|
logger.info(f" Firma: {sig}")
|
||||||
else:
|
else:
|
||||||
print("✗ Método NO encontrado")
|
logger.error("✗ Método NO encontrado")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ Error: {e}")
|
logger.exception("✗ Error verificando método: %s", e)
|
||||||
|
|
||||||
# Test 3: Probar cálculo de impuestos
|
# Test 3: Probar cálculo de impuestos
|
||||||
print("\nTEST 3: Calcular precio con impuestos")
|
logger.info("\nTEST 3: Calcular precio con impuestos")
|
||||||
print("-" * 60)
|
logger.info("-" * 60)
|
||||||
|
|
||||||
# Buscar un producto con impuestos
|
# Buscar un producto con impuestos
|
||||||
product = env["product.product"].search(
|
product = env["product.product"].search(
|
||||||
|
|
@ -80,7 +83,7 @@ try:
|
||||||
)
|
)
|
||||||
|
|
||||||
if not product:
|
if not product:
|
||||||
print(" Creando producto de prueba...")
|
logger.info(" Creando producto de prueba...")
|
||||||
|
|
||||||
# Buscar impuesto existente
|
# Buscar impuesto existente
|
||||||
tax = env["account.tax"].search(
|
tax = env["account.tax"].search(
|
||||||
|
|
@ -97,19 +100,22 @@ try:
|
||||||
"sale_ok": True,
|
"sale_ok": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
print(f" Producto creado: {product.name}")
|
logger.info(f" Producto creado: {product.name}")
|
||||||
else:
|
else:
|
||||||
print(" ✗ No hay impuestos de venta configurados")
|
logger.error(" ✗ No hay impuestos de venta configurados")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
print(f" Producto encontrado: {product.name}")
|
logger.info(f" Producto encontrado: {product.name}")
|
||||||
|
|
||||||
print(f" Precio de lista: {product.list_price:.2f} €")
|
logger.info(f" Precio de lista: {product.list_price:.2f} €")
|
||||||
|
|
||||||
taxes = product.taxes_id.filtered(lambda t: t.company_id == env.company)
|
taxes = product.taxes_id.filtered(lambda t: t.company_id == env.company)
|
||||||
|
|
||||||
if taxes:
|
if taxes:
|
||||||
print(f" Impuestos: {', '.join(f'{t.name} ({t.amount}%)' for t in taxes)}")
|
logger.info(
|
||||||
|
" Impuestos: %s",
|
||||||
|
", ".join(f"{t.name} ({t.amount}%)" for t in taxes),
|
||||||
|
)
|
||||||
|
|
||||||
# Calcular precio con impuestos
|
# Calcular precio con impuestos
|
||||||
base_price = product.list_price
|
base_price = product.list_price
|
||||||
|
|
@ -124,24 +130,26 @@ try:
|
||||||
price_with_tax = tax_result["total_included"]
|
price_with_tax = tax_result["total_included"]
|
||||||
tax_amount = price_with_tax - price_without_tax
|
tax_amount = price_with_tax - price_without_tax
|
||||||
|
|
||||||
print(f"\n Cálculo:")
|
logger.info("\n Cálculo:")
|
||||||
print(f" Base: {base_price:.2f} €")
|
logger.info(f" Base: {base_price:.2f} €")
|
||||||
print(f" Sin IVA: {price_without_tax:.2f} €")
|
logger.info(f" Sin IVA: {price_without_tax:.2f} €")
|
||||||
print(f" IVA: {tax_amount:.2f} €")
|
logger.info(f" IVA: {tax_amount:.2f} €")
|
||||||
print(f" CON IVA: {price_with_tax:.2f} €")
|
logger.info(f" CON IVA: {price_with_tax:.2f} €")
|
||||||
|
|
||||||
if price_with_tax > price_without_tax:
|
if price_with_tax > price_without_tax:
|
||||||
print(
|
logger.info(
|
||||||
f"\n ✓ PASADO: Precio con IVA ({price_with_tax:.2f}) > sin IVA ({price_without_tax:.2f})"
|
"\n ✓ PASADO: Precio con IVA (%.2f) > sin IVA (%.2f)",
|
||||||
|
price_with_tax,
|
||||||
|
price_without_tax,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(f"\n ✗ FALLADO: Impuestos no se calculan correctamente")
|
logger.error("\n ✗ FALLADO: Impuestos no se calculan correctamente")
|
||||||
else:
|
else:
|
||||||
print(" ⚠ Producto sin impuestos")
|
logger.warning(" ⚠ Producto sin impuestos")
|
||||||
|
|
||||||
# Test 4: Verificar OCA _get_price
|
# Test 4: Verificar OCA _get_price
|
||||||
print("\nTEST 4: Verificar OCA _get_price")
|
logger.info("\nTEST 4: Verificar OCA _get_price")
|
||||||
print("-" * 60)
|
logger.info("-" * 60)
|
||||||
|
|
||||||
pricelist = env["product.pricelist"].search(
|
pricelist = env["product.pricelist"].search(
|
||||||
[("company_id", "=", env.company.id)], limit=1
|
[("company_id", "=", env.company.id)], limit=1
|
||||||
|
|
@ -154,19 +162,21 @@ try:
|
||||||
fposition=False,
|
fposition=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f" OCA _get_price:")
|
logger.info(" OCA _get_price:")
|
||||||
print(f" value: {price_info.get('value', 0):.2f} €")
|
logger.info(" value: %.2f €", price_info.get("value", 0))
|
||||||
print(f" tax_included: {price_info.get('tax_included', False)}")
|
logger.info(
|
||||||
|
" tax_included: %s", str(price_info.get("tax_included", False))
|
||||||
|
)
|
||||||
|
|
||||||
if not price_info.get("tax_included", False):
|
if not price_info.get("tax_included", False):
|
||||||
print(f" ✓ PASADO: OCA retorna precio SIN IVA (esperado)")
|
logger.info(" ✓ PASADO: OCA retorna precio SIN IVA (esperado)")
|
||||||
else:
|
else:
|
||||||
print(f" ⚠ OCA indica IVA incluido")
|
logger.warning(" ⚠ OCA indica IVA incluido")
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
logger.info("\n" + "=" * 60)
|
||||||
print("RESUMEN")
|
logger.info("RESUMEN")
|
||||||
print("=" * 60)
|
logger.info("=" * 60)
|
||||||
print("""
|
logger.info("""
|
||||||
Corrección implementada:
|
Corrección implementada:
|
||||||
1. ✓ Método _compute_price_with_taxes añadido
|
1. ✓ Método _compute_price_with_taxes añadido
|
||||||
2. ✓ Calcula precio CON IVA usando taxes.compute_all()
|
2. ✓ Calcula precio CON IVA usando taxes.compute_all()
|
||||||
|
|
@ -177,10 +187,10 @@ El método OCA _get_price retorna precios SIN IVA.
|
||||||
Nuestra función _compute_price_with_taxes añade el IVA.
|
Nuestra función _compute_price_with_taxes añade el IVA.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
print("✓ Todos los tests completados exitosamente\n")
|
logger.info("✓ Todos los tests completados exitosamente\n")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n✗ ERROR: {e}\n")
|
logger.exception("\n✗ ERROR: %s\n", e)
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import _
|
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.http import route
|
from odoo.http import route
|
||||||
|
|
||||||
|
|
@ -37,13 +36,13 @@ class CustomerPortal(sale_portal.CustomerPortal):
|
||||||
|
|
||||||
# Add translated day names for pickup_day display
|
# Add translated day names for pickup_day display
|
||||||
values["day_names"] = [
|
values["day_names"] = [
|
||||||
_("Monday"),
|
request.env._("Monday"),
|
||||||
_("Tuesday"),
|
request.env._("Tuesday"),
|
||||||
_("Wednesday"),
|
request.env._("Wednesday"),
|
||||||
_("Thursday"),
|
request.env._("Thursday"),
|
||||||
_("Friday"),
|
request.env._("Friday"),
|
||||||
_("Saturday"),
|
request.env._("Saturday"),
|
||||||
_("Sunday"),
|
request.env._("Sunday"),
|
||||||
]
|
]
|
||||||
|
|
||||||
request.session["my_orders_history"] = values["orders"].ids[:100]
|
request.session["my_orders_history"] = values["orders"].ids[:100]
|
||||||
|
|
@ -60,13 +59,13 @@ class CustomerPortal(sale_portal.CustomerPortal):
|
||||||
# If it's a template render (not a redirect), add day_names to the context
|
# If it's a template render (not a redirect), add day_names to the context
|
||||||
if hasattr(response, "qcontext"):
|
if hasattr(response, "qcontext"):
|
||||||
response.qcontext["day_names"] = [
|
response.qcontext["day_names"] = [
|
||||||
_("Monday"),
|
request.env._("Monday"),
|
||||||
_("Tuesday"),
|
request.env._("Tuesday"),
|
||||||
_("Wednesday"),
|
request.env._("Wednesday"),
|
||||||
_("Thursday"),
|
request.env._("Thursday"),
|
||||||
_("Friday"),
|
request.env._("Friday"),
|
||||||
_("Saturday"),
|
request.env._("Saturday"),
|
||||||
_("Sunday"),
|
request.env._("Sunday"),
|
||||||
]
|
]
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
4
website_sale_aplicoop/migrations/18.0.1.0.0/__init__.py
Normal file
4
website_sale_aplicoop/migrations/18.0.1.0.0/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
"""Make migrations folder a package so mypy maps module names correctly.
|
||||||
|
|
||||||
|
Empty on purpose.
|
||||||
|
"""
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
# Copyright 2025 Criptomart
|
# Copyright 2025 Criptomart
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from odoo import SUPERUSER_ID
|
from odoo import SUPERUSER_ID
|
||||||
from odoo import api
|
from odoo import api
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def migrate(cr, version):
|
def migrate(cr, version):
|
||||||
"""Migración para agregar soporte multicompañía.
|
"""Migración para agregar soporte multicompañía.
|
||||||
|
|
@ -27,5 +31,4 @@ def migrate(cr, version):
|
||||||
(default_company.id,),
|
(default_company.id,),
|
||||||
)
|
)
|
||||||
|
|
||||||
cr.commit()
|
_logger.info("Asignado company_id=%d a group.order", default_company.id)
|
||||||
print(f"✓ Asignado company_id={default_company.id} a group.order")
|
|
||||||
|
|
|
||||||
|
|
@ -243,13 +243,11 @@ class GroupOrder(models.Model):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
self.env._(
|
self.env._(
|
||||||
"Group %(group)s belongs to company %(group_company)s, "
|
"Group %(group)s belongs to company %(group_company)s, "
|
||||||
"not to %(record_company)s."
|
"not to %(record_company)s.",
|
||||||
|
group=group.name,
|
||||||
|
group_company=group.company_id.name,
|
||||||
|
record_company=record.company_id.name,
|
||||||
)
|
)
|
||||||
% {
|
|
||||||
"group": group.name,
|
|
||||||
"group_company": group.company_id.name,
|
|
||||||
"record_company": record.company_id.name,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.constrains("start_date", "end_date")
|
@api.constrains("start_date", "end_date")
|
||||||
|
|
@ -545,9 +543,10 @@ class GroupOrder(models.Model):
|
||||||
self.env._(
|
self.env._(
|
||||||
"For weekly orders, pickup day (%(pickup)s) must be after or equal to "
|
"For weekly orders, pickup day (%(pickup)s) must be after or equal to "
|
||||||
"cutoff day (%(cutoff)s) in the same week. Current configuration would "
|
"cutoff day (%(cutoff)s) in the same week. Current configuration would "
|
||||||
"put pickup before cutoff, which is illogical."
|
"put pickup before cutoff, which is illogical.",
|
||||||
|
pickup=pickup_name,
|
||||||
|
cutoff=cutoff_name,
|
||||||
)
|
)
|
||||||
% {"pickup": pickup_name, "cutoff": cutoff_name}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# === Onchange Methods ===
|
# === Onchange Methods ===
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
# Copyright 2025 Criptomart
|
# Copyright 2025 Criptomart
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||||
|
|
||||||
from odoo import _
|
|
||||||
from odoo import api
|
from odoo import api
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
# Note: translation function _ is not used in this module (removed to satisfy flake8)
|
||||||
|
|
||||||
|
|
||||||
class ProductProduct(models.Model):
|
class ProductProduct(models.Model):
|
||||||
_inherit = "product.product"
|
_inherit = "product.product"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
# Copyright 2025-Today Criptomart
|
# Copyright 2025-Today Criptomart
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||||
|
|
||||||
from odoo import _
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
# Note: translation function _ is not used in this module (removed to satisfy flake8)
|
||||||
|
|
||||||
|
|
||||||
class ResPartner(models.Model):
|
class ResPartner(models.Model):
|
||||||
_inherit = "res.partner"
|
_inherit = "res.partner"
|
||||||
|
|
|
||||||
|
|
@ -87,4 +87,3 @@
|
||||||
.info-date {
|
.info-date {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@ class TestLoadDraftOrder(TransactionCase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
other_user = self.env["res.users"].create(
|
self.env["res.users"].create(
|
||||||
{
|
{
|
||||||
"name": "Other User",
|
"name": "Other User",
|
||||||
"login": "other@test.com",
|
"login": "other@test.com",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from datetime import timedelta
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError # noqa: F401
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -430,7 +430,7 @@ class TestOrderWithoutEndDate(TransactionCase):
|
||||||
"""Test order with end_date = NULL (ongoing order)."""
|
"""Test order with end_date = NULL (ongoing order)."""
|
||||||
start = date.today()
|
start = date.today()
|
||||||
|
|
||||||
order = self.env["group.order"].create(
|
self.env["group.order"].create(
|
||||||
{
|
{
|
||||||
"name": "Permanent Order",
|
"name": "Permanent Order",
|
||||||
"group_ids": [(6, 0, [self.group.id])],
|
"group_ids": [(6, 0, [self.group.id])],
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ Coverage:
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from odoo.exceptions import AccessError
|
from odoo.exceptions import AccessError # noqa: F401
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError # noqa: F401
|
||||||
from odoo.tests.common import HttpCase
|
from odoo.tests.common import HttpCase # noqa: F401
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -467,7 +467,7 @@ class TestConfirmOrderEndpoint(TransactionCase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
other_order = self.env["group.order"].create(
|
self.env["group.order"].create(
|
||||||
{
|
{
|
||||||
"name": "Other Order",
|
"name": "Other Order",
|
||||||
"group_ids": [(6, 0, [other_group.id])],
|
"group_ids": [(6, 0, [other_group.id])],
|
||||||
|
|
@ -601,7 +601,7 @@ class TestLoadDraftEndpoint(TransactionCase):
|
||||||
expired_order.action_open()
|
expired_order.action_open()
|
||||||
expired_order.action_close()
|
expired_order.action_close()
|
||||||
|
|
||||||
old_sale = self.env["sale.order"].create(
|
self.env["sale.order"].create(
|
||||||
{
|
{
|
||||||
"partner_id": self.member_partner.id,
|
"partner_id": self.member_partner.id,
|
||||||
"group_order_id": expired_order.id,
|
"group_order_id": expired_order.id,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from psycopg2 import IntegrityError
|
from psycopg2 import IntegrityError # noqa: F401
|
||||||
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ class TestMultiCompanyGroupOrder(TransactionCase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
order2 = self.env["group.order"].create(
|
self.env["group.order"].create(
|
||||||
{
|
{
|
||||||
"name": "Pedido Company 2",
|
"name": "Pedido Company 2",
|
||||||
"group_ids": [(6, 0, [self.group2.id])],
|
"group_ids": [(6, 0, [self.group2.id])],
|
||||||
|
|
|
||||||
83
website_sale_aplicoop/tests/test_portal_access.py
Normal file
83
website_sale_aplicoop/tests/test_portal_access.py
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Copyright 2026
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import HttpCase
|
||||||
|
|
||||||
|
|
||||||
|
@tagged("post_install", "-at_install")
|
||||||
|
class TestPortalAccess(HttpCase):
|
||||||
|
"""Verifica que un usuario portal pueda acceder a la página de un pedido (eskaera)."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
# Create a consumer group and a member partner
|
||||||
|
self.group = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Portal Test Group",
|
||||||
|
"is_company": True,
|
||||||
|
"email": "portal-group@test.com",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.member_partner = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Portal Member",
|
||||||
|
"email": "portal-member@test.com",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add member to the group
|
||||||
|
self.group.member_ids = [(4, self.member_partner.id)]
|
||||||
|
|
||||||
|
# Create a portal user (password = login for HttpCase.authenticate convenience)
|
||||||
|
login = "portal.user@test.com"
|
||||||
|
self.portal_user = self.env["res.users"].create(
|
||||||
|
{
|
||||||
|
"name": "Portal User",
|
||||||
|
"login": login,
|
||||||
|
"password": login,
|
||||||
|
"partner_id": self.member_partner.id,
|
||||||
|
# Add portal group
|
||||||
|
"groups_id": [(4, self.env.ref("base.group_portal").id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create and open a group.order belonging to the same company
|
||||||
|
start_date = datetime.now().date()
|
||||||
|
self.group_order = self.env["group.order"].create(
|
||||||
|
{
|
||||||
|
"name": "Portal Access Order",
|
||||||
|
"group_ids": [(6, 0, [self.group.id])],
|
||||||
|
"type": "regular",
|
||||||
|
"start_date": start_date,
|
||||||
|
"end_date": start_date + timedelta(days=7),
|
||||||
|
"period": "weekly",
|
||||||
|
"pickup_day": "3",
|
||||||
|
"cutoff_day": "0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.group_order.action_open()
|
||||||
|
|
||||||
|
def test_portal_user_can_view_eskaera_page(self):
|
||||||
|
"""El endpoint /eskaera/<id> debe ser accesible por un usuario portal que pertenezca a la compañía."""
|
||||||
|
# Authenticate as portal user
|
||||||
|
self.authenticate(self.portal_user.login, self.portal_user.login)
|
||||||
|
|
||||||
|
# Request the eskaera page
|
||||||
|
response = self.url_open(
|
||||||
|
f"/eskaera/{self.group_order.id}", allow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should return 200 OK and not redirect to login
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Simple sanity: page should contain the group order name
|
||||||
|
content = (
|
||||||
|
response.get_data(as_text=True)
|
||||||
|
if hasattr(response, "get_data")
|
||||||
|
else getattr(response, "text", "")
|
||||||
|
)
|
||||||
|
self.assertIn(self.group_order.name, content)
|
||||||
85
website_sale_aplicoop/tests/test_portal_get_routes.py
Normal file
85
website_sale_aplicoop/tests/test_portal_get_routes.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# 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}")
|
||||||
101
website_sale_aplicoop/tests/test_portal_product_uom_access.py
Normal file
101
website_sale_aplicoop/tests/test_portal_product_uom_access.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# 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)
|
||||||
|
|
@ -490,6 +490,6 @@ class TestPricingWithPricelist(TransactionCase):
|
||||||
)
|
)
|
||||||
# If it doesn't raise, check the result is valid
|
# If it doesn't raise, check the result is valid
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# If it raises, that's also acceptable behavior
|
# If it raises, that's also acceptable behavior
|
||||||
self.assertTrue(True, "Negative quantity properly rejected")
|
self.assertTrue(True, "Negative quantity properly rejected")
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,8 @@ class TestProductDiscoveryUnion(TransactionCase):
|
||||||
"""Test discovery includes products from linked categories."""
|
"""Test discovery includes products from linked categories."""
|
||||||
self.group_order.category_ids = [(4, self.category1.id)]
|
self.group_order.category_ids = [(4, self.category1.id)]
|
||||||
|
|
||||||
discovered = self.group_order.product_ids # Computed
|
# 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)
|
# Should include cat1_product and supplier_product (both in category1)
|
||||||
# Note: depends on how discovery is computed
|
# Note: depends on how discovery is computed
|
||||||
|
|
||||||
|
|
@ -346,9 +347,13 @@ class TestDeepCategoryHierarchies(TransactionCase):
|
||||||
# Attempt to create circular ref may fail
|
# Attempt to create circular ref may fail
|
||||||
try:
|
try:
|
||||||
self.cat_l1.parent_id = self.cat_l5.id # Creates loop
|
self.cat_l1.parent_id = self.cat_l5.id # Creates loop
|
||||||
except:
|
except Exception as exc:
|
||||||
# Expected: Odoo should prevent circular refs
|
# Expected: Odoo should prevent circular refs. Log for visibility.
|
||||||
pass
|
import logging
|
||||||
|
|
||||||
|
logging.getLogger(__name__).info(
|
||||||
|
"Expected exception creating circular category: %s", str(exc)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestEmptySourcesDiscovery(TransactionCase):
|
class TestEmptySourcesDiscovery(TransactionCase):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from odoo import _
|
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
from odoo.tests.common import tagged
|
from odoo.tests.common import tagged
|
||||||
|
|
||||||
|
|
@ -82,9 +81,7 @@ class TestTemplatesRendering(TransactionCase):
|
||||||
def test_day_names_context_is_provided(self):
|
def test_day_names_context_is_provided(self):
|
||||||
"""Test that day_names context is provided by the controller method."""
|
"""Test that day_names context is provided by the controller method."""
|
||||||
# Simulate what the controller does, passing env for test context
|
# Simulate what the controller does, passing env for test context
|
||||||
from odoo.addons.website_sale_aplicoop.controllers.website_sale import (
|
from ..controllers.website_sale import AplicoopWebsiteSale
|
||||||
AplicoopWebsiteSale,
|
|
||||||
)
|
|
||||||
|
|
||||||
controller = AplicoopWebsiteSale()
|
controller = AplicoopWebsiteSale()
|
||||||
day_names = controller._get_day_names(env=self.env)
|
day_names = controller._get_day_names(env=self.env)
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,7 @@ class TestUserPartnerValidation(TransactionCase):
|
||||||
def test_user_without_partner_cannot_access_order(self):
|
def test_user_without_partner_cannot_access_order(self):
|
||||||
"""Test that user without partner_id has no access to orders."""
|
"""Test that user without partner_id has no access to orders."""
|
||||||
start_date = datetime.now().date()
|
start_date = datetime.now().date()
|
||||||
order = self.env["group.order"].create(
|
self.env["group.order"].create(
|
||||||
{
|
{
|
||||||
"name": "Test Order",
|
"name": "Test Order",
|
||||||
"group_ids": [(6, 0, [self.group.id])],
|
"group_ids": [(6, 0, [self.group.id])],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue