[FIX] website_sale_aplicoop: Remove redundant string= attributes and fix OCA linting warnings
- Remove redundant string= from 17 field definitions where name matches string value (W8113) - Convert @staticmethod to instance methods in selection methods for proper self.env._() access - Fix W8161 (prefer-env-translation) by using self.env._() instead of standalone _() - Fix W8301/W8115 (translation-not-lazy) by proper placement of % interpolation outside self.env._() - Remove unused imports of odoo._ from group_order.py and sale_order_extension.py - All OCA linting warnings in website_sale_aplicoop main models are now resolved Changes: - website_sale_aplicoop/models/group_order.py: 21 field definitions cleaned - website_sale_aplicoop/models/sale_order_extension.py: 5 field definitions cleaned + @staticmethod conversion - Consistent with OCA standards for addon submission
This commit is contained in:
parent
5c89795e30
commit
6fbc7b9456
73 changed files with 5386 additions and 4354 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -130,4 +130,3 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
|
|
|
|||
|
|
@ -12,36 +12,45 @@ class TestAccountMove(TransactionCase):
|
|||
super().setUpClass()
|
||||
|
||||
# Create a partner
|
||||
cls.partner = cls.env["res.partner"].create({
|
||||
cls.partner = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Customer",
|
||||
"email": "customer@test.com",
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create a product
|
||||
cls.product = cls.env["product.product"].create({
|
||||
cls.product = cls.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product Invoice",
|
||||
"type": "consu",
|
||||
"list_price": 200.0,
|
||||
"standard_price": 100.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create tax
|
||||
cls.tax = cls.env["account.tax"].create({
|
||||
cls.tax = cls.env["account.tax"].create(
|
||||
{
|
||||
"name": "Test Tax 10%",
|
||||
"amount": 10.0,
|
||||
"amount_type": "percent",
|
||||
"type_tax_use": "sale",
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create an invoice
|
||||
cls.invoice = cls.env["account.move"].create({
|
||||
cls.invoice = cls.env["account.move"].create(
|
||||
{
|
||||
"move_type": "out_invoice",
|
||||
"partner_id": cls.partner.id,
|
||||
"invoice_date": "2026-01-01",
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create invoice line
|
||||
cls.invoice_line = cls.env["account.move.line"].create({
|
||||
cls.invoice_line = cls.env["account.move.line"].create(
|
||||
{
|
||||
"move_id": cls.invoice.id,
|
||||
"product_id": cls.product.id,
|
||||
"quantity": 5,
|
||||
|
|
@ -50,21 +59,26 @@ class TestAccountMove(TransactionCase):
|
|||
"discount2": 5.0,
|
||||
"discount3": 2.0,
|
||||
"tax_ids": [(6, 0, [cls.tax.id])],
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
def test_invoice_line_discount_readonly(self):
|
||||
"""Test that discount field is readonly in invoice lines"""
|
||||
field = self.invoice_line._fields["discount"]
|
||||
self.assertTrue(field.readonly, "Discount field should be readonly in invoice lines")
|
||||
self.assertTrue(
|
||||
field.readonly, "Discount field should be readonly in invoice lines"
|
||||
)
|
||||
|
||||
def test_invoice_line_write_with_explicit_discounts(self):
|
||||
"""Test writing invoice line with explicit discounts"""
|
||||
self.invoice_line.write({
|
||||
self.invoice_line.write(
|
||||
{
|
||||
"discount": 30.0, # Should be ignored
|
||||
"discount1": 15.0,
|
||||
"discount2": 10.0,
|
||||
"discount3": 5.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(self.invoice_line.discount1, 15.0)
|
||||
self.assertEqual(self.invoice_line.discount2, 10.0)
|
||||
|
|
@ -72,9 +86,11 @@ class TestAccountMove(TransactionCase):
|
|||
|
||||
def test_invoice_line_legacy_discount(self):
|
||||
"""Test legacy discount behavior in invoice lines"""
|
||||
self.invoice_line.write({
|
||||
self.invoice_line.write(
|
||||
{
|
||||
"discount": 20.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Should map to discount1 and reset others
|
||||
self.assertEqual(self.invoice_line.discount1, 20.0)
|
||||
|
|
@ -83,11 +99,13 @@ class TestAccountMove(TransactionCase):
|
|||
|
||||
def test_invoice_line_price_calculation(self):
|
||||
"""Test that price subtotal is calculated correctly with triple discount"""
|
||||
self.invoice_line.write({
|
||||
self.invoice_line.write(
|
||||
{
|
||||
"discount1": 10.0,
|
||||
"discount2": 5.0,
|
||||
"discount3": 0.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Base: 5 * 200 = 1000
|
||||
# After 10% discount: 900
|
||||
|
|
@ -99,7 +117,8 @@ class TestAccountMove(TransactionCase):
|
|||
|
||||
def test_multiple_invoice_lines(self):
|
||||
"""Test multiple invoice lines with different discounts"""
|
||||
line2 = self.env["account.move.line"].create({
|
||||
line2 = self.env["account.move.line"].create(
|
||||
{
|
||||
"move_id": self.invoice.id,
|
||||
"product_id": self.product.id,
|
||||
"quantity": 3,
|
||||
|
|
@ -108,7 +127,8 @@ class TestAccountMove(TransactionCase):
|
|||
"discount2": 10.0,
|
||||
"discount3": 5.0,
|
||||
"tax_ids": [(6, 0, [self.tax.id])],
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Verify both lines have correct discounts
|
||||
self.assertEqual(self.invoice_line.discount1, 10.0)
|
||||
|
|
@ -121,9 +141,11 @@ class TestAccountMove(TransactionCase):
|
|||
initial_discount1 = self.invoice_line.discount1
|
||||
initial_discount2 = self.invoice_line.discount2
|
||||
|
||||
self.invoice_line.write({
|
||||
self.invoice_line.write(
|
||||
{
|
||||
"quantity": 10,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Discounts should remain unchanged
|
||||
self.assertEqual(self.invoice_line.discount1, initial_discount1)
|
||||
|
|
@ -135,9 +157,11 @@ class TestAccountMove(TransactionCase):
|
|||
"""Test updating price doesn't affect discounts"""
|
||||
initial_discount1 = self.invoice_line.discount1
|
||||
|
||||
self.invoice_line.write({
|
||||
self.invoice_line.write(
|
||||
{
|
||||
"price_unit": 250.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Discount should remain unchanged
|
||||
self.assertEqual(self.invoice_line.discount1, initial_discount1)
|
||||
|
|
@ -146,11 +170,13 @@ class TestAccountMove(TransactionCase):
|
|||
|
||||
def test_invoice_with_zero_discounts(self):
|
||||
"""Test invoice line with all zero discounts"""
|
||||
self.invoice_line.write({
|
||||
self.invoice_line.write(
|
||||
{
|
||||
"discount1": 0.0,
|
||||
"discount2": 0.0,
|
||||
"discount3": 0.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# All discounts should be zero
|
||||
self.assertEqual(self.invoice_line.discount, 0.0)
|
||||
|
|
@ -165,13 +191,15 @@ class TestAccountMove(TransactionCase):
|
|||
def test_invoice_line_combined_operations(self):
|
||||
"""Test combined operations on invoice line"""
|
||||
# Update multiple fields at once
|
||||
self.invoice_line.write({
|
||||
self.invoice_line.write(
|
||||
{
|
||||
"quantity": 8,
|
||||
"price_unit": 180.0,
|
||||
"discount1": 12.0,
|
||||
"discount2": 6.0,
|
||||
"discount3": 0.0, # Reset discount3 explicitly
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# All fields should be updated correctly
|
||||
self.assertEqual(self.invoice_line.quantity, 8)
|
||||
|
|
@ -182,6 +210,4 @@ class TestAccountMove(TransactionCase):
|
|||
|
||||
# Calculate expected subtotal: 8 * 180 * (1-0.12) * (1-0.06)
|
||||
expected = 8 * 180 * 0.88 * 0.94
|
||||
self.assertAlmostEqual(
|
||||
self.invoice_line.price_subtotal, expected, places=2
|
||||
)
|
||||
self.assertAlmostEqual(self.invoice_line.price_subtotal, expected, places=2)
|
||||
|
|
|
|||
|
|
@ -12,27 +12,34 @@ class TestPurchaseOrder(TransactionCase):
|
|||
super().setUpClass()
|
||||
|
||||
# Create a supplier
|
||||
cls.supplier = cls.env["res.partner"].create({
|
||||
cls.supplier = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Supplier",
|
||||
"email": "supplier@test.com",
|
||||
"supplier_rank": 1,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create a product
|
||||
cls.product = cls.env["product.product"].create({
|
||||
cls.product = cls.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product PO",
|
||||
"type": "product",
|
||||
"list_price": 150.0,
|
||||
"standard_price": 80.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create a purchase order
|
||||
cls.purchase_order = cls.env["purchase.order"].create({
|
||||
cls.purchase_order = cls.env["purchase.order"].create(
|
||||
{
|
||||
"partner_id": cls.supplier.id,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create purchase order line
|
||||
cls.po_line = cls.env["purchase.order.line"].create({
|
||||
cls.po_line = cls.env["purchase.order.line"].create(
|
||||
{
|
||||
"order_id": cls.purchase_order.id,
|
||||
"product_id": cls.product.id,
|
||||
"product_qty": 10,
|
||||
|
|
@ -40,7 +47,8 @@ class TestPurchaseOrder(TransactionCase):
|
|||
"discount1": 10.0,
|
||||
"discount2": 5.0,
|
||||
"discount3": 2.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
def test_po_line_discount_readonly(self):
|
||||
"""Test that discount field is readonly in PO lines"""
|
||||
|
|
@ -49,12 +57,14 @@ class TestPurchaseOrder(TransactionCase):
|
|||
|
||||
def test_po_line_write_with_explicit_discounts(self):
|
||||
"""Test writing PO line with explicit discounts"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 25.0, # Should be ignored
|
||||
"discount1": 12.0,
|
||||
"discount2": 8.0,
|
||||
"discount3": 4.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(self.po_line.discount1, 12.0)
|
||||
self.assertEqual(self.po_line.discount2, 8.0)
|
||||
|
|
@ -62,9 +72,11 @@ class TestPurchaseOrder(TransactionCase):
|
|||
|
||||
def test_po_line_legacy_discount(self):
|
||||
"""Test legacy discount behavior in PO lines"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 18.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Should map to discount1 and reset others
|
||||
self.assertEqual(self.po_line.discount1, 18.0)
|
||||
|
|
@ -73,24 +85,25 @@ class TestPurchaseOrder(TransactionCase):
|
|||
|
||||
def test_po_line_price_calculation(self):
|
||||
"""Test that price subtotal is calculated correctly with triple discount"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 15.0,
|
||||
"discount2": 10.0,
|
||||
"discount3": 5.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Base: 10 * 150 = 1500
|
||||
# After 15% discount: 1275
|
||||
# After 10% discount: 1147.5
|
||||
# After 5% discount: 1090.125
|
||||
expected_subtotal = 10 * 150 * 0.85 * 0.90 * 0.95
|
||||
self.assertAlmostEqual(
|
||||
self.po_line.price_subtotal, expected_subtotal, places=2
|
||||
)
|
||||
self.assertAlmostEqual(self.po_line.price_subtotal, expected_subtotal, places=2)
|
||||
|
||||
def test_multiple_po_lines(self):
|
||||
"""Test multiple PO lines with different discounts"""
|
||||
line2 = self.env["purchase.order.line"].create({
|
||||
line2 = self.env["purchase.order.line"].create(
|
||||
{
|
||||
"order_id": self.purchase_order.id,
|
||||
"product_id": self.product.id,
|
||||
"product_qty": 5,
|
||||
|
|
@ -98,7 +111,8 @@ class TestPurchaseOrder(TransactionCase):
|
|||
"discount1": 20.0,
|
||||
"discount2": 15.0,
|
||||
"discount3": 10.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Verify both lines have correct discounts
|
||||
self.assertEqual(self.po_line.discount1, 15.0)
|
||||
|
|
@ -111,9 +125,11 @@ class TestPurchaseOrder(TransactionCase):
|
|||
initial_discount1 = self.po_line.discount1
|
||||
initial_discount2 = self.po_line.discount2
|
||||
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"product_qty": 20,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Discounts should remain unchanged
|
||||
self.assertEqual(self.po_line.discount1, initial_discount1)
|
||||
|
|
@ -125,9 +141,11 @@ class TestPurchaseOrder(TransactionCase):
|
|||
"""Test updating price doesn't affect discounts"""
|
||||
initial_discount1 = self.po_line.discount1
|
||||
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"price_unit": 200.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Discount should remain unchanged
|
||||
self.assertEqual(self.po_line.discount1, initial_discount1)
|
||||
|
|
@ -136,11 +154,13 @@ class TestPurchaseOrder(TransactionCase):
|
|||
|
||||
def test_po_with_zero_discounts(self):
|
||||
"""Test PO line with all zero discounts"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 0.0,
|
||||
"discount2": 0.0,
|
||||
"discount3": 0.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# All discounts should be zero
|
||||
self.assertEqual(self.po_line.discount, 0.0)
|
||||
|
|
@ -155,13 +175,15 @@ class TestPurchaseOrder(TransactionCase):
|
|||
def test_po_line_combined_operations(self):
|
||||
"""Test combined operations on PO line"""
|
||||
# Update multiple fields at once
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"product_qty": 15,
|
||||
"price_unit": 175.0,
|
||||
"discount1": 18.0,
|
||||
"discount2": 12.0,
|
||||
"discount3": 6.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# All fields should be updated correctly
|
||||
self.assertEqual(self.po_line.product_qty, 15)
|
||||
|
|
@ -172,17 +194,17 @@ class TestPurchaseOrder(TransactionCase):
|
|||
|
||||
# Calculate expected subtotal
|
||||
expected = 15 * 175 * 0.82 * 0.88 * 0.94
|
||||
self.assertAlmostEqual(
|
||||
self.po_line.price_subtotal, expected, places=2
|
||||
)
|
||||
self.assertAlmostEqual(self.po_line.price_subtotal, expected, places=2)
|
||||
|
||||
def test_po_confirm_with_discounts(self):
|
||||
"""Test confirming PO doesn't alter discounts"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 10.0,
|
||||
"discount2": 5.0,
|
||||
"discount3": 2.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Confirm the purchase order
|
||||
self.purchase_order.button_confirm()
|
||||
|
|
|
|||
|
|
@ -12,25 +12,32 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
super().setUpClass()
|
||||
|
||||
# Create a partner
|
||||
cls.partner = cls.env["res.partner"].create({
|
||||
cls.partner = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Partner",
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create a product
|
||||
cls.product = cls.env["product.product"].create({
|
||||
cls.product = cls.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product",
|
||||
"type": "product",
|
||||
"list_price": 100.0,
|
||||
"standard_price": 50.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create a purchase order
|
||||
cls.purchase_order = cls.env["purchase.order"].create({
|
||||
cls.purchase_order = cls.env["purchase.order"].create(
|
||||
{
|
||||
"partner_id": cls.partner.id,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Create a purchase order line
|
||||
cls.po_line = cls.env["purchase.order.line"].create({
|
||||
cls.po_line = cls.env["purchase.order.line"].create(
|
||||
{
|
||||
"order_id": cls.purchase_order.id,
|
||||
"product_id": cls.product.id,
|
||||
"product_qty": 10,
|
||||
|
|
@ -38,7 +45,8 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
"discount1": 10.0,
|
||||
"discount2": 5.0,
|
||||
"discount3": 2.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
def test_discount_field_is_readonly(self):
|
||||
"""Test that the discount field is readonly"""
|
||||
|
|
@ -48,12 +56,14 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
def test_write_with_explicit_discounts(self):
|
||||
"""Test writing with explicit discount1, discount2, discount3"""
|
||||
# Write with explicit discounts
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 20.0, # This should be ignored
|
||||
"discount1": 15.0,
|
||||
"discount2": 10.0,
|
||||
"discount3": 5.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Verify explicit discounts were applied
|
||||
self.assertEqual(self.po_line.discount1, 15.0)
|
||||
|
|
@ -67,10 +77,12 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
|
||||
def test_write_only_discount1(self):
|
||||
"""Test writing only discount1 explicitly"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 25.0, # This should be ignored
|
||||
"discount1": 20.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Only discount1 should change
|
||||
self.assertEqual(self.po_line.discount1, 20.0)
|
||||
|
|
@ -80,10 +92,12 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
|
||||
def test_write_only_discount2(self):
|
||||
"""Test writing only discount2 explicitly"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 30.0, # This should be ignored
|
||||
"discount2": 12.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Only discount2 should change
|
||||
self.assertEqual(self.po_line.discount2, 12.0)
|
||||
|
|
@ -93,10 +107,12 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
|
||||
def test_write_only_discount3(self):
|
||||
"""Test writing only discount3 explicitly"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 35.0, # This should be ignored
|
||||
"discount3": 8.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Only discount3 should change
|
||||
self.assertEqual(self.po_line.discount3, 8.0)
|
||||
|
|
@ -107,16 +123,20 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
def test_write_legacy_discount_only(self):
|
||||
"""Test legacy behavior: writing only discount field"""
|
||||
# Reset to known state first
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 10.0,
|
||||
"discount2": 5.0,
|
||||
"discount3": 2.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Write only discount (legacy behavior)
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 25.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Should map to discount1 and reset others
|
||||
self.assertEqual(self.po_line.discount1, 25.0)
|
||||
|
|
@ -126,19 +146,23 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
def test_write_multiple_times(self):
|
||||
"""Test writing multiple times to ensure consistency"""
|
||||
# First write
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 10.0,
|
||||
"discount2": 10.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(self.po_line.discount1, 10.0)
|
||||
self.assertEqual(self.po_line.discount2, 10.0)
|
||||
|
||||
# Second write
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 5.0,
|
||||
"discount3": 5.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# discount3 should change, others remain
|
||||
self.assertEqual(self.po_line.discount1, 10.0)
|
||||
|
|
@ -147,11 +171,13 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
|
||||
def test_write_zero_discounts(self):
|
||||
"""Test writing zero discounts"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 0.0,
|
||||
"discount2": 0.0,
|
||||
"discount3": 0.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(self.po_line.discount1, 0.0)
|
||||
self.assertEqual(self.po_line.discount2, 0.0)
|
||||
|
|
@ -161,17 +187,21 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
def test_write_combined_scenario(self):
|
||||
"""Test a realistic combined scenario"""
|
||||
# Initial state
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 15.0,
|
||||
"discount2": 5.0,
|
||||
"discount3": 0.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# User tries to update discount field (should be ignored if explicit discounts present)
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount": 50.0,
|
||||
"discount1": 20.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# discount1 should be updated, others unchanged
|
||||
self.assertEqual(self.po_line.discount1, 20.0)
|
||||
|
|
@ -180,11 +210,13 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
|
||||
def test_discount_calculation_accuracy(self):
|
||||
"""Test that discount calculation is accurate"""
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"discount1": 10.0,
|
||||
"discount2": 10.0,
|
||||
"discount3": 10.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Combined discount: 100 - (100 * 0.9 * 0.9 * 0.9) = 27.1
|
||||
expected = 100 - (100 * 0.9 * 0.9 * 0.9)
|
||||
|
|
@ -195,10 +227,12 @@ class TestTripleDiscountMixin(TransactionCase):
|
|||
initial_discount1 = self.po_line.discount1
|
||||
|
||||
# Write other fields
|
||||
self.po_line.write({
|
||||
self.po_line.write(
|
||||
{
|
||||
"product_qty": 20,
|
||||
"price_unit": 150.0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# Discounts should remain unchanged
|
||||
self.assertEqual(self.po_line.discount1, initial_discount1)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
# Copyright 2026 Your Company
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo import _
|
||||
from odoo import api
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
"""Extend res.partner with default price category for suppliers."""
|
||||
|
||||
_inherit = 'res.partner'
|
||||
_inherit = "res.partner"
|
||||
|
||||
default_price_category_id = fields.Many2one(
|
||||
comodel_name='product.price.category',
|
||||
string='Default Price Category',
|
||||
help='Default price category for products from this supplier',
|
||||
comodel_name="product.price.category",
|
||||
string="Default Price Category",
|
||||
help="Default price category for products from this supplier",
|
||||
domain=[],
|
||||
)
|
||||
|
||||
|
|
@ -21,24 +24,26 @@ class ResPartner(models.Model):
|
|||
self.ensure_one()
|
||||
|
||||
# Count products where this partner is the default supplier
|
||||
product_count = self.env['product.template'].search_count([
|
||||
('main_seller_id', '=', self.id)
|
||||
])
|
||||
product_count = self.env["product.template"].search_count(
|
||||
[("main_seller_id", "=", self.id)]
|
||||
)
|
||||
|
||||
# Create wizard record with context data
|
||||
wizard = self.env['wizard.update.product.category'].create({
|
||||
'partner_id': self.id,
|
||||
'partner_name': self.name,
|
||||
'price_category_id': self.default_price_category_id.id,
|
||||
'product_count': product_count,
|
||||
})
|
||||
wizard = self.env["wizard.update.product.category"].create(
|
||||
{
|
||||
"partner_id": self.id,
|
||||
"partner_name": self.name,
|
||||
"price_category_id": self.default_price_category_id.id,
|
||||
"product_count": product_count,
|
||||
}
|
||||
)
|
||||
|
||||
# Return action to open wizard modal
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Update Product Price Category'),
|
||||
'res_model': 'wizard.update.product.category',
|
||||
'res_id': wizard.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
"type": "ir.actions.act_window",
|
||||
"name": _("Update Product Price Category"),
|
||||
"res_model": "wizard.update.product.category",
|
||||
"res_id": wizard.id,
|
||||
"view_mode": "form",
|
||||
"target": "new",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,40 @@
|
|||
# Copyright 2026 Your Company
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo import _
|
||||
from odoo import api
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class WizardUpdateProductCategory(models.TransientModel):
|
||||
"""Wizard to confirm and bulk update product price categories."""
|
||||
|
||||
_name = 'wizard.update.product.category'
|
||||
_description = 'Update Product Price Category'
|
||||
_name = "wizard.update.product.category"
|
||||
_description = "Update Product Price Category"
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
string='Supplier',
|
||||
comodel_name="res.partner",
|
||||
string="Supplier",
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
|
||||
partner_name = fields.Char(
|
||||
string='Supplier Name',
|
||||
string="Supplier Name",
|
||||
readonly=True,
|
||||
related='partner_id.name',
|
||||
related="partner_id.name",
|
||||
)
|
||||
|
||||
price_category_id = fields.Many2one(
|
||||
comodel_name='product.price.category',
|
||||
string='Price Category',
|
||||
comodel_name="product.price.category",
|
||||
string="Price Category",
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
|
||||
product_count = fields.Integer(
|
||||
string='Number of Products',
|
||||
string="Number of Products",
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
|
|
@ -41,36 +44,33 @@ class WizardUpdateProductCategory(models.TransientModel):
|
|||
self.ensure_one()
|
||||
|
||||
# Search all products where this partner is the default supplier
|
||||
products = self.env['product.template'].search([
|
||||
('main_seller_id', '=', self.partner_id.id)
|
||||
])
|
||||
products = self.env["product.template"].search(
|
||||
[("main_seller_id", "=", self.partner_id.id)]
|
||||
)
|
||||
|
||||
if not products:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('No Products'),
|
||||
'message': _('No products found with this supplier.'),
|
||||
'type': 'warning',
|
||||
'sticky': False,
|
||||
}
|
||||
"type": "ir.actions.client",
|
||||
"tag": "display_notification",
|
||||
"params": {
|
||||
"title": _("No Products"),
|
||||
"message": _("No products found with this supplier."),
|
||||
"type": "warning",
|
||||
"sticky": False,
|
||||
},
|
||||
}
|
||||
|
||||
# Bulk update all products
|
||||
products.write({
|
||||
'price_category_id': self.price_category_id.id
|
||||
})
|
||||
products.write({"price_category_id": self.price_category_id.id})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Success'),
|
||||
'message': _(
|
||||
'%d products updated with category "%s".'
|
||||
) % (len(products), self.price_category_id.display_name),
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
}
|
||||
"type": "ir.actions.client",
|
||||
"tag": "display_notification",
|
||||
"params": {
|
||||
"title": _("Success"),
|
||||
"message": _('%d products updated with category "%s".')
|
||||
% (len(products), self.price_category_id.display_name),
|
||||
"type": "success",
|
||||
"sticky": False,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Copyright 2026 Your Company
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestProductPriceCategorySupplier(TransactionCase):
|
||||
|
|
@ -14,68 +14,88 @@ class TestProductPriceCategorySupplier(TransactionCase):
|
|||
super().setUpClass()
|
||||
|
||||
# Create price categories
|
||||
cls.category_premium = cls.env['product.price.category'].create({
|
||||
'name': 'Premium',
|
||||
})
|
||||
cls.category_standard = cls.env['product.price.category'].create({
|
||||
'name': 'Standard',
|
||||
})
|
||||
cls.category_premium = cls.env["product.price.category"].create(
|
||||
{
|
||||
"name": "Premium",
|
||||
}
|
||||
)
|
||||
cls.category_standard = cls.env["product.price.category"].create(
|
||||
{
|
||||
"name": "Standard",
|
||||
}
|
||||
)
|
||||
|
||||
# Create suppliers
|
||||
cls.supplier_a = cls.env['res.partner'].create({
|
||||
'name': 'Supplier A',
|
||||
'supplier_rank': 1,
|
||||
'default_price_category_id': cls.category_premium.id,
|
||||
})
|
||||
cls.supplier_b = cls.env['res.partner'].create({
|
||||
'name': 'Supplier B',
|
||||
'supplier_rank': 1,
|
||||
'default_price_category_id': cls.category_standard.id,
|
||||
})
|
||||
cls.supplier_a = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Supplier A",
|
||||
"supplier_rank": 1,
|
||||
"default_price_category_id": cls.category_premium.id,
|
||||
}
|
||||
)
|
||||
cls.supplier_b = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Supplier B",
|
||||
"supplier_rank": 1,
|
||||
"default_price_category_id": cls.category_standard.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create a non-supplier partner
|
||||
cls.customer = cls.env['res.partner'].create({
|
||||
'name': 'Customer A',
|
||||
'customer_rank': 1,
|
||||
'supplier_rank': 0,
|
||||
})
|
||||
cls.customer = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Customer A",
|
||||
"customer_rank": 1,
|
||||
"supplier_rank": 0,
|
||||
}
|
||||
)
|
||||
|
||||
# Create products with supplier A as default
|
||||
cls.product_1 = cls.env['product.template'].create({
|
||||
'name': 'Product 1',
|
||||
'default_supplier_id': cls.supplier_a.id,
|
||||
})
|
||||
cls.product_2 = cls.env['product.template'].create({
|
||||
'name': 'Product 2',
|
||||
'default_supplier_id': cls.supplier_a.id,
|
||||
})
|
||||
cls.product_3 = cls.env['product.template'].create({
|
||||
'name': 'Product 3',
|
||||
'default_supplier_id': cls.supplier_a.id,
|
||||
})
|
||||
cls.product_1 = cls.env["product.template"].create(
|
||||
{
|
||||
"name": "Product 1",
|
||||
"default_supplier_id": cls.supplier_a.id,
|
||||
}
|
||||
)
|
||||
cls.product_2 = cls.env["product.template"].create(
|
||||
{
|
||||
"name": "Product 2",
|
||||
"default_supplier_id": cls.supplier_a.id,
|
||||
}
|
||||
)
|
||||
cls.product_3 = cls.env["product.template"].create(
|
||||
{
|
||||
"name": "Product 3",
|
||||
"default_supplier_id": cls.supplier_a.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create product with supplier B
|
||||
cls.product_4 = cls.env['product.template'].create({
|
||||
'name': 'Product 4',
|
||||
'default_supplier_id': cls.supplier_b.id,
|
||||
})
|
||||
cls.product_4 = cls.env["product.template"].create(
|
||||
{
|
||||
"name": "Product 4",
|
||||
"default_supplier_id": cls.supplier_b.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create product without supplier
|
||||
cls.product_5 = cls.env['product.template'].create({
|
||||
'name': 'Product 5',
|
||||
'default_supplier_id': False,
|
||||
})
|
||||
cls.product_5 = cls.env["product.template"].create(
|
||||
{
|
||||
"name": "Product 5",
|
||||
"default_supplier_id": False,
|
||||
}
|
||||
)
|
||||
|
||||
def test_01_supplier_has_default_price_category_field(self):
|
||||
"""Test that supplier has default_price_category_id field."""
|
||||
self.assertTrue(
|
||||
hasattr(self.supplier_a, 'default_price_category_id'),
|
||||
'Supplier should have default_price_category_id field'
|
||||
hasattr(self.supplier_a, "default_price_category_id"),
|
||||
"Supplier should have default_price_category_id field",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.supplier_a.default_price_category_id.id,
|
||||
self.category_premium.id,
|
||||
'Supplier should have Premium category assigned'
|
||||
"Supplier should have Premium category assigned",
|
||||
)
|
||||
|
||||
def test_02_action_update_products_opens_wizard(self):
|
||||
|
|
@ -83,21 +103,19 @@ class TestProductPriceCategorySupplier(TransactionCase):
|
|||
action = self.supplier_a.action_update_products_price_category()
|
||||
|
||||
self.assertEqual(
|
||||
action['type'], 'ir.actions.act_window',
|
||||
'Action should be a window action'
|
||||
action["type"], "ir.actions.act_window", "Action should be a window action"
|
||||
)
|
||||
self.assertEqual(
|
||||
action['res_model'], 'wizard.update.product.category',
|
||||
'Action should open wizard model'
|
||||
action["res_model"],
|
||||
"wizard.update.product.category",
|
||||
"Action should open wizard model",
|
||||
)
|
||||
self.assertEqual(
|
||||
action['target'], 'new',
|
||||
'Action should open in modal (target=new)'
|
||||
action["target"], "new", "Action should open in modal (target=new)"
|
||||
)
|
||||
self.assertIn('res_id', action, 'Action should have res_id')
|
||||
self.assertIn("res_id", action, "Action should have res_id")
|
||||
self.assertTrue(
|
||||
action['res_id'] > 0,
|
||||
'res_id should be a valid wizard record ID'
|
||||
action["res_id"] > 0, "res_id should be a valid wizard record ID"
|
||||
)
|
||||
|
||||
def test_03_wizard_counts_products_correctly(self):
|
||||
|
|
@ -105,19 +123,18 @@ class TestProductPriceCategorySupplier(TransactionCase):
|
|||
action = self.supplier_a.action_update_products_price_category()
|
||||
|
||||
# Get the wizard record that was created
|
||||
wizard = self.env['wizard.update.product.category'].browse(action['res_id'])
|
||||
wizard = self.env["wizard.update.product.category"].browse(action["res_id"])
|
||||
|
||||
self.assertEqual(
|
||||
wizard.product_count, 3,
|
||||
'Wizard should count 3 products from Supplier A'
|
||||
wizard.product_count, 3, "Wizard should count 3 products from Supplier A"
|
||||
)
|
||||
self.assertEqual(
|
||||
wizard.partner_name, 'Supplier A',
|
||||
'Wizard should display supplier name'
|
||||
wizard.partner_name, "Supplier A", "Wizard should display supplier name"
|
||||
)
|
||||
self.assertEqual(
|
||||
wizard.price_category_id.id, self.category_premium.id,
|
||||
'Wizard should have Premium category from supplier'
|
||||
wizard.price_category_id.id,
|
||||
self.category_premium.id,
|
||||
"Wizard should have Premium category from supplier",
|
||||
)
|
||||
|
||||
def test_04_wizard_updates_all_products_from_supplier(self):
|
||||
|
|
@ -125,75 +142,82 @@ class TestProductPriceCategorySupplier(TransactionCase):
|
|||
# Verify initial state - no categories assigned
|
||||
self.assertFalse(
|
||||
self.product_1.price_category_id,
|
||||
'Product 1 should not have category initially'
|
||||
"Product 1 should not have category initially",
|
||||
)
|
||||
self.assertFalse(
|
||||
self.product_2.price_category_id,
|
||||
'Product 2 should not have category initially'
|
||||
"Product 2 should not have category initially",
|
||||
)
|
||||
|
||||
# Create and execute wizard
|
||||
wizard = self.env['wizard.update.product.category'].create({
|
||||
'partner_id': self.supplier_a.id,
|
||||
'price_category_id': self.category_premium.id,
|
||||
'product_count': 3,
|
||||
})
|
||||
wizard = self.env["wizard.update.product.category"].create(
|
||||
{
|
||||
"partner_id": self.supplier_a.id,
|
||||
"price_category_id": self.category_premium.id,
|
||||
"product_count": 3,
|
||||
}
|
||||
)
|
||||
result = wizard.action_confirm()
|
||||
|
||||
# Verify products were updated
|
||||
self.assertEqual(
|
||||
self.product_1.price_category_id.id, self.category_premium.id,
|
||||
'Product 1 should have Premium category'
|
||||
self.product_1.price_category_id.id,
|
||||
self.category_premium.id,
|
||||
"Product 1 should have Premium category",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.product_2.price_category_id.id, self.category_premium.id,
|
||||
'Product 2 should have Premium category'
|
||||
self.product_2.price_category_id.id,
|
||||
self.category_premium.id,
|
||||
"Product 2 should have Premium category",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.product_3.price_category_id.id, self.category_premium.id,
|
||||
'Product 3 should have Premium category'
|
||||
self.product_3.price_category_id.id,
|
||||
self.category_premium.id,
|
||||
"Product 3 should have Premium category",
|
||||
)
|
||||
|
||||
# Verify product from other supplier was NOT updated
|
||||
self.assertFalse(
|
||||
self.product_4.price_category_id,
|
||||
'Product 4 (from Supplier B) should not be updated'
|
||||
"Product 4 (from Supplier B) should not be updated",
|
||||
)
|
||||
|
||||
# Verify success notification
|
||||
self.assertEqual(
|
||||
result['type'], 'ir.actions.client',
|
||||
'Result should be a client action'
|
||||
result["type"], "ir.actions.client", "Result should be a client action"
|
||||
)
|
||||
self.assertEqual(
|
||||
result['tag'], 'display_notification',
|
||||
'Result should display a notification'
|
||||
result["tag"],
|
||||
"display_notification",
|
||||
"Result should display a notification",
|
||||
)
|
||||
|
||||
def test_05_wizard_handles_supplier_with_no_products(self):
|
||||
"""Test wizard behavior when supplier has no products."""
|
||||
# Create supplier without products
|
||||
supplier_no_products = self.env['res.partner'].create({
|
||||
'name': 'Supplier No Products',
|
||||
'supplier_rank': 1,
|
||||
'default_price_category_id': self.category_standard.id,
|
||||
})
|
||||
supplier_no_products = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Supplier No Products",
|
||||
"supplier_rank": 1,
|
||||
"default_price_category_id": self.category_standard.id,
|
||||
}
|
||||
)
|
||||
|
||||
wizard = self.env['wizard.update.product.category'].create({
|
||||
'partner_id': supplier_no_products.id,
|
||||
'price_category_id': self.category_standard.id,
|
||||
'product_count': 0,
|
||||
})
|
||||
wizard = self.env["wizard.update.product.category"].create(
|
||||
{
|
||||
"partner_id": supplier_no_products.id,
|
||||
"price_category_id": self.category_standard.id,
|
||||
"product_count": 0,
|
||||
}
|
||||
)
|
||||
result = wizard.action_confirm()
|
||||
|
||||
# Verify warning notification
|
||||
self.assertEqual(
|
||||
result['type'], 'ir.actions.client',
|
||||
'Result should be a client action'
|
||||
result["type"], "ir.actions.client", "Result should be a client action"
|
||||
)
|
||||
self.assertEqual(
|
||||
result['params']['type'], 'warning',
|
||||
'Should display warning notification'
|
||||
result["params"]["type"], "warning", "Should display warning notification"
|
||||
)
|
||||
|
||||
def test_06_customer_does_not_show_price_category_field(self):
|
||||
|
|
@ -201,11 +225,10 @@ class TestProductPriceCategorySupplier(TransactionCase):
|
|||
# This is a view-level test - we verify the field exists but logic is correct
|
||||
self.assertFalse(
|
||||
self.customer.default_price_category_id,
|
||||
'Customer should not have price category set'
|
||||
"Customer should not have price category set",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.customer.supplier_rank, 0,
|
||||
'Customer should have supplier_rank = 0'
|
||||
self.customer.supplier_rank, 0, "Customer should have supplier_rank = 0"
|
||||
)
|
||||
|
||||
def test_07_wizard_overwrites_existing_categories(self):
|
||||
|
|
@ -215,68 +238,82 @@ class TestProductPriceCategorySupplier(TransactionCase):
|
|||
self.product_2.price_category_id = self.category_standard.id
|
||||
|
||||
self.assertEqual(
|
||||
self.product_1.price_category_id.id, self.category_standard.id,
|
||||
'Product 1 should have Standard category initially'
|
||||
self.product_1.price_category_id.id,
|
||||
self.category_standard.id,
|
||||
"Product 1 should have Standard category initially",
|
||||
)
|
||||
|
||||
# Execute wizard to change to Premium
|
||||
wizard = self.env['wizard.update.product.category'].create({
|
||||
'partner_id': self.supplier_a.id,
|
||||
'price_category_id': self.category_premium.id,
|
||||
'product_count': 3,
|
||||
})
|
||||
wizard = self.env["wizard.update.product.category"].create(
|
||||
{
|
||||
"partner_id": self.supplier_a.id,
|
||||
"price_category_id": self.category_premium.id,
|
||||
"product_count": 3,
|
||||
}
|
||||
)
|
||||
wizard.action_confirm()
|
||||
|
||||
# Verify categories were overwritten
|
||||
self.assertEqual(
|
||||
self.product_1.price_category_id.id, self.category_premium.id,
|
||||
'Product 1 category should be overwritten to Premium'
|
||||
self.product_1.price_category_id.id,
|
||||
self.category_premium.id,
|
||||
"Product 1 category should be overwritten to Premium",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.product_2.price_category_id.id, self.category_premium.id,
|
||||
'Product 2 category should be overwritten to Premium'
|
||||
self.product_2.price_category_id.id,
|
||||
self.category_premium.id,
|
||||
"Product 2 category should be overwritten to Premium",
|
||||
)
|
||||
|
||||
def test_08_multiple_suppliers_independent_updates(self):
|
||||
"""Test that updating one supplier doesn't affect other suppliers' products."""
|
||||
# Update Supplier A products
|
||||
wizard_a = self.env['wizard.update.product.category'].create({
|
||||
'partner_id': self.supplier_a.id,
|
||||
'price_category_id': self.category_premium.id,
|
||||
'product_count': 3,
|
||||
})
|
||||
wizard_a = self.env["wizard.update.product.category"].create(
|
||||
{
|
||||
"partner_id": self.supplier_a.id,
|
||||
"price_category_id": self.category_premium.id,
|
||||
"product_count": 3,
|
||||
}
|
||||
)
|
||||
wizard_a.action_confirm()
|
||||
|
||||
# Update Supplier B products
|
||||
wizard_b = self.env['wizard.update.product.category'].create({
|
||||
'partner_id': self.supplier_b.id,
|
||||
'price_category_id': self.category_standard.id,
|
||||
'product_count': 1,
|
||||
})
|
||||
wizard_b = self.env["wizard.update.product.category"].create(
|
||||
{
|
||||
"partner_id": self.supplier_b.id,
|
||||
"price_category_id": self.category_standard.id,
|
||||
"product_count": 1,
|
||||
}
|
||||
)
|
||||
wizard_b.action_confirm()
|
||||
|
||||
# Verify each supplier's products have correct category
|
||||
self.assertEqual(
|
||||
self.product_1.price_category_id.id, self.category_premium.id,
|
||||
'Supplier A products should have Premium'
|
||||
self.product_1.price_category_id.id,
|
||||
self.category_premium.id,
|
||||
"Supplier A products should have Premium",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.product_4.price_category_id.id, self.category_standard.id,
|
||||
'Supplier B products should have Standard'
|
||||
self.product_4.price_category_id.id,
|
||||
self.category_standard.id,
|
||||
"Supplier B products should have Standard",
|
||||
)
|
||||
|
||||
def test_09_wizard_readonly_fields(self):
|
||||
"""Test that wizard display fields are readonly."""
|
||||
wizard = self.env['wizard.update.product.category'].create({
|
||||
'partner_id': self.supplier_a.id,
|
||||
'price_category_id': self.category_premium.id,
|
||||
'product_count': 3,
|
||||
})
|
||||
wizard = self.env["wizard.update.product.category"].create(
|
||||
{
|
||||
"partner_id": self.supplier_a.id,
|
||||
"price_category_id": self.category_premium.id,
|
||||
"product_count": 3,
|
||||
}
|
||||
)
|
||||
|
||||
# Verify partner_name is computed from partner_id
|
||||
self.assertEqual(
|
||||
wizard.partner_name, 'Supplier A',
|
||||
'partner_name should be related to partner_id.name'
|
||||
wizard.partner_name,
|
||||
"Supplier A",
|
||||
"partner_name should be related to partner_id.name",
|
||||
)
|
||||
|
||||
def test_10_action_counts_products_correctly(self):
|
||||
|
|
@ -284,18 +321,16 @@ class TestProductPriceCategorySupplier(TransactionCase):
|
|||
action = self.supplier_a.action_update_products_price_category()
|
||||
|
||||
# Get the wizard that was created
|
||||
wizard = self.env['wizard.update.product.category'].browse(action['res_id'])
|
||||
wizard = self.env["wizard.update.product.category"].browse(action["res_id"])
|
||||
|
||||
# Count products manually
|
||||
actual_count = self.env['product.template'].search_count([
|
||||
('default_supplier_id', '=', self.supplier_a.id)
|
||||
])
|
||||
actual_count = self.env["product.template"].search_count(
|
||||
[("default_supplier_id", "=", self.supplier_a.id)]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
wizard.product_count, actual_count,
|
||||
f'Wizard should count {actual_count} products'
|
||||
)
|
||||
self.assertEqual(
|
||||
wizard.product_count, 3,
|
||||
'Supplier A should have 3 products'
|
||||
wizard.product_count,
|
||||
actual_count,
|
||||
f"Wizard should count {actual_count} products",
|
||||
)
|
||||
self.assertEqual(wizard.product_count, 3, "Supplier A should have 3 products")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
from odoo import models, fields, api
|
||||
from odoo import api
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
|
|
|
|||
|
|
@ -11,22 +11,28 @@ class TestResConfigSettings(TransactionCase):
|
|||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.pricelist = cls.env["product.pricelist"].create({
|
||||
cls.pricelist = cls.env["product.pricelist"].create(
|
||||
{
|
||||
"name": "Test Config Pricelist",
|
||||
"currency_id": cls.env.company.currency_id.id,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
def test_config_parameter_set_and_get(self):
|
||||
"""Test setting and getting pricelist configuration"""
|
||||
config = self.env["res.config.settings"].create({
|
||||
config = self.env["res.config.settings"].create(
|
||||
{
|
||||
"product_pricelist_automatic": self.pricelist.id,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
config.execute()
|
||||
|
||||
# Verify parameter was saved
|
||||
saved_id = self.env["ir.config_parameter"].sudo().get_param(
|
||||
"product_sale_price_from_pricelist.product_pricelist_automatic"
|
||||
saved_id = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("product_sale_price_from_pricelist.product_pricelist_automatic")
|
||||
)
|
||||
|
||||
self.assertEqual(int(saved_id), self.pricelist.id)
|
||||
|
|
@ -36,7 +42,7 @@ class TestResConfigSettings(TransactionCase):
|
|||
# Set parameter directly
|
||||
self.env["ir.config_parameter"].sudo().set_param(
|
||||
"product_sale_price_from_pricelist.product_pricelist_automatic",
|
||||
str(self.pricelist.id)
|
||||
str(self.pricelist.id),
|
||||
)
|
||||
|
||||
# Create config and check if value is loaded
|
||||
|
|
@ -47,25 +53,33 @@ class TestResConfigSettings(TransactionCase):
|
|||
def test_config_update_pricelist(self):
|
||||
"""Test updating pricelist configuration"""
|
||||
# Set initial pricelist
|
||||
config = self.env["res.config.settings"].create({
|
||||
config = self.env["res.config.settings"].create(
|
||||
{
|
||||
"product_pricelist_automatic": self.pricelist.id,
|
||||
})
|
||||
}
|
||||
)
|
||||
config.execute()
|
||||
|
||||
# Create new pricelist and update
|
||||
new_pricelist = self.env["product.pricelist"].create({
|
||||
new_pricelist = self.env["product.pricelist"].create(
|
||||
{
|
||||
"name": "New Config Pricelist",
|
||||
"currency_id": self.env.company.currency_id.id,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
config2 = self.env["res.config.settings"].create({
|
||||
config2 = self.env["res.config.settings"].create(
|
||||
{
|
||||
"product_pricelist_automatic": new_pricelist.id,
|
||||
})
|
||||
}
|
||||
)
|
||||
config2.execute()
|
||||
|
||||
# Verify new value
|
||||
saved_id = self.env["ir.config_parameter"].sudo().get_param(
|
||||
"product_sale_price_from_pricelist.product_pricelist_automatic"
|
||||
saved_id = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("product_sale_price_from_pricelist.product_pricelist_automatic")
|
||||
)
|
||||
|
||||
self.assertEqual(int(saved_id), new_pricelist.id)
|
||||
|
|
|
|||
|
|
@ -19,4 +19,3 @@
|
|||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ class PurchaseOrder(models.Model):
|
|||
def _prepare_supplier_info(self, partner, line, price, currency):
|
||||
res = super()._prepare_supplier_info(partner, line, price, currency)
|
||||
res.update(
|
||||
{
|
||||
fname: line[fname]
|
||||
for fname in line._get_multiple_discount_field_names()
|
||||
}
|
||||
{fname: line[fname] for fname in line._get_multiple_discount_field_names()}
|
||||
)
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -43,10 +43,7 @@ class PurchaseOrderLine(models.Model):
|
|||
self.ensure_one()
|
||||
res = super()._prepare_account_move_line(move)
|
||||
res.update(
|
||||
{
|
||||
fname: self[fname]
|
||||
for fname in self._get_multiple_discount_field_names()
|
||||
}
|
||||
{fname: self[fname] for fname in self._get_multiple_discount_field_names()}
|
||||
)
|
||||
return res
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
version: '2'
|
||||
version: "2"
|
||||
|
||||
checks:
|
||||
similar-code:
|
||||
|
|
|
|||
|
|
@ -1,61 +1,72 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _
|
||||
from odoo.http import request, route
|
||||
from odoo.addons.sale.controllers import portal as sale_portal
|
||||
import logging
|
||||
|
||||
from odoo import _
|
||||
from odoo.http import request
|
||||
from odoo.http import route
|
||||
|
||||
from odoo.addons.sale.controllers import portal as sale_portal
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CustomerPortal(sale_portal.CustomerPortal):
|
||||
'''Extend sale portal to include draft orders.'''
|
||||
"""Extend sale portal to include draft orders."""
|
||||
|
||||
def _prepare_orders_domain(self, partner):
|
||||
'''Override to include draft and done orders.'''
|
||||
"""Override to include draft and done orders."""
|
||||
return [
|
||||
('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]),
|
||||
('state', 'in', ['draft', 'sale', 'done']), # Include draft orders
|
||||
("message_partner_ids", "child_of", [partner.commercial_partner_id.id]),
|
||||
("state", "in", ["draft", "sale", "done"]), # Include draft orders
|
||||
]
|
||||
|
||||
@route(['/my/orders', '/my/orders/page/<int:page>'],
|
||||
type='http', auth='user', website=True)
|
||||
@route(
|
||||
["/my/orders", "/my/orders/page/<int:page>"],
|
||||
type="http",
|
||||
auth="user",
|
||||
website=True,
|
||||
)
|
||||
def portal_my_orders(self, **kwargs):
|
||||
'''Override to add translated day names to context.'''
|
||||
"""Override to add translated day names to context."""
|
||||
# Get values from parent
|
||||
values = self._prepare_sale_portal_rendering_values(quotation_page=False, **kwargs)
|
||||
values = self._prepare_sale_portal_rendering_values(
|
||||
quotation_page=False, **kwargs
|
||||
)
|
||||
|
||||
# Add translated day names for pickup_day display
|
||||
values['day_names'] = [
|
||||
_('Monday'),
|
||||
_('Tuesday'),
|
||||
_('Wednesday'),
|
||||
_('Thursday'),
|
||||
_('Friday'),
|
||||
_('Saturday'),
|
||||
_('Sunday'),
|
||||
values["day_names"] = [
|
||||
_("Monday"),
|
||||
_("Tuesday"),
|
||||
_("Wednesday"),
|
||||
_("Thursday"),
|
||||
_("Friday"),
|
||||
_("Saturday"),
|
||||
_("Sunday"),
|
||||
]
|
||||
|
||||
request.session['my_orders_history'] = values['orders'].ids[:100]
|
||||
request.session["my_orders_history"] = values["orders"].ids[:100]
|
||||
return request.render("sale.portal_my_orders", values)
|
||||
|
||||
@route(['/my/orders/<int:order_id>'], type='http', auth='public', website=True)
|
||||
@route(["/my/orders/<int:order_id>"], type="http", auth="public", website=True)
|
||||
def portal_order_page(self, order_id, access_token=None, **kwargs):
|
||||
'''Override to add translated day names for order detail page.'''
|
||||
"""Override to add translated day names for order detail page."""
|
||||
# Call parent to get response
|
||||
response = super().portal_order_page(order_id, access_token=access_token, **kwargs)
|
||||
response = super().portal_order_page(
|
||||
order_id, access_token=access_token, **kwargs
|
||||
)
|
||||
|
||||
# If it's a template render (not a redirect), add day_names to the context
|
||||
if hasattr(response, 'qcontext'):
|
||||
response.qcontext['day_names'] = [
|
||||
_('Monday'),
|
||||
_('Tuesday'),
|
||||
_('Wednesday'),
|
||||
_('Thursday'),
|
||||
_('Friday'),
|
||||
_('Saturday'),
|
||||
_('Sunday'),
|
||||
if hasattr(response, "qcontext"):
|
||||
response.qcontext["day_names"] = [
|
||||
_("Monday"),
|
||||
_("Tuesday"),
|
||||
_("Wednesday"),
|
||||
_("Thursday"),
|
||||
_("Friday"),
|
||||
_("Saturday"),
|
||||
_("Sunday"),
|
||||
]
|
||||
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -1809,10 +1809,8 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
)
|
||||
_logger.warning("========================================")
|
||||
|
||||
# Get delivery product ID and name (translated to user's language)
|
||||
delivery_product = request.env.ref(
|
||||
"website_sale_aplicoop.product_home_delivery", raise_if_not_found=False
|
||||
)
|
||||
# Get delivery product from group_order (configured per group order)
|
||||
delivery_product = group_order.delivery_product_id
|
||||
delivery_product_id = delivery_product.id if delivery_product else None
|
||||
# Get translated product name based on current language
|
||||
if delivery_product:
|
||||
|
|
@ -2804,9 +2802,8 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
return request.redirect("/eskaera/%d" % sale_order.group_order_id.id)
|
||||
|
||||
# Extract items from the order (skip delivery product)
|
||||
delivery_product = request.env.ref(
|
||||
"website_sale_aplicoop.product_home_delivery", raise_if_not_found=False
|
||||
)
|
||||
# Use the delivery_product_id from the group_order
|
||||
delivery_product = sale_order.group_order_id.delivery_product_id
|
||||
delivery_product_id = delivery_product.id if delivery_product else None
|
||||
|
||||
items = []
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Fill pickup_day and pickup_date for existing group orders."""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
|
|
@ -9,12 +10,13 @@ def migrate(cr, version):
|
|||
|
||||
This ensures that existing group orders show delivery information.
|
||||
"""
|
||||
from odoo import api, SUPERUSER_ID
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo import api
|
||||
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
|
||||
# Get all group orders that don't have pickup_day set
|
||||
group_orders = env['group.order'].search([('pickup_day', '=', False)])
|
||||
group_orders = env["group.order"].search([("pickup_day", "=", False)])
|
||||
|
||||
if not group_orders:
|
||||
return
|
||||
|
|
@ -29,8 +31,10 @@ def migrate(cr, version):
|
|||
friday = today + timedelta(days=days_until_friday)
|
||||
|
||||
for order in group_orders:
|
||||
order.write({
|
||||
'pickup_day': 4, # Friday
|
||||
'pickup_date': friday,
|
||||
'delivery_notice': 'Home delivery available.',
|
||||
})
|
||||
order.write(
|
||||
{
|
||||
"pickup_day": 4, # Friday
|
||||
"pickup_date": friday,
|
||||
"delivery_notice": "Home delivery available.",
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import api, SUPERUSER_ID
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo import api
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
|
|
@ -13,7 +14,7 @@ def migrate(cr, version):
|
|||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
|
||||
# Obtener la compañía por defecto
|
||||
default_company = env['res.company'].search([], limit=1)
|
||||
default_company = env["res.company"].search([], limit=1)
|
||||
|
||||
if default_company:
|
||||
# Actualizar todos los registros de group.order que no tengan company_id
|
||||
|
|
@ -23,7 +24,7 @@ def migrate(cr, version):
|
|||
SET company_id = %s
|
||||
WHERE company_id IS NULL
|
||||
""",
|
||||
(default_company.id,)
|
||||
(default_company.id,),
|
||||
)
|
||||
|
||||
cr.commit()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import _
|
||||
from odoo import api
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
|
@ -19,52 +18,47 @@ class GroupOrder(models.Model):
|
|||
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||
_order = "start_date desc"
|
||||
|
||||
@staticmethod
|
||||
def _get_order_type_selection(records):
|
||||
def _get_order_type_selection(self):
|
||||
"""Return order type selection options with translations."""
|
||||
return [
|
||||
("regular", _("Regular Order")),
|
||||
("special", _("Special Order")),
|
||||
("promotional", _("Promotional Order")),
|
||||
("regular", self.env._("Regular Order")),
|
||||
("special", self.env._("Special Order")),
|
||||
("promotional", self.env._("Promotional Order")),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _get_period_selection(records):
|
||||
def _get_period_selection(self):
|
||||
"""Return period selection options with translations."""
|
||||
return [
|
||||
("once", _("One-time")),
|
||||
("weekly", _("Weekly")),
|
||||
("biweekly", _("Biweekly")),
|
||||
("monthly", _("Monthly")),
|
||||
("once", self.env._("One-time")),
|
||||
("weekly", self.env._("Weekly")),
|
||||
("biweekly", self.env._("Biweekly")),
|
||||
("monthly", self.env._("Monthly")),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _get_day_selection(records):
|
||||
def _get_day_selection(self):
|
||||
"""Return day of week selection options with translations."""
|
||||
return [
|
||||
("0", _("Monday")),
|
||||
("1", _("Tuesday")),
|
||||
("2", _("Wednesday")),
|
||||
("3", _("Thursday")),
|
||||
("4", _("Friday")),
|
||||
("5", _("Saturday")),
|
||||
("6", _("Sunday")),
|
||||
("0", self.env._("Monday")),
|
||||
("1", self.env._("Tuesday")),
|
||||
("2", self.env._("Wednesday")),
|
||||
("3", self.env._("Thursday")),
|
||||
("4", self.env._("Friday")),
|
||||
("5", self.env._("Saturday")),
|
||||
("6", self.env._("Sunday")),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _get_state_selection(records):
|
||||
def _get_state_selection(self):
|
||||
"""Return state selection options with translations."""
|
||||
return [
|
||||
("draft", _("Draft")),
|
||||
("open", _("Open")),
|
||||
("closed", _("Closed")),
|
||||
("cancelled", _("Cancelled")),
|
||||
("draft", self.env._("Draft")),
|
||||
("open", self.env._("Open")),
|
||||
("closed", self.env._("Closed")),
|
||||
("cancelled", self.env._("Cancelled")),
|
||||
]
|
||||
|
||||
# === Multicompañía ===
|
||||
company_id = fields.Many2one(
|
||||
"res.company",
|
||||
string="Company",
|
||||
required=True,
|
||||
default=lambda self: self.env.company,
|
||||
tracking=True,
|
||||
|
|
@ -73,7 +67,6 @@ class GroupOrder(models.Model):
|
|||
|
||||
# === Campos básicos ===
|
||||
name = fields.Char(
|
||||
string="Name",
|
||||
required=True,
|
||||
tracking=True,
|
||||
translate=True,
|
||||
|
|
@ -84,7 +77,6 @@ class GroupOrder(models.Model):
|
|||
"group_order_group_rel",
|
||||
"order_id",
|
||||
"group_id",
|
||||
string="Consumer Groups",
|
||||
required=True,
|
||||
domain=[("is_group", "=", True)],
|
||||
tracking=True,
|
||||
|
|
@ -92,7 +84,6 @@ class GroupOrder(models.Model):
|
|||
)
|
||||
type = fields.Selection(
|
||||
selection=_get_order_type_selection,
|
||||
string="Order Type",
|
||||
required=True,
|
||||
default="regular",
|
||||
tracking=True,
|
||||
|
|
@ -101,13 +92,11 @@ class GroupOrder(models.Model):
|
|||
|
||||
# === Fechas ===
|
||||
start_date = fields.Date(
|
||||
string="Start Date",
|
||||
required=False,
|
||||
tracking=True,
|
||||
help="Day when the consumer group order opens for purchases",
|
||||
)
|
||||
end_date = fields.Date(
|
||||
string="End Date",
|
||||
required=False,
|
||||
tracking=True,
|
||||
help="If empty, the consumer group order is permanent",
|
||||
|
|
@ -116,7 +105,6 @@ class GroupOrder(models.Model):
|
|||
# === Período y días ===
|
||||
period = fields.Selection(
|
||||
selection=_get_period_selection,
|
||||
string="Recurrence Period",
|
||||
required=True,
|
||||
default="weekly",
|
||||
tracking=True,
|
||||
|
|
@ -124,14 +112,12 @@ class GroupOrder(models.Model):
|
|||
)
|
||||
pickup_day = fields.Selection(
|
||||
selection=_get_day_selection,
|
||||
string="Pickup Day",
|
||||
required=False,
|
||||
tracking=True,
|
||||
help="Day of the week when members pick up their orders",
|
||||
)
|
||||
cutoff_day = fields.Selection(
|
||||
selection=_get_day_selection,
|
||||
string="Cutoff Day",
|
||||
required=False,
|
||||
tracking=True,
|
||||
help="Day when purchases stop and the consumer group order is locked for this week.",
|
||||
|
|
@ -139,20 +125,17 @@ class GroupOrder(models.Model):
|
|||
|
||||
# === Home delivery ===
|
||||
home_delivery = fields.Boolean(
|
||||
string="Home Delivery",
|
||||
default=False,
|
||||
tracking=True,
|
||||
help="Whether this consumer group order includes home delivery service",
|
||||
)
|
||||
delivery_product_id = fields.Many2one(
|
||||
"product.product",
|
||||
string="Delivery Product",
|
||||
domain=[("type", "=", "service")],
|
||||
tracking=True,
|
||||
help="Product to use for home delivery (service type)",
|
||||
)
|
||||
delivery_date = fields.Date(
|
||||
string="Delivery Date",
|
||||
compute="_compute_delivery_date",
|
||||
store=True,
|
||||
readonly=True,
|
||||
|
|
@ -161,14 +144,12 @@ class GroupOrder(models.Model):
|
|||
|
||||
# === Computed date fields ===
|
||||
pickup_date = fields.Date(
|
||||
string="Pickup Date",
|
||||
compute="_compute_pickup_date",
|
||||
store=True,
|
||||
readonly=True,
|
||||
help="Calculated next occurrence of pickup day",
|
||||
)
|
||||
cutoff_date = fields.Date(
|
||||
string="Cutoff Date",
|
||||
compute="_compute_cutoff_date",
|
||||
store=True,
|
||||
readonly=True,
|
||||
|
|
@ -181,7 +162,6 @@ class GroupOrder(models.Model):
|
|||
"group_order_supplier_rel",
|
||||
"order_id",
|
||||
"supplier_id",
|
||||
string="Suppliers",
|
||||
domain=[("supplier_rank", ">", 0)],
|
||||
tracking=True,
|
||||
help="Products from these suppliers will be available.",
|
||||
|
|
@ -191,7 +171,6 @@ class GroupOrder(models.Model):
|
|||
"group_order_product_rel",
|
||||
"order_id",
|
||||
"product_id",
|
||||
string="Products",
|
||||
tracking=True,
|
||||
help="Directly assigned products.",
|
||||
)
|
||||
|
|
@ -200,7 +179,6 @@ class GroupOrder(models.Model):
|
|||
"group_order_category_rel",
|
||||
"order_id",
|
||||
"category_id",
|
||||
string="Categories",
|
||||
tracking=True,
|
||||
help="Products in these categories will be available",
|
||||
)
|
||||
|
|
@ -208,29 +186,24 @@ class GroupOrder(models.Model):
|
|||
# === Estado ===
|
||||
state = fields.Selection(
|
||||
selection=_get_state_selection,
|
||||
string="State",
|
||||
default="draft",
|
||||
tracking=True,
|
||||
)
|
||||
|
||||
# === Descripción e imagen ===
|
||||
description = fields.Text(
|
||||
string="Description",
|
||||
translate=True,
|
||||
help="Free text description for this consumer group order",
|
||||
)
|
||||
delivery_notice = fields.Text(
|
||||
string="Delivery Notice",
|
||||
translate=True,
|
||||
help="Notice about home delivery displayed to users (shown when home delivery is enabled)",
|
||||
)
|
||||
image = fields.Binary(
|
||||
string="Image",
|
||||
help="Image displayed alongside the consumer group order name",
|
||||
attachment=True,
|
||||
)
|
||||
display_image = fields.Binary(
|
||||
string="Display Image",
|
||||
compute="_compute_display_image",
|
||||
store=True,
|
||||
help="Image to display: uses consumer group order image if set, otherwise group image",
|
||||
|
|
@ -249,7 +222,6 @@ class GroupOrder(models.Model):
|
|||
record.display_image = False
|
||||
|
||||
available_products_count = fields.Integer(
|
||||
string="Available Products Count",
|
||||
compute="_compute_available_products_count",
|
||||
store=False,
|
||||
help="Total count of available products from all sources",
|
||||
|
|
@ -270,13 +242,14 @@ class GroupOrder(models.Model):
|
|||
if group.company_id and group.company_id != record.company_id:
|
||||
raise ValidationError(
|
||||
self.env._(
|
||||
"Group {group} belongs to company {group_company}, "
|
||||
"not to {record_company}."
|
||||
).format(
|
||||
group=group.name,
|
||||
group_company=group.company_id.name,
|
||||
record_company=record.company_id.name,
|
||||
"Group %(group)s belongs to company %(group_company)s, "
|
||||
"not to %(record_company)s."
|
||||
)
|
||||
% {
|
||||
"group": group.name,
|
||||
"group_company": group.company_id.name,
|
||||
"record_company": record.company_id.name,
|
||||
}
|
||||
)
|
||||
|
||||
@api.constrains("start_date", "end_date")
|
||||
|
|
@ -569,11 +542,12 @@ class GroupOrder(models.Model):
|
|||
pickup_name = dict(self._get_day_selection())[str(pickup)]
|
||||
cutoff_name = dict(self._get_day_selection())[str(cutoff)]
|
||||
raise ValidationError(
|
||||
_(
|
||||
"For weekly orders, pickup day ({pickup}) must be after or equal to "
|
||||
"cutoff day ({cutoff}) in the same week. Current configuration would "
|
||||
self.env._(
|
||||
"For weekly orders, pickup day (%(pickup)s) must be after or equal to "
|
||||
"cutoff day (%(cutoff)s) in the same week. Current configuration would "
|
||||
"put pickup before cutoff, which is illogical."
|
||||
).format(pickup=pickup_name, cutoff=cutoff_name)
|
||||
)
|
||||
% {"pickup": pickup_name, "cutoff": cutoff_name}
|
||||
)
|
||||
|
||||
# === Onchange Methods ===
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
"""
|
||||
|
|
@ -27,144 +26,150 @@ def _register_translations():
|
|||
# ========================
|
||||
# Action Labels
|
||||
# ========================
|
||||
_('Save Cart')
|
||||
_('Reload Cart')
|
||||
_('Browse Product Categories')
|
||||
_('Proceed to Checkout')
|
||||
_('Confirm Order')
|
||||
_('Back to Cart')
|
||||
_('Remove Item')
|
||||
_('Add to Cart')
|
||||
_('Save as Draft')
|
||||
_('Load Draft')
|
||||
_('Browse Product Categories')
|
||||
_("Save Cart")
|
||||
_("Reload Cart")
|
||||
_("Browse Product Categories")
|
||||
_("Proceed to Checkout")
|
||||
_("Confirm Order")
|
||||
_("Back to Cart")
|
||||
_("Remove Item")
|
||||
_("Add to Cart")
|
||||
_("Save as Draft")
|
||||
_("Load Draft")
|
||||
_("Browse Product Categories")
|
||||
|
||||
# ========================
|
||||
# Draft Modal Labels
|
||||
# ========================
|
||||
_('Draft Already Exists')
|
||||
_('A saved draft already exists for this week.')
|
||||
_('You have two options:')
|
||||
_('Option 1: Merge with Existing Draft')
|
||||
_('Combine your current cart with the existing draft.')
|
||||
_('Existing draft has')
|
||||
_('Current cart has')
|
||||
_('item(s)')
|
||||
_('Products will be merged by adding quantities. If a product exists in both, quantities will be combined.')
|
||||
_('Option 2: Replace with Current Cart')
|
||||
_('Delete the old draft and save only the current cart items.')
|
||||
_('The existing draft will be permanently deleted.')
|
||||
_('Merge')
|
||||
_('Replace')
|
||||
_("Draft Already Exists")
|
||||
_("A saved draft already exists for this week.")
|
||||
_("You have two options:")
|
||||
_("Option 1: Merge with Existing Draft")
|
||||
_("Combine your current cart with the existing draft.")
|
||||
_("Existing draft has")
|
||||
_("Current cart has")
|
||||
_("item(s)")
|
||||
_(
|
||||
"Products will be merged by adding quantities. If a product exists in both, quantities will be combined."
|
||||
)
|
||||
_("Option 2: Replace with Current Cart")
|
||||
_("Delete the old draft and save only the current cart items.")
|
||||
_("The existing draft will be permanently deleted.")
|
||||
_("Merge")
|
||||
_("Replace")
|
||||
|
||||
# ========================
|
||||
# Draft Save/Load Confirmations
|
||||
# ========================
|
||||
_('Are you sure you want to save this cart as draft? Items to save: ')
|
||||
_('You will be able to reload this cart later.')
|
||||
_('Are you sure you want to load your last saved draft?')
|
||||
_('This will replace the current items in your cart')
|
||||
_('with the saved draft.')
|
||||
_("Are you sure you want to save this cart as draft? Items to save: ")
|
||||
_("You will be able to reload this cart later.")
|
||||
_("Are you sure you want to load your last saved draft?")
|
||||
_("This will replace the current items in your cart")
|
||||
_("with the saved draft.")
|
||||
|
||||
# ========================
|
||||
# Cart Messages (All Variations)
|
||||
# ========================
|
||||
_('Your cart is empty')
|
||||
_('This order\'s cart is empty.')
|
||||
_('This order\'s cart is empty')
|
||||
_('added to cart')
|
||||
_('items')
|
||||
_('Your cart has been restored')
|
||||
_("Your cart is empty")
|
||||
_("This order's cart is empty.")
|
||||
_("This order's cart is empty")
|
||||
_("added to cart")
|
||||
_("items")
|
||||
_("Your cart has been restored")
|
||||
|
||||
# ========================
|
||||
# Confirmation & Validation
|
||||
# ========================
|
||||
_('Confirmation')
|
||||
_('Confirm')
|
||||
_('Cancel')
|
||||
_('Please enter a valid quantity')
|
||||
_("Confirmation")
|
||||
_("Confirm")
|
||||
_("Cancel")
|
||||
_("Please enter a valid quantity")
|
||||
|
||||
# ========================
|
||||
# Error Messages
|
||||
# ========================
|
||||
_('Error: Order ID not found')
|
||||
_('No draft orders found for this week')
|
||||
_('Connection error')
|
||||
_('Error loading order')
|
||||
_('Error loading draft')
|
||||
_('Unknown error')
|
||||
_('Error saving cart')
|
||||
_('Error processing response')
|
||||
_("Error: Order ID not found")
|
||||
_("No draft orders found for this week")
|
||||
_("Connection error")
|
||||
_("Error loading order")
|
||||
_("Error loading draft")
|
||||
_("Unknown error")
|
||||
_("Error saving cart")
|
||||
_("Error processing response")
|
||||
|
||||
# ========================
|
||||
# Success Messages
|
||||
# ========================
|
||||
_('Cart saved as draft successfully')
|
||||
_('Draft order loaded successfully')
|
||||
_('Draft merged successfully')
|
||||
_('Draft replaced successfully')
|
||||
_('Order loaded')
|
||||
_('Thank you! Your order has been confirmed.')
|
||||
_('Quantity updated')
|
||||
_("Cart saved as draft successfully")
|
||||
_("Draft order loaded successfully")
|
||||
_("Draft merged successfully")
|
||||
_("Draft replaced successfully")
|
||||
_("Order loaded")
|
||||
_("Thank you! Your order has been confirmed.")
|
||||
_("Quantity updated")
|
||||
|
||||
# ========================
|
||||
# Field Labels
|
||||
# ========================
|
||||
_('Product')
|
||||
_('Supplier')
|
||||
_('Price')
|
||||
_('Quantity')
|
||||
_('Subtotal')
|
||||
_('Total')
|
||||
_("Product")
|
||||
_("Supplier")
|
||||
_("Price")
|
||||
_("Quantity")
|
||||
_("Subtotal")
|
||||
_("Total")
|
||||
|
||||
# ========================
|
||||
# Checkout Page Labels
|
||||
# ========================
|
||||
_('Home Delivery')
|
||||
_('Delivery Information')
|
||||
_('Delivery Information: Your order will be delivered at {pickup_day} {pickup_date}')
|
||||
_('Your order will be delivered the day after pickup between 11:00 - 14:00')
|
||||
_('Important')
|
||||
_('Once you confirm this order, you will not be able to modify it. Please review carefully before confirming.')
|
||||
_("Home Delivery")
|
||||
_("Delivery Information")
|
||||
_(
|
||||
"Delivery Information: Your order will be delivered at {pickup_day} {pickup_date}"
|
||||
)
|
||||
_("Your order will be delivered the day after pickup between 11:00 - 14:00")
|
||||
_("Important")
|
||||
_(
|
||||
"Once you confirm this order, you will not be able to modify it. Please review carefully before confirming."
|
||||
)
|
||||
|
||||
# ========================
|
||||
# Search & Filter Labels
|
||||
# ========================
|
||||
_('Search')
|
||||
_('Search products...')
|
||||
_('No products found')
|
||||
_('Categories')
|
||||
_('All categories')
|
||||
_("Search")
|
||||
_("Search products...")
|
||||
_("No products found")
|
||||
_("Categories")
|
||||
_("All categories")
|
||||
|
||||
# ========================
|
||||
# Category Labels
|
||||
# ========================
|
||||
_('Order Type')
|
||||
_('Order Period')
|
||||
_('Cutoff Day')
|
||||
_('Pickup Day')
|
||||
_('Store Pickup Day')
|
||||
_('Open until')
|
||||
_("Order Type")
|
||||
_("Order Period")
|
||||
_("Cutoff Day")
|
||||
_("Pickup Day")
|
||||
_("Store Pickup Day")
|
||||
_("Open until")
|
||||
|
||||
# ========================
|
||||
# Portal Page Labels (New)
|
||||
# ========================
|
||||
_('Load in Cart')
|
||||
_('Consumer Group')
|
||||
_('Delivery Information')
|
||||
_('Delivery Date:')
|
||||
_('Pickup Date:')
|
||||
_('Delivery Notice:')
|
||||
_('No special delivery instructions')
|
||||
_('Pickup Location:')
|
||||
_("Load in Cart")
|
||||
_("Consumer Group")
|
||||
_("Delivery Information")
|
||||
_("Delivery Date:")
|
||||
_("Pickup Date:")
|
||||
_("Delivery Notice:")
|
||||
_("No special delivery instructions")
|
||||
_("Pickup Location:")
|
||||
|
||||
# ========================
|
||||
# Day Names (Required for translations)
|
||||
# ========================
|
||||
_('Monday')
|
||||
_('Tuesday')
|
||||
_('Wednesday')
|
||||
_('Thursday')
|
||||
_('Friday')
|
||||
_('Saturday')
|
||||
_('Sunday')
|
||||
_("Monday")
|
||||
_("Tuesday")
|
||||
_("Wednesday")
|
||||
_("Thursday")
|
||||
_("Friday")
|
||||
_("Saturday")
|
||||
_("Sunday")
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo import _
|
||||
from odoo import api
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
_inherit = "product.product"
|
||||
|
||||
group_order_ids = fields.Many2many(
|
||||
'group.order',
|
||||
'group_order_product_rel',
|
||||
'product_id',
|
||||
'order_id',
|
||||
string='Group Orders',
|
||||
"group.order",
|
||||
"group_order_product_rel",
|
||||
"product_id",
|
||||
"order_id",
|
||||
string="Group Orders",
|
||||
readonly=True,
|
||||
help='Group orders where this product is available',
|
||||
help="Group orders where this product is available",
|
||||
)
|
||||
|
||||
@api.model
|
||||
|
|
@ -25,26 +28,25 @@ class ProductProduct(models.Model):
|
|||
responsibilities together. Keep this wrapper so existing callers
|
||||
on `product.product` keep working.
|
||||
"""
|
||||
order = self.env['group.order'].browse(order_id)
|
||||
order = self.env["group.order"].browse(order_id)
|
||||
if not order.exists():
|
||||
return self.browse()
|
||||
return order._get_products_for_group_order(order.id)
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
_inherit = "product.template"
|
||||
|
||||
group_order_ids = fields.Many2many(
|
||||
'group.order',
|
||||
compute='_compute_group_order_ids',
|
||||
string='Consumer Group Orders',
|
||||
"group.order",
|
||||
compute="_compute_group_order_ids",
|
||||
string="Consumer Group Orders",
|
||||
readonly=True,
|
||||
help='Consumer group orders where variants of this product are available',
|
||||
help="Consumer group orders where variants of this product are available",
|
||||
)
|
||||
|
||||
@api.depends('product_variant_ids.group_order_ids')
|
||||
@api.depends("product_variant_ids.group_order_ids")
|
||||
def _compute_group_order_ids(self):
|
||||
for template in self:
|
||||
variants = template.product_variant_ids
|
||||
template.group_order_ids = variants.mapped('group_order_ids')
|
||||
|
||||
template.group_order_ids = variants.mapped("group_order_ids")
|
||||
|
|
|
|||
|
|
@ -1,37 +1,39 @@
|
|||
# Copyright 2025-Today Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo import _
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
_inherit = "res.partner"
|
||||
|
||||
# Campo para identificar si un partner es un grupo
|
||||
is_group = fields.Boolean(
|
||||
string='Is a Consumer Group?',
|
||||
help='Check this box if the partner represents a group of users',
|
||||
string="Is a Consumer Group?",
|
||||
help="Check this box if the partner represents a group of users",
|
||||
default=False,
|
||||
)
|
||||
|
||||
# Relación para los miembros de un grupo (si is_group es True)
|
||||
member_ids = fields.Many2many(
|
||||
'res.partner',
|
||||
'res_partner_group_members_rel',
|
||||
'group_id',
|
||||
'member_id',
|
||||
domain=[('is_group', '=', True)],
|
||||
string='Consumer Groups',
|
||||
help='Consumer Groups this partner belongs to',
|
||||
"res.partner",
|
||||
"res_partner_group_members_rel",
|
||||
"group_id",
|
||||
"member_id",
|
||||
domain=[("is_group", "=", True)],
|
||||
string="Consumer Groups",
|
||||
help="Consumer Groups this partner belongs to",
|
||||
)
|
||||
|
||||
# Inverse relation: group orders this group participates in
|
||||
group_order_ids = fields.Many2many(
|
||||
'group.order',
|
||||
'group_order_group_rel',
|
||||
'group_id',
|
||||
'order_id',
|
||||
string='Consumer Group Orders',
|
||||
help='Group orders this consumer group participates in',
|
||||
"group.order",
|
||||
"group_order_group_rel",
|
||||
"group_id",
|
||||
"order_id",
|
||||
string="Consumer Group Orders",
|
||||
help="Group orders this consumer group participates in",
|
||||
readonly=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,46 +1,42 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
_inherit = "sale.order"
|
||||
|
||||
@staticmethod
|
||||
def _get_pickup_day_selection(records):
|
||||
def _get_pickup_day_selection(self):
|
||||
"""Return pickup day selection options with translations."""
|
||||
return [
|
||||
('0', _('Monday')),
|
||||
('1', _('Tuesday')),
|
||||
('2', _('Wednesday')),
|
||||
('3', _('Thursday')),
|
||||
('4', _('Friday')),
|
||||
('5', _('Saturday')),
|
||||
('6', _('Sunday')),
|
||||
("0", self.env._("Monday")),
|
||||
("1", self.env._("Tuesday")),
|
||||
("2", self.env._("Wednesday")),
|
||||
("3", self.env._("Thursday")),
|
||||
("4", self.env._("Friday")),
|
||||
("5", self.env._("Saturday")),
|
||||
("6", self.env._("Sunday")),
|
||||
]
|
||||
|
||||
pickup_day = fields.Selection(
|
||||
selection=_get_pickup_day_selection,
|
||||
string='Pickup Day',
|
||||
help='Day of week when this order will be picked up (inherited from group order)',
|
||||
help="Day of week when this order will be picked up (inherited from group order)",
|
||||
)
|
||||
|
||||
group_order_id = fields.Many2one(
|
||||
'group.order',
|
||||
string='Consumer Group Order',
|
||||
help='Reference to the consumer group order that originated this sale order',
|
||||
"group.order",
|
||||
help="Reference to the consumer group order that originated this sale order",
|
||||
)
|
||||
|
||||
pickup_date = fields.Date(
|
||||
string='Pickup Date',
|
||||
help='Calculated pickup/delivery date (inherited from consumer group order)',
|
||||
help="Calculated pickup/delivery date (inherited from consumer group order)",
|
||||
)
|
||||
|
||||
home_delivery = fields.Boolean(
|
||||
string='Home Delivery',
|
||||
default=False,
|
||||
help='Whether this order includes home delivery (inherited from consumer group order)',
|
||||
help="Whether this order includes home delivery (inherited from consumer group order)",
|
||||
)
|
||||
|
||||
def _get_name_portal_content_view(self):
|
||||
|
|
@ -52,5 +48,5 @@ class SaleOrder(models.Model):
|
|||
"""
|
||||
self.ensure_one()
|
||||
if self.group_order_id:
|
||||
return 'website_sale_aplicoop.sale_order_portal_content_aplicoop'
|
||||
return "website_sale_aplicoop.sale_order_portal_content_aplicoop"
|
||||
return super()._get_name_portal_content_view()
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ The implementation follows OCA standards for:
|
|||
- Code quality and testing (26 passing tests)
|
||||
- Documentation structure and multilingual support
|
||||
- Security and access control
|
||||
|
||||
|
|
|
|||
|
|
@ -48,4 +48,3 @@
|
|||
|
||||
- `start_date` must be ≤ `end_date` (when both filled)
|
||||
- Empty end_date = permanent order
|
||||
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ access_group_order_user,group.order user,model_group_order,website_sale_aplicoop
|
|||
access_group_order_manager,group.order manager,model_group_order,website_sale_aplicoop.group_group_order_manager,1,1,1,1
|
||||
access_group_order_portal,group.order portal,model_group_order,base.group_portal,1,0,0,0
|
||||
access_product_supplierinfo_portal,product.supplierinfo portal,product.model_product_supplierinfo,base.group_portal,1,0,0,0
|
||||
|
||||
|
|
|
|||
|
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
with open("README.rst", "r", encoding="utf-8") as fh:
|
||||
with open("README.rst", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setup(
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
--border-dark: #718096;
|
||||
|
||||
/* ========== TYPOGRAPHY ========== */
|
||||
--font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
Arial, sans-serif;
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
|
|
@ -57,7 +58,7 @@
|
|||
|
||||
/* ========== TRANSITIONS ========== */
|
||||
--transition-fast: 200ms ease;
|
||||
--transition-normal: 320ms cubic-bezier(.2, .9, .2, 1);
|
||||
--transition-normal: 320ms cubic-bezier(0.2, 0.9, 0.2, 1);
|
||||
--transition-slow: 500ms ease;
|
||||
|
||||
/* ========== Z-INDEX ========== */
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
border: 1px solid rgba(90, 103, 216, 0.12);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 6px 18px rgba(28, 37, 80, 0.06);
|
||||
transition: transform 320ms cubic-bezier(.2, .9, .2, 1), box-shadow 320ms, border-color 320ms, background 320ms;
|
||||
transition: transform 320ms cubic-bezier(0.2, 0.9, 0.2, 1), box-shadow 320ms, border-color 320ms,
|
||||
background 320ms;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -139,7 +140,7 @@
|
|||
}
|
||||
|
||||
.eskaera-order-card .btn::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,11 @@
|
|||
}
|
||||
|
||||
.product-card:hover .card-body {
|
||||
background: linear-gradient(135deg, rgba(108, 117, 125, 0.10) 0%, rgba(108, 117, 125, 0.08) 100%);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(108, 117, 125, 0.1) 0%,
|
||||
rgba(108, 117, 125, 0.08) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.product-card .card-title {
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@
|
|||
* Page backgrounds and main layout structures
|
||||
*/
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
background-color: transparent !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
body.website_published {
|
||||
background: linear-gradient(135deg,
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color-mix(in srgb, var(--primary-color) 30%, white),
|
||||
color-mix(in srgb, var(--primary-color) 60%, black)
|
||||
) !important;
|
||||
|
|
@ -32,21 +34,24 @@ body.website_published .eskaera-checkout-page {
|
|||
|
||||
.eskaera-page,
|
||||
.eskaera-generic-page {
|
||||
background: linear-gradient(180deg,
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--primary-color) 10%, white),
|
||||
color-mix(in srgb, var(--primary-color) 70%, black)
|
||||
) !important;
|
||||
}
|
||||
|
||||
.eskaera-shop-page {
|
||||
background: linear-gradient(135deg,
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color-mix(in srgb, var(--primary-color) 10%, white),
|
||||
color-mix(in srgb, var(--primary-color) 10%, rgb(135, 135, 135))
|
||||
) !important;
|
||||
}
|
||||
|
||||
.eskaera-checkout-page {
|
||||
background: linear-gradient(-135deg,
|
||||
background: linear-gradient(
|
||||
-135deg,
|
||||
color-mix(in srgb, var(--primary-color) 0%, white),
|
||||
color-mix(in srgb, var(--primary-color) 60%, black)
|
||||
) !important;
|
||||
|
|
@ -54,29 +59,54 @@ body.website_published .eskaera-checkout-page {
|
|||
|
||||
.eskaera-page::before,
|
||||
.eskaera-generic-page::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 50%, color-mix(in srgb, var(--primary-color, white) 20%, transparent) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, color-mix(in srgb, var(--primary-color) 25%, transparent) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 20%, color-mix(in srgb, var(--primary-color, white) 15%, transparent) 0%, transparent 50%);
|
||||
background-image: radial-gradient(
|
||||
circle at 20% 50%,
|
||||
color-mix(in srgb, var(--primary-color, white) 20%, transparent) 0%,
|
||||
transparent 50%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 80% 80%,
|
||||
color-mix(in srgb, var(--primary-color) 25%, transparent) 0%,
|
||||
transparent 50%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 40% 20%,
|
||||
color-mix(in srgb, var(--primary-color, white) 15%, transparent) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
}
|
||||
|
||||
.eskaera-shop-page::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 15% 30%, color-mix(in srgb, var(--primary-color, white) 18%, transparent) 0%, transparent 50%),
|
||||
radial-gradient(circle at 85% 70%, color-mix(in srgb, var(--primary-color) 22%, transparent) 0%, transparent 50%);
|
||||
background-image: radial-gradient(
|
||||
circle at 15% 30%,
|
||||
color-mix(in srgb, var(--primary-color, white) 18%, transparent) 0%,
|
||||
transparent 50%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 85% 70%,
|
||||
color-mix(in srgb, var(--primary-color) 22%, transparent) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
}
|
||||
|
||||
.eskaera-checkout-page::before {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 50%, color-mix(in srgb, var(--primary-color, white) 20%, transparent) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, color-mix(in srgb, var(--primary-color) 25%, transparent) 0%, transparent 50%);
|
||||
background-image: radial-gradient(
|
||||
circle at 20% 50%,
|
||||
color-mix(in srgb, var(--primary-color, white) 20%, transparent) 0%,
|
||||
transparent 50%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 80% 80%,
|
||||
color-mix(in srgb, var(--primary-color) 25%, transparent) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
}
|
||||
|
||||
.eskaera-page::before,
|
||||
.eskaera-shop-page::before,
|
||||
.eskaera-generic-page::before,
|
||||
.eskaera-checkout-page::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
|
|
|||
|
|
@ -15,36 +15,36 @@
|
|||
/* ============================================
|
||||
1. BASE & VARIABLES
|
||||
============================================ */
|
||||
@import 'base/variables.css';
|
||||
@import 'base/utilities.css';
|
||||
@import "base/variables.css";
|
||||
@import "base/utilities.css";
|
||||
|
||||
/* ============================================
|
||||
2. LAYOUT & PAGES
|
||||
============================================ */
|
||||
@import 'layout/pages.css';
|
||||
@import 'layout/header.css';
|
||||
@import "layout/pages.css";
|
||||
@import "layout/header.css";
|
||||
|
||||
/* ============================================
|
||||
3. COMPONENTS (Reusable UI elements)
|
||||
============================================ */
|
||||
@import 'components/product-card.css';
|
||||
@import 'components/order-card.css';
|
||||
@import 'components/cart.css';
|
||||
@import 'components/buttons.css';
|
||||
@import 'components/quantity-control.css';
|
||||
@import 'components/forms.css';
|
||||
@import 'components/alerts.css';
|
||||
@import 'components/tag-filter.css';
|
||||
@import "components/product-card.css";
|
||||
@import "components/order-card.css";
|
||||
@import "components/cart.css";
|
||||
@import "components/buttons.css";
|
||||
@import "components/quantity-control.css";
|
||||
@import "components/forms.css";
|
||||
@import "components/alerts.css";
|
||||
@import "components/tag-filter.css";
|
||||
|
||||
/* ============================================
|
||||
4. SECTIONS (Page-specific layouts)
|
||||
============================================ */
|
||||
@import 'sections/products-grid.css';
|
||||
@import 'sections/order-list.css';
|
||||
@import 'sections/checkout.css';
|
||||
@import 'sections/info-cards.css';
|
||||
@import "sections/products-grid.css";
|
||||
@import "sections/order-list.css";
|
||||
@import "sections/checkout.css";
|
||||
@import "sections/info-cards.css";
|
||||
|
||||
/* ============================================
|
||||
5. RESPONSIVE DESIGN (Media queries)
|
||||
============================================ */
|
||||
@import 'layout/responsive.css';
|
||||
@import "layout/responsive.css";
|
||||
|
|
|
|||
|
|
@ -5,93 +5,104 @@
|
|||
* before rendering the checkout summary.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
console.log('[CHECKOUT] Script loaded');
|
||||
console.log("[CHECKOUT] Script loaded");
|
||||
|
||||
// Get order ID from button
|
||||
var confirmBtn = document.getElementById('confirm-order-btn');
|
||||
var confirmBtn = document.getElementById("confirm-order-btn");
|
||||
if (!confirmBtn) {
|
||||
console.log('[CHECKOUT] No confirm button found');
|
||||
console.log("[CHECKOUT] No confirm button found");
|
||||
return;
|
||||
}
|
||||
|
||||
var orderId = confirmBtn.getAttribute('data-order-id');
|
||||
var orderId = confirmBtn.getAttribute("data-order-id");
|
||||
if (!orderId) {
|
||||
console.log('[CHECKOUT] No order ID found');
|
||||
console.log("[CHECKOUT] No order ID found");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[CHECKOUT] Order ID:', orderId);
|
||||
console.log("[CHECKOUT] Order ID:", orderId);
|
||||
|
||||
// Get summary div
|
||||
var summaryDiv = document.getElementById('checkout-summary');
|
||||
var summaryDiv = document.getElementById("checkout-summary");
|
||||
if (!summaryDiv) {
|
||||
console.log('[CHECKOUT] No summary div found');
|
||||
console.log("[CHECKOUT] No summary div found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Function to fetch labels and render checkout
|
||||
var fetchLabelsAndRender = function() {
|
||||
console.log('[CHECKOUT] Fetching labels...');
|
||||
var fetchLabelsAndRender = function () {
|
||||
console.log("[CHECKOUT] Fetching labels...");
|
||||
|
||||
// Wait for window.groupOrderShop.labels to be initialized (contains hardcoded labels)
|
||||
var waitForLabels = function(callback, maxWait = 3000, checkInterval = 50) {
|
||||
var waitForLabels = function (callback, maxWait = 3000, checkInterval = 50) {
|
||||
var startTime = Date.now();
|
||||
var checkLabels = function() {
|
||||
if (window.groupOrderShop && window.groupOrderShop.labels && Object.keys(window.groupOrderShop.labels).length > 0) {
|
||||
console.log('[CHECKOUT] ✅ Hardcoded labels found, proceeding');
|
||||
var checkLabels = function () {
|
||||
if (
|
||||
window.groupOrderShop &&
|
||||
window.groupOrderShop.labels &&
|
||||
Object.keys(window.groupOrderShop.labels).length > 0
|
||||
) {
|
||||
console.log("[CHECKOUT] ✅ Hardcoded labels found, proceeding");
|
||||
callback();
|
||||
} else if (Date.now() - startTime < maxWait) {
|
||||
setTimeout(checkLabels, checkInterval);
|
||||
} else {
|
||||
console.log('[CHECKOUT] ⚠️ Timeout waiting for labels, proceeding anyway');
|
||||
console.log("[CHECKOUT] ⚠️ Timeout waiting for labels, proceeding anyway");
|
||||
callback();
|
||||
}
|
||||
};
|
||||
checkLabels();
|
||||
};
|
||||
|
||||
waitForLabels(function() {
|
||||
waitForLabels(function () {
|
||||
// Now fetch additional labels from server
|
||||
// Detect current language from document or navigator
|
||||
var currentLang = document.documentElement.lang ||
|
||||
document.documentElement.getAttribute('lang') ||
|
||||
var currentLang =
|
||||
document.documentElement.lang ||
|
||||
document.documentElement.getAttribute("lang") ||
|
||||
navigator.language ||
|
||||
'es_ES';
|
||||
console.log('[CHECKOUT] Detected language:', currentLang);
|
||||
"es_ES";
|
||||
console.log("[CHECKOUT] Detected language:", currentLang);
|
||||
|
||||
fetch('/eskaera/labels', {
|
||||
method: 'POST',
|
||||
fetch("/eskaera/labels", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
lang: currentLang
|
||||
lang: currentLang,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.then(function(response) {
|
||||
console.log('[CHECKOUT] Response status:', response.status);
|
||||
.then(function (response) {
|
||||
console.log("[CHECKOUT] Response status:", response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
console.log('[CHECKOUT] Response data:', data);
|
||||
.then(function (data) {
|
||||
console.log("[CHECKOUT] Response data:", data);
|
||||
var serverLabels = data.result || data;
|
||||
console.log('[CHECKOUT] Server labels count:', Object.keys(serverLabels).length);
|
||||
console.log('[CHECKOUT] Sample server labels:', {
|
||||
console.log(
|
||||
"[CHECKOUT] Server labels count:",
|
||||
Object.keys(serverLabels).length
|
||||
);
|
||||
console.log("[CHECKOUT] Sample server labels:", {
|
||||
draft_merged_success: serverLabels.draft_merged_success,
|
||||
home_delivery: serverLabels.home_delivery
|
||||
home_delivery: serverLabels.home_delivery,
|
||||
});
|
||||
|
||||
// CRITICAL: Merge server labels with existing hardcoded labels
|
||||
// Hardcoded labels MUST take precedence over server labels
|
||||
if (window.groupOrderShop && window.groupOrderShop.labels) {
|
||||
var existingLabels = window.groupOrderShop.labels;
|
||||
console.log('[CHECKOUT] Existing hardcoded labels count:', Object.keys(existingLabels).length);
|
||||
console.log('[CHECKOUT] Sample existing labels:', {
|
||||
console.log(
|
||||
"[CHECKOUT] Existing hardcoded labels count:",
|
||||
Object.keys(existingLabels).length
|
||||
);
|
||||
console.log("[CHECKOUT] Sample existing labels:", {
|
||||
draft_merged_success: existingLabels.draft_merged_success,
|
||||
home_delivery: existingLabels.home_delivery
|
||||
home_delivery: existingLabels.home_delivery,
|
||||
});
|
||||
|
||||
// Start with server labels, then overwrite with hardcoded ones
|
||||
|
|
@ -99,23 +110,26 @@
|
|||
Object.assign(mergedLabels, existingLabels);
|
||||
|
||||
window.groupOrderShop.labels = mergedLabels;
|
||||
console.log('[CHECKOUT] ✅ Merged labels - final count:', Object.keys(mergedLabels).length);
|
||||
console.log('[CHECKOUT] Verification:', {
|
||||
console.log(
|
||||
"[CHECKOUT] ✅ Merged labels - final count:",
|
||||
Object.keys(mergedLabels).length
|
||||
);
|
||||
console.log("[CHECKOUT] Verification:", {
|
||||
draft_merged_success: mergedLabels.draft_merged_success,
|
||||
home_delivery: mergedLabels.home_delivery
|
||||
home_delivery: mergedLabels.home_delivery,
|
||||
});
|
||||
} else {
|
||||
// If no existing labels, use server labels as fallback
|
||||
if (window.groupOrderShop) {
|
||||
window.groupOrderShop.labels = serverLabels;
|
||||
}
|
||||
console.log('[CHECKOUT] ⚠️ No existing labels, using server labels');
|
||||
console.log("[CHECKOUT] ⚠️ No existing labels, using server labels");
|
||||
}
|
||||
|
||||
window.renderCheckoutSummary(window.groupOrderShop.labels);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('[CHECKOUT] Error:', error);
|
||||
.catch(function (error) {
|
||||
console.error("[CHECKOUT] Error:", error);
|
||||
// Fallback to translated labels
|
||||
window.renderCheckoutSummary(window.getCheckoutLabels());
|
||||
});
|
||||
|
|
@ -125,20 +139,24 @@
|
|||
// Listen for cart ready event instead of polling
|
||||
if (window.groupOrderShop && window.groupOrderShop.orderId) {
|
||||
// Cart already initialized, render immediately
|
||||
console.log('[CHECKOUT] Cart already ready');
|
||||
console.log("[CHECKOUT] Cart already ready");
|
||||
fetchLabelsAndRender();
|
||||
} else {
|
||||
// Wait for cart initialization event
|
||||
console.log('[CHECKOUT] Waiting for cart ready event...');
|
||||
document.addEventListener('groupOrderCartReady', function() {
|
||||
console.log('[CHECKOUT] Cart ready event received');
|
||||
console.log("[CHECKOUT] Waiting for cart ready event...");
|
||||
document.addEventListener(
|
||||
"groupOrderCartReady",
|
||||
function () {
|
||||
console.log("[CHECKOUT] Cart ready event received");
|
||||
fetchLabelsAndRender();
|
||||
}, { once: true });
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
// Fallback timeout in case event never fires
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
if (window.groupOrderShop && window.groupOrderShop.orderId) {
|
||||
console.log('[CHECKOUT] Fallback timeout triggered');
|
||||
console.log("[CHECKOUT] Fallback timeout triggered");
|
||||
fetchLabelsAndRender();
|
||||
}
|
||||
}, 500);
|
||||
|
|
@ -148,67 +166,88 @@
|
|||
* Render order summary table or empty message
|
||||
* Exposed globally so other scripts can call it
|
||||
*/
|
||||
window.renderCheckoutSummary = function(labels) {
|
||||
window.renderCheckoutSummary = function (labels) {
|
||||
labels = labels || window.getCheckoutLabels();
|
||||
|
||||
var summaryDiv = document.getElementById('checkout-summary');
|
||||
var summaryDiv = document.getElementById("checkout-summary");
|
||||
if (!summaryDiv) return;
|
||||
|
||||
var cartKey = 'eskaera_' + (document.getElementById('confirm-order-btn') ? document.getElementById('confirm-order-btn').getAttribute('data-order-id') : '1') + '_cart';
|
||||
var cart = JSON.parse(localStorage.getItem(cartKey) || '{}');
|
||||
var cartKey =
|
||||
"eskaera_" +
|
||||
(document.getElementById("confirm-order-btn")
|
||||
? document.getElementById("confirm-order-btn").getAttribute("data-order-id")
|
||||
: "1") +
|
||||
"_cart";
|
||||
var cart = JSON.parse(localStorage.getItem(cartKey) || "{}");
|
||||
|
||||
var summaryTable = summaryDiv.querySelector('.checkout-summary-table');
|
||||
var tbody = summaryDiv.querySelector('#checkout-summary-tbody');
|
||||
var totalSection = summaryDiv.querySelector('.checkout-total-section');
|
||||
var summaryTable = summaryDiv.querySelector(".checkout-summary-table");
|
||||
var tbody = summaryDiv.querySelector("#checkout-summary-tbody");
|
||||
var totalSection = summaryDiv.querySelector(".checkout-total-section");
|
||||
|
||||
// If no table found, create it with headers (shouldn't happen, but fallback)
|
||||
if (!summaryTable) {
|
||||
var html = '<table class="table table-hover checkout-summary-table" id="checkout-summary-table" role="grid" aria-label="Purchase summary"><thead class="table-dark"><tr>' +
|
||||
'<th scope="col" class="col-name">' + escapeHtml(labels.product) + '</th>' +
|
||||
'<th scope="col" class="col-qty text-center">' + escapeHtml(labels.quantity) + '</th>' +
|
||||
'<th scope="col" class="col-price text-right">' + escapeHtml(labels.price) + '</th>' +
|
||||
'<th scope="col" class="col-subtotal text-right">' + escapeHtml(labels.subtotal) + '</th>' +
|
||||
var html =
|
||||
'<table class="table table-hover checkout-summary-table" id="checkout-summary-table" role="grid" aria-label="Purchase summary"><thead class="table-dark"><tr>' +
|
||||
'<th scope="col" class="col-name">' +
|
||||
escapeHtml(labels.product) +
|
||||
"</th>" +
|
||||
'<th scope="col" class="col-qty text-center">' +
|
||||
escapeHtml(labels.quantity) +
|
||||
"</th>" +
|
||||
'<th scope="col" class="col-price text-right">' +
|
||||
escapeHtml(labels.price) +
|
||||
"</th>" +
|
||||
'<th scope="col" class="col-subtotal text-right">' +
|
||||
escapeHtml(labels.subtotal) +
|
||||
"</th>" +
|
||||
'</tr></thead><tbody id="checkout-summary-tbody"></tbody></table>' +
|
||||
'<div class="checkout-total-section"><div class="total-row">' +
|
||||
'<span class="total-label">' + escapeHtml(labels.total) + '</span>' +
|
||||
'<span class="total-label">' +
|
||||
escapeHtml(labels.total) +
|
||||
"</span>" +
|
||||
'<span class="total-amount" id="checkout-total-amount">€0.00</span>' +
|
||||
'</div></div>';
|
||||
"</div></div>";
|
||||
summaryDiv.innerHTML = html;
|
||||
summaryTable = summaryDiv.querySelector('.checkout-summary-table');
|
||||
tbody = summaryDiv.querySelector('#checkout-summary-tbody');
|
||||
totalSection = summaryDiv.querySelector('.checkout-total-section');
|
||||
summaryTable = summaryDiv.querySelector(".checkout-summary-table");
|
||||
tbody = summaryDiv.querySelector("#checkout-summary-tbody");
|
||||
totalSection = summaryDiv.querySelector(".checkout-total-section");
|
||||
}
|
||||
|
||||
// Clear only tbody, preserve headers
|
||||
tbody.innerHTML = '';
|
||||
tbody.innerHTML = "";
|
||||
|
||||
if (Object.keys(cart).length === 0) {
|
||||
// Show empty message if cart is empty
|
||||
var emptyRow = document.createElement('tr');
|
||||
emptyRow.id = 'checkout-empty-row';
|
||||
emptyRow.className = 'empty-message';
|
||||
emptyRow.innerHTML = '<td colspan="4" class="text-center text-muted py-4">' +
|
||||
var emptyRow = document.createElement("tr");
|
||||
emptyRow.id = "checkout-empty-row";
|
||||
emptyRow.className = "empty-message";
|
||||
emptyRow.innerHTML =
|
||||
'<td colspan="4" class="text-center text-muted py-4">' +
|
||||
'<i class="fa fa-inbox fa-2x mb-2"></i>' +
|
||||
'<p>' + escapeHtml(labels.empty) + '</p>' +
|
||||
'</td>';
|
||||
"<p>" +
|
||||
escapeHtml(labels.empty) +
|
||||
"</p>" +
|
||||
"</td>";
|
||||
tbody.appendChild(emptyRow);
|
||||
|
||||
// Hide total section
|
||||
totalSection.style.display = 'none';
|
||||
totalSection.style.display = "none";
|
||||
} else {
|
||||
// Hide empty row if visible
|
||||
var emptyRow = tbody.querySelector('#checkout-empty-row');
|
||||
var emptyRow = tbody.querySelector("#checkout-empty-row");
|
||||
if (emptyRow) emptyRow.remove();
|
||||
|
||||
// Get delivery product ID from page data
|
||||
var checkoutPage = document.querySelector('.eskaera-checkout-page');
|
||||
var deliveryProductId = checkoutPage ? checkoutPage.getAttribute('data-delivery-product-id') : null;
|
||||
var checkoutPage = document.querySelector(".eskaera-checkout-page");
|
||||
var deliveryProductId = checkoutPage
|
||||
? checkoutPage.getAttribute("data-delivery-product-id")
|
||||
: null;
|
||||
|
||||
// Separate normal products from delivery product
|
||||
var normalProducts = [];
|
||||
var deliveryProduct = null;
|
||||
|
||||
Object.keys(cart).forEach(function(productId) {
|
||||
Object.keys(cart).forEach(function (productId) {
|
||||
if (productId === deliveryProductId) {
|
||||
deliveryProduct = { id: productId, item: cart[productId] };
|
||||
} else {
|
||||
|
|
@ -217,14 +256,14 @@
|
|||
});
|
||||
|
||||
// Sort normal products numerically
|
||||
normalProducts.sort(function(a, b) {
|
||||
normalProducts.sort(function (a, b) {
|
||||
return parseInt(a.id) - parseInt(b.id);
|
||||
});
|
||||
|
||||
var total = 0;
|
||||
|
||||
// Render normal products first
|
||||
normalProducts.forEach(function(product) {
|
||||
normalProducts.forEach(function (product) {
|
||||
var item = product.item;
|
||||
var qty = parseFloat(item.quantity || item.qty || 1);
|
||||
if (isNaN(qty)) qty = 1;
|
||||
|
|
@ -233,11 +272,20 @@
|
|||
var subtotal = qty * price;
|
||||
total += subtotal;
|
||||
|
||||
var row = document.createElement('tr');
|
||||
row.innerHTML = '<td>' + escapeHtml(item.name) + '</td>' +
|
||||
'<td class="text-center">' + qty.toFixed(2).replace(/\.?0+$/, '') + '</td>' +
|
||||
'<td class="text-right">€' + price.toFixed(2) + '</td>' +
|
||||
'<td class="text-right">€' + subtotal.toFixed(2) + '</td>';
|
||||
var row = document.createElement("tr");
|
||||
row.innerHTML =
|
||||
"<td>" +
|
||||
escapeHtml(item.name) +
|
||||
"</td>" +
|
||||
'<td class="text-center">' +
|
||||
qty.toFixed(2).replace(/\.?0+$/, "") +
|
||||
"</td>" +
|
||||
'<td class="text-right">€' +
|
||||
price.toFixed(2) +
|
||||
"</td>" +
|
||||
'<td class="text-right">€' +
|
||||
subtotal.toFixed(2) +
|
||||
"</td>";
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
|
|
@ -251,32 +299,41 @@
|
|||
var subtotal = qty * price;
|
||||
total += subtotal;
|
||||
|
||||
var row = document.createElement('tr');
|
||||
row.innerHTML = '<td>' + escapeHtml(item.name) + '</td>' +
|
||||
'<td class="text-center">' + qty.toFixed(2).replace(/\.?0+$/, '') + '</td>' +
|
||||
'<td class="text-right">€' + price.toFixed(2) + '</td>' +
|
||||
'<td class="text-right">€' + subtotal.toFixed(2) + '</td>';
|
||||
var row = document.createElement("tr");
|
||||
row.innerHTML =
|
||||
"<td>" +
|
||||
escapeHtml(item.name) +
|
||||
"</td>" +
|
||||
'<td class="text-center">' +
|
||||
qty.toFixed(2).replace(/\.?0+$/, "") +
|
||||
"</td>" +
|
||||
'<td class="text-right">€' +
|
||||
price.toFixed(2) +
|
||||
"</td>" +
|
||||
'<td class="text-right">€' +
|
||||
subtotal.toFixed(2) +
|
||||
"</td>";
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
|
||||
// Update total
|
||||
var totalAmount = summaryDiv.querySelector('#checkout-total-amount');
|
||||
var totalAmount = summaryDiv.querySelector("#checkout-total-amount");
|
||||
if (totalAmount) {
|
||||
totalAmount.textContent = '€' + total.toFixed(2);
|
||||
totalAmount.textContent = "€" + total.toFixed(2);
|
||||
}
|
||||
|
||||
// Show total section
|
||||
totalSection.style.display = 'block';
|
||||
totalSection.style.display = "block";
|
||||
}
|
||||
|
||||
console.log('[CHECKOUT] Summary rendered');
|
||||
console.log("[CHECKOUT] Summary rendered");
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape HTML to prevent XSS
|
||||
*/
|
||||
function escapeHtml(text) {
|
||||
var div = document.createElement('div');
|
||||
var div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
* This file is kept for backwards compatibility but is no longer needed.
|
||||
* The main renderSummary() logic is in checkout_labels.js
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
// Checkout rendering is handled by checkout_labels.js
|
||||
})();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,56 +3,65 @@
|
|||
* Manages home delivery checkbox and product addition/removal
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var HomeDeliveryManager = {
|
||||
deliveryProductId: null,
|
||||
deliveryProductPrice: 5.74,
|
||||
deliveryProductName: 'Home Delivery', // Default fallback
|
||||
deliveryProductName: "Home Delivery", // Default fallback
|
||||
orderId: null,
|
||||
homeDeliveryEnabled: false,
|
||||
|
||||
init: function() {
|
||||
init: function () {
|
||||
// Get delivery product info from data attributes
|
||||
var checkoutPage = document.querySelector('.eskaera-checkout-page');
|
||||
var checkoutPage = document.querySelector(".eskaera-checkout-page");
|
||||
if (checkoutPage) {
|
||||
this.deliveryProductId = checkoutPage.getAttribute('data-delivery-product-id');
|
||||
console.log('[HomeDelivery] deliveryProductId from attribute:', this.deliveryProductId, 'type:', typeof this.deliveryProductId);
|
||||
this.deliveryProductId = checkoutPage.getAttribute("data-delivery-product-id");
|
||||
console.log(
|
||||
"[HomeDelivery] deliveryProductId from attribute:",
|
||||
this.deliveryProductId,
|
||||
"type:",
|
||||
typeof this.deliveryProductId
|
||||
);
|
||||
|
||||
var price = checkoutPage.getAttribute('data-delivery-product-price');
|
||||
var price = checkoutPage.getAttribute("data-delivery-product-price");
|
||||
if (price) {
|
||||
this.deliveryProductPrice = parseFloat(price);
|
||||
}
|
||||
|
||||
// Get translated product name from data attribute (auto-translated by Odoo server)
|
||||
var productName = checkoutPage.getAttribute('data-delivery-product-name');
|
||||
var productName = checkoutPage.getAttribute("data-delivery-product-name");
|
||||
if (productName) {
|
||||
this.deliveryProductName = productName;
|
||||
console.log('[HomeDelivery] Using translated product name from server:', this.deliveryProductName);
|
||||
console.log(
|
||||
"[HomeDelivery] Using translated product name from server:",
|
||||
this.deliveryProductName
|
||||
);
|
||||
}
|
||||
|
||||
// Check if home delivery is enabled for this order
|
||||
var homeDeliveryAttr = checkoutPage.getAttribute('data-home-delivery-enabled');
|
||||
this.homeDeliveryEnabled = homeDeliveryAttr === 'true' || homeDeliveryAttr === 'True';
|
||||
console.log('[HomeDelivery] Home delivery enabled:', this.homeDeliveryEnabled);
|
||||
var homeDeliveryAttr = checkoutPage.getAttribute("data-home-delivery-enabled");
|
||||
this.homeDeliveryEnabled =
|
||||
homeDeliveryAttr === "true" || homeDeliveryAttr === "True";
|
||||
console.log("[HomeDelivery] Home delivery enabled:", this.homeDeliveryEnabled);
|
||||
|
||||
// Show/hide home delivery section based on configuration
|
||||
this.toggleHomeDeliverySection();
|
||||
}
|
||||
|
||||
// Get order ID from confirm button
|
||||
var confirmBtn = document.getElementById('confirm-order-btn');
|
||||
var confirmBtn = document.getElementById("confirm-order-btn");
|
||||
if (confirmBtn) {
|
||||
this.orderId = confirmBtn.getAttribute('data-order-id');
|
||||
console.log('[HomeDelivery] orderId from button:', this.orderId);
|
||||
this.orderId = confirmBtn.getAttribute("data-order-id");
|
||||
console.log("[HomeDelivery] orderId from button:", this.orderId);
|
||||
}
|
||||
|
||||
var checkbox = document.getElementById('home-delivery-checkbox');
|
||||
var checkbox = document.getElementById("home-delivery-checkbox");
|
||||
if (!checkbox) return;
|
||||
|
||||
var self = this;
|
||||
checkbox.addEventListener('change', function() {
|
||||
checkbox.addEventListener("change", function () {
|
||||
if (this.checked) {
|
||||
self.addDeliveryProduct();
|
||||
self.showDeliveryInfo();
|
||||
|
|
@ -66,42 +75,44 @@
|
|||
this.checkDeliveryInCart();
|
||||
},
|
||||
|
||||
toggleHomeDeliverySection: function() {
|
||||
var homeDeliverySection = document.querySelector('[id*="home-delivery"], [class*="home-delivery"]');
|
||||
var checkbox = document.getElementById('home-delivery-checkbox');
|
||||
var homeDeliveryContainer = document.getElementById('home-delivery-container');
|
||||
toggleHomeDeliverySection: function () {
|
||||
var homeDeliverySection = document.querySelector(
|
||||
'[id*="home-delivery"], [class*="home-delivery"]'
|
||||
);
|
||||
var checkbox = document.getElementById("home-delivery-checkbox");
|
||||
var homeDeliveryContainer = document.getElementById("home-delivery-container");
|
||||
|
||||
if (this.homeDeliveryEnabled) {
|
||||
// Show home delivery option
|
||||
if (checkbox) {
|
||||
checkbox.closest('.form-check').style.display = 'block';
|
||||
checkbox.closest(".form-check").style.display = "block";
|
||||
}
|
||||
if (homeDeliveryContainer) {
|
||||
homeDeliveryContainer.style.display = 'block';
|
||||
homeDeliveryContainer.style.display = "block";
|
||||
}
|
||||
console.log('[HomeDelivery] Home delivery option shown');
|
||||
console.log("[HomeDelivery] Home delivery option shown");
|
||||
} else {
|
||||
// Hide home delivery option and delivery info alert
|
||||
if (checkbox) {
|
||||
checkbox.closest('.form-check').style.display = 'none';
|
||||
checkbox.closest(".form-check").style.display = "none";
|
||||
checkbox.checked = false;
|
||||
}
|
||||
if (homeDeliveryContainer) {
|
||||
homeDeliveryContainer.style.display = 'none';
|
||||
homeDeliveryContainer.style.display = "none";
|
||||
}
|
||||
// Also hide the delivery info alert when home delivery is disabled
|
||||
this.hideDeliveryInfo();
|
||||
this.removeDeliveryProduct();
|
||||
console.log('[HomeDelivery] Home delivery option and delivery info hidden');
|
||||
console.log("[HomeDelivery] Home delivery option and delivery info hidden");
|
||||
}
|
||||
},
|
||||
|
||||
checkDeliveryInCart: function() {
|
||||
checkDeliveryInCart: function () {
|
||||
if (!this.deliveryProductId) return;
|
||||
|
||||
var cart = this.getCart();
|
||||
if (cart[this.deliveryProductId]) {
|
||||
var checkbox = document.getElementById('home-delivery-checkbox');
|
||||
var checkbox = document.getElementById("home-delivery-checkbox");
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
this.showDeliveryInfo();
|
||||
|
|
@ -109,93 +120,103 @@
|
|||
}
|
||||
},
|
||||
|
||||
getCart: function() {
|
||||
getCart: function () {
|
||||
if (!this.orderId) return {};
|
||||
var cartKey = 'eskaera_' + this.orderId + '_cart';
|
||||
var cartKey = "eskaera_" + this.orderId + "_cart";
|
||||
var cartStr = localStorage.getItem(cartKey);
|
||||
return cartStr ? JSON.parse(cartStr) : {};
|
||||
},
|
||||
|
||||
saveCart: function(cart) {
|
||||
saveCart: function (cart) {
|
||||
if (!this.orderId) return;
|
||||
var cartKey = 'eskaera_' + this.orderId + '_cart';
|
||||
var cartKey = "eskaera_" + this.orderId + "_cart";
|
||||
localStorage.setItem(cartKey, JSON.stringify(cart));
|
||||
|
||||
// Re-render checkout summary without reloading
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
// Use the global function from checkout_labels.js
|
||||
if (typeof window.renderCheckoutSummary === 'function') {
|
||||
if (typeof window.renderCheckoutSummary === "function") {
|
||||
window.renderCheckoutSummary();
|
||||
}
|
||||
}, 50);
|
||||
},
|
||||
|
||||
renderCheckoutSummary: function() {
|
||||
renderCheckoutSummary: function () {
|
||||
// Stub - now handled by global window.renderCheckoutSummary
|
||||
},
|
||||
|
||||
addDeliveryProduct: function() {
|
||||
addDeliveryProduct: function () {
|
||||
if (!this.deliveryProductId) {
|
||||
console.warn('[HomeDelivery] Delivery product ID not found');
|
||||
console.warn("[HomeDelivery] Delivery product ID not found");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[HomeDelivery] Adding delivery product - deliveryProductId:', this.deliveryProductId, 'orderId:', this.orderId);
|
||||
console.log(
|
||||
"[HomeDelivery] Adding delivery product - deliveryProductId:",
|
||||
this.deliveryProductId,
|
||||
"orderId:",
|
||||
this.orderId
|
||||
);
|
||||
var cart = this.getCart();
|
||||
console.log('[HomeDelivery] Current cart before adding:', cart);
|
||||
console.log("[HomeDelivery] Current cart before adding:", cart);
|
||||
|
||||
cart[this.deliveryProductId] = {
|
||||
id: this.deliveryProductId,
|
||||
name: this.deliveryProductName,
|
||||
price: this.deliveryProductPrice,
|
||||
qty: 1
|
||||
qty: 1,
|
||||
};
|
||||
console.log('[HomeDelivery] Cart after adding delivery:', cart);
|
||||
console.log("[HomeDelivery] Cart after adding delivery:", cart);
|
||||
this.saveCart(cart);
|
||||
console.log('[HomeDelivery] Delivery product added to localStorage');
|
||||
console.log("[HomeDelivery] Delivery product added to localStorage");
|
||||
},
|
||||
|
||||
removeDeliveryProduct: function() {
|
||||
removeDeliveryProduct: function () {
|
||||
if (!this.deliveryProductId) {
|
||||
console.warn('[HomeDelivery] Delivery product ID not found');
|
||||
console.warn("[HomeDelivery] Delivery product ID not found");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[HomeDelivery] Removing delivery product - deliveryProductId:', this.deliveryProductId, 'orderId:', this.orderId);
|
||||
console.log(
|
||||
"[HomeDelivery] Removing delivery product - deliveryProductId:",
|
||||
this.deliveryProductId,
|
||||
"orderId:",
|
||||
this.orderId
|
||||
);
|
||||
var cart = this.getCart();
|
||||
console.log('[HomeDelivery] Current cart before removing:', cart);
|
||||
console.log("[HomeDelivery] Current cart before removing:", cart);
|
||||
|
||||
if (cart[this.deliveryProductId]) {
|
||||
delete cart[this.deliveryProductId];
|
||||
console.log('[HomeDelivery] Cart after removing delivery:', cart);
|
||||
console.log("[HomeDelivery] Cart after removing delivery:", cart);
|
||||
}
|
||||
this.saveCart(cart);
|
||||
console.log('[HomeDelivery] Delivery product removed from localStorage');
|
||||
console.log("[HomeDelivery] Delivery product removed from localStorage");
|
||||
},
|
||||
|
||||
showDeliveryInfo: function() {
|
||||
var alert = document.getElementById('delivery-info-alert');
|
||||
showDeliveryInfo: function () {
|
||||
var alert = document.getElementById("delivery-info-alert");
|
||||
if (alert) {
|
||||
console.log('[HomeDelivery] Showing delivery info alert');
|
||||
alert.classList.remove('d-none');
|
||||
alert.style.display = 'block';
|
||||
console.log("[HomeDelivery] Showing delivery info alert");
|
||||
alert.classList.remove("d-none");
|
||||
alert.style.display = "block";
|
||||
}
|
||||
},
|
||||
|
||||
hideDeliveryInfo: function() {
|
||||
var alert = document.getElementById('delivery-info-alert');
|
||||
hideDeliveryInfo: function () {
|
||||
var alert = document.getElementById("delivery-info-alert");
|
||||
if (alert) {
|
||||
console.log('[HomeDelivery] Hiding delivery info alert');
|
||||
alert.classList.add('d-none');
|
||||
alert.style.display = 'none';
|
||||
}
|
||||
console.log("[HomeDelivery] Hiding delivery info alert");
|
||||
alert.classList.add("d-none");
|
||||
alert.style.display = "none";
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
HomeDeliveryManager.init();
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@
|
|||
* License AGPL-3.0 or later
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Keep legacy functions as wrappers for backwards compatibility
|
||||
|
||||
/**
|
||||
* DEPRECATED - Use i18nManager.getAll() or i18nManager.get(key) instead
|
||||
*/
|
||||
window.getCheckoutLabels = function(key) {
|
||||
window.getCheckoutLabels = function (key) {
|
||||
if (window.i18nManager && window.i18nManager.initialized) {
|
||||
if (key) {
|
||||
return window.i18nManager.get(key);
|
||||
|
|
@ -38,30 +38,29 @@
|
|||
/**
|
||||
* DEPRECATED - Use i18nManager.getAll() instead
|
||||
*/
|
||||
window.getSearchLabels = function() {
|
||||
window.getSearchLabels = function () {
|
||||
if (window.i18nManager && window.i18nManager.initialized) {
|
||||
return {
|
||||
'searchPlaceholder': window.i18nManager.get('search_products'),
|
||||
'noResults': window.i18nManager.get('no_results')
|
||||
searchPlaceholder: window.i18nManager.get("search_products"),
|
||||
noResults: window.i18nManager.get("no_results"),
|
||||
};
|
||||
}
|
||||
return {
|
||||
'searchPlaceholder': 'Search products...',
|
||||
'noResults': 'No products found'
|
||||
searchPlaceholder: "Search products...",
|
||||
noResults: "No products found",
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* DEPRECATED - Use i18nManager.formatCurrency(amount) instead
|
||||
*/
|
||||
window.formatCurrency = function(amount) {
|
||||
window.formatCurrency = function (amount) {
|
||||
if (window.i18nManager) {
|
||||
return window.i18nManager.formatCurrency(amount);
|
||||
}
|
||||
// Fallback
|
||||
return '€' + parseFloat(amount).toFixed(2);
|
||||
return "€" + parseFloat(amount).toFixed(2);
|
||||
};
|
||||
|
||||
console.log('[i18n_helpers] DEPRECATED - Use i18n_manager.js instead');
|
||||
|
||||
console.log("[i18n_helpers] DEPRECATED - Use i18n_manager.js instead");
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
* License AGPL-3.0 or later
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
window.i18nManager = {
|
||||
labels: null,
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
* Initialize by fetching translations from server
|
||||
* Returns a Promise that resolves when translations are loaded
|
||||
*/
|
||||
init: function() {
|
||||
init: function () {
|
||||
if (this.initialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
@ -38,36 +38,40 @@
|
|||
var self = this;
|
||||
|
||||
// Detect user's language from document or fallback to en_US
|
||||
var detectedLang = document.documentElement.lang || 'es_ES';
|
||||
console.log('[i18nManager] Detected language:', detectedLang);
|
||||
var detectedLang = document.documentElement.lang || "es_ES";
|
||||
console.log("[i18nManager] Detected language:", detectedLang);
|
||||
|
||||
// Fetch translations from server
|
||||
this.initPromise = fetch('/eskaera/i18n', {
|
||||
method: 'POST',
|
||||
this.initPromise = fetch("/eskaera/i18n", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ lang: detectedLang })
|
||||
body: JSON.stringify({ lang: detectedLang }),
|
||||
})
|
||||
.then(function(response) {
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('HTTP error, status = ' + response.status);
|
||||
throw new Error("HTTP error, status = " + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
.then(function (data) {
|
||||
// Handle JSON-RPC response format
|
||||
// Server returns: { "jsonrpc": "2.0", "id": null, "result": { labels } }
|
||||
// Extract the actual labels from the result property
|
||||
var labels = data.result || data;
|
||||
|
||||
console.log('[i18nManager] ✓ Loaded', Object.keys(labels).length, 'translation labels');
|
||||
console.log(
|
||||
"[i18nManager] ✓ Loaded",
|
||||
Object.keys(labels).length,
|
||||
"translation labels"
|
||||
);
|
||||
self.labels = labels;
|
||||
self.initialized = true;
|
||||
return labels;
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('[i18nManager] Error loading translations:', error);
|
||||
.catch(function (error) {
|
||||
console.error("[i18nManager] Error loading translations:", error);
|
||||
// Fallback to empty object so app doesn't crash
|
||||
self.labels = {};
|
||||
self.initialized = true;
|
||||
|
|
@ -81,9 +85,9 @@
|
|||
* Get a specific translation label
|
||||
* Returns the translated string or the key if not found
|
||||
*/
|
||||
get: function(key) {
|
||||
get: function (key) {
|
||||
if (!this.initialized) {
|
||||
console.warn('[i18nManager] Not yet initialized. Call init() first.');
|
||||
console.warn("[i18nManager] Not yet initialized. Call init() first.");
|
||||
return key;
|
||||
}
|
||||
return this.labels[key] || key;
|
||||
|
|
@ -92,9 +96,9 @@
|
|||
/**
|
||||
* Get all translation labels as object
|
||||
*/
|
||||
getAll: function() {
|
||||
getAll: function () {
|
||||
if (!this.initialized) {
|
||||
console.warn('[i18nManager] Not yet initialized. Call init() first.');
|
||||
console.warn("[i18nManager] Not yet initialized. Call init() first.");
|
||||
return {};
|
||||
}
|
||||
return this.labels;
|
||||
|
|
@ -103,7 +107,7 @@
|
|||
/**
|
||||
* Check if a specific label exists
|
||||
*/
|
||||
has: function(key) {
|
||||
has: function (key) {
|
||||
if (!this.initialized) return false;
|
||||
return key in this.labels;
|
||||
},
|
||||
|
|
@ -111,43 +115,42 @@
|
|||
/**
|
||||
* Format currency to Euro format
|
||||
*/
|
||||
formatCurrency: function(amount) {
|
||||
formatCurrency: function (amount) {
|
||||
try {
|
||||
return new Intl.NumberFormat(document.documentElement.lang || 'es_ES', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
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);
|
||||
return "€" + parseFloat(amount).toFixed(2);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Escape HTML to prevent XSS
|
||||
*/
|
||||
escapeHtml: function(text) {
|
||||
if (!text) return '';
|
||||
var div = document.createElement('div');
|
||||
escapeHtml: function (text) {
|
||||
if (!text) return "";
|
||||
var div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Auto-initialize on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
i18nManager.init().catch(function(err) {
|
||||
console.error('[i18nManager] Auto-init failed:', err);
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
i18nManager.init().catch(function (err) {
|
||||
console.error("[i18nManager] Auto-init failed:", err);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// DOM already loaded
|
||||
setTimeout(function() {
|
||||
i18nManager.init().catch(function(err) {
|
||||
console.error('[i18nManager] Auto-init failed:', err);
|
||||
setTimeout(function () {
|
||||
i18nManager.init().catch(function (err) {
|
||||
console.error("[i18nManager] Auto-init failed:", err);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -3,144 +3,174 @@
|
|||
* Tests core cart functionality (add, remove, update, calculate)
|
||||
*/
|
||||
|
||||
odoo.define('website_sale_aplicoop.test_cart_functions', function (require) {
|
||||
'use strict';
|
||||
odoo.define("website_sale_aplicoop.test_cart_functions", function (require) {
|
||||
"use strict";
|
||||
|
||||
var QUnit = window.QUnit;
|
||||
|
||||
QUnit.module('website_sale_aplicoop', {
|
||||
beforeEach: function() {
|
||||
QUnit.module(
|
||||
"website_sale_aplicoop",
|
||||
{
|
||||
beforeEach: function () {
|
||||
// Setup: Initialize groupOrderShop object
|
||||
window.groupOrderShop = {
|
||||
orderId: '1',
|
||||
orderId: "1",
|
||||
cart: {},
|
||||
labels: {
|
||||
'save_cart': 'Save Cart',
|
||||
'reload_cart': 'Reload Cart',
|
||||
'checkout': 'Checkout',
|
||||
'confirm_order': 'Confirm Order',
|
||||
'back_to_cart': 'Back to Cart'
|
||||
}
|
||||
save_cart: "Save Cart",
|
||||
reload_cart: "Reload Cart",
|
||||
checkout: "Checkout",
|
||||
confirm_order: "Confirm Order",
|
||||
back_to_cart: "Back to Cart",
|
||||
},
|
||||
};
|
||||
|
||||
// Clear localStorage
|
||||
localStorage.clear();
|
||||
},
|
||||
afterEach: function() {
|
||||
afterEach: function () {
|
||||
// Cleanup
|
||||
localStorage.clear();
|
||||
delete window.groupOrderShop;
|
||||
}
|
||||
}, function() {
|
||||
|
||||
QUnit.test('groupOrderShop object initializes correctly', function(assert) {
|
||||
},
|
||||
},
|
||||
function () {
|
||||
QUnit.test("groupOrderShop object initializes correctly", function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
assert.ok(window.groupOrderShop, 'groupOrderShop object exists');
|
||||
assert.equal(window.groupOrderShop.orderId, '1', 'orderId is set');
|
||||
assert.ok(typeof window.groupOrderShop.cart === 'object', 'cart is an object');
|
||||
assert.ok(window.groupOrderShop, "groupOrderShop object exists");
|
||||
assert.equal(window.groupOrderShop.orderId, "1", "orderId is set");
|
||||
assert.ok(typeof window.groupOrderShop.cart === "object", "cart is an object");
|
||||
});
|
||||
|
||||
QUnit.test('cart starts empty', function(assert) {
|
||||
QUnit.test("cart starts empty", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var cartKeys = Object.keys(window.groupOrderShop.cart);
|
||||
assert.equal(cartKeys.length, 0, 'cart has no items initially');
|
||||
assert.equal(cartKeys.length, 0, "cart has no items initially");
|
||||
});
|
||||
|
||||
QUnit.test('can add item to cart', function(assert) {
|
||||
QUnit.test("can add item to cart", function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
// Add a product to cart
|
||||
var productId = '123';
|
||||
var productId = "123";
|
||||
var productData = {
|
||||
name: 'Test Product',
|
||||
price: 10.50,
|
||||
quantity: 2
|
||||
name: "Test Product",
|
||||
price: 10.5,
|
||||
quantity: 2,
|
||||
};
|
||||
|
||||
window.groupOrderShop.cart[productId] = productData;
|
||||
|
||||
assert.equal(Object.keys(window.groupOrderShop.cart).length, 1, 'cart has 1 item');
|
||||
assert.ok(window.groupOrderShop.cart[productId], 'product exists in cart');
|
||||
assert.equal(window.groupOrderShop.cart[productId].name, 'Test Product', 'product name is correct');
|
||||
assert.equal(window.groupOrderShop.cart[productId].quantity, 2, 'product quantity is correct');
|
||||
assert.equal(Object.keys(window.groupOrderShop.cart).length, 1, "cart has 1 item");
|
||||
assert.ok(window.groupOrderShop.cart[productId], "product exists in cart");
|
||||
assert.equal(
|
||||
window.groupOrderShop.cart[productId].name,
|
||||
"Test Product",
|
||||
"product name is correct"
|
||||
);
|
||||
assert.equal(
|
||||
window.groupOrderShop.cart[productId].quantity,
|
||||
2,
|
||||
"product quantity is correct"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('can remove item from cart', function(assert) {
|
||||
QUnit.test("can remove item from cart", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// Add then remove
|
||||
var productId = '123';
|
||||
var productId = "123";
|
||||
window.groupOrderShop.cart[productId] = {
|
||||
name: 'Test Product',
|
||||
price: 10.50,
|
||||
quantity: 2
|
||||
name: "Test Product",
|
||||
price: 10.5,
|
||||
quantity: 2,
|
||||
};
|
||||
|
||||
assert.equal(Object.keys(window.groupOrderShop.cart).length, 1, 'cart has 1 item after add');
|
||||
assert.equal(
|
||||
Object.keys(window.groupOrderShop.cart).length,
|
||||
1,
|
||||
"cart has 1 item after add"
|
||||
);
|
||||
|
||||
delete window.groupOrderShop.cart[productId];
|
||||
|
||||
assert.equal(Object.keys(window.groupOrderShop.cart).length, 0, 'cart is empty after remove');
|
||||
assert.equal(
|
||||
Object.keys(window.groupOrderShop.cart).length,
|
||||
0,
|
||||
"cart is empty after remove"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('can update item quantity', function(assert) {
|
||||
QUnit.test("can update item quantity", function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var productId = '123';
|
||||
var productId = "123";
|
||||
window.groupOrderShop.cart[productId] = {
|
||||
name: 'Test Product',
|
||||
price: 10.50,
|
||||
quantity: 2
|
||||
name: "Test Product",
|
||||
price: 10.5,
|
||||
quantity: 2,
|
||||
};
|
||||
|
||||
assert.equal(window.groupOrderShop.cart[productId].quantity, 2, 'initial quantity is 2');
|
||||
assert.equal(
|
||||
window.groupOrderShop.cart[productId].quantity,
|
||||
2,
|
||||
"initial quantity is 2"
|
||||
);
|
||||
|
||||
// Update quantity
|
||||
window.groupOrderShop.cart[productId].quantity = 5;
|
||||
|
||||
assert.equal(window.groupOrderShop.cart[productId].quantity, 5, 'quantity updated to 5');
|
||||
assert.equal(Object.keys(window.groupOrderShop.cart).length, 1, 'still only 1 item in cart');
|
||||
assert.equal(
|
||||
window.groupOrderShop.cart[productId].quantity,
|
||||
5,
|
||||
"quantity updated to 5"
|
||||
);
|
||||
assert.equal(
|
||||
Object.keys(window.groupOrderShop.cart).length,
|
||||
1,
|
||||
"still only 1 item in cart"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('cart total calculates correctly', function(assert) {
|
||||
QUnit.test("cart total calculates correctly", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
// Add multiple products
|
||||
window.groupOrderShop.cart['123'] = {
|
||||
name: 'Product 1',
|
||||
price: 10.00,
|
||||
quantity: 2
|
||||
window.groupOrderShop.cart["123"] = {
|
||||
name: "Product 1",
|
||||
price: 10.0,
|
||||
quantity: 2,
|
||||
};
|
||||
|
||||
window.groupOrderShop.cart['456'] = {
|
||||
name: 'Product 2',
|
||||
price: 5.50,
|
||||
quantity: 3
|
||||
window.groupOrderShop.cart["456"] = {
|
||||
name: "Product 2",
|
||||
price: 5.5,
|
||||
quantity: 3,
|
||||
};
|
||||
|
||||
// Calculate total manually
|
||||
var total = 0;
|
||||
Object.keys(window.groupOrderShop.cart).forEach(function(productId) {
|
||||
Object.keys(window.groupOrderShop.cart).forEach(function (productId) {
|
||||
var item = window.groupOrderShop.cart[productId];
|
||||
total += item.price * item.quantity;
|
||||
});
|
||||
|
||||
// Expected: (10.00 * 2) + (5.50 * 3) = 20.00 + 16.50 = 36.50
|
||||
assert.equal(total.toFixed(2), '36.50', 'cart total is correct');
|
||||
assert.equal(total.toFixed(2), "36.50", "cart total is correct");
|
||||
});
|
||||
|
||||
QUnit.test('localStorage saves cart correctly', function(assert) {
|
||||
QUnit.test("localStorage saves cart correctly", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var cartKey = 'eskaera_1_cart';
|
||||
var cartKey = "eskaera_1_cart";
|
||||
var testCart = {
|
||||
'123': {
|
||||
name: 'Test Product',
|
||||
price: 10.50,
|
||||
quantity: 2
|
||||
}
|
||||
123: {
|
||||
name: "Test Product",
|
||||
price: 10.5,
|
||||
quantity: 2,
|
||||
},
|
||||
};
|
||||
|
||||
// Save to localStorage
|
||||
|
|
@ -149,76 +179,97 @@ odoo.define('website_sale_aplicoop.test_cart_functions', function (require) {
|
|||
// Retrieve and verify
|
||||
var savedCart = JSON.parse(localStorage.getItem(cartKey));
|
||||
|
||||
assert.ok(savedCart, 'cart was saved to localStorage');
|
||||
assert.equal(savedCart['123'].name, 'Test Product', 'cart data is correct');
|
||||
assert.ok(savedCart, "cart was saved to localStorage");
|
||||
assert.equal(savedCart["123"].name, "Test Product", "cart data is correct");
|
||||
});
|
||||
|
||||
QUnit.test('labels object is initialized', function(assert) {
|
||||
QUnit.test("labels object is initialized", function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
assert.ok(window.groupOrderShop.labels, 'labels object exists');
|
||||
assert.equal(window.groupOrderShop.labels['save_cart'], 'Save Cart', 'save_cart label exists');
|
||||
assert.equal(window.groupOrderShop.labels['reload_cart'], 'Reload Cart', 'reload_cart label exists');
|
||||
assert.equal(window.groupOrderShop.labels['checkout'], 'Checkout', 'checkout label exists');
|
||||
assert.equal(window.groupOrderShop.labels['confirm_order'], 'Confirm Order', 'confirm_order label exists');
|
||||
assert.ok(window.groupOrderShop.labels, "labels object exists");
|
||||
assert.equal(
|
||||
window.groupOrderShop.labels["save_cart"],
|
||||
"Save Cart",
|
||||
"save_cart label exists"
|
||||
);
|
||||
assert.equal(
|
||||
window.groupOrderShop.labels["reload_cart"],
|
||||
"Reload Cart",
|
||||
"reload_cart label exists"
|
||||
);
|
||||
assert.equal(
|
||||
window.groupOrderShop.labels["checkout"],
|
||||
"Checkout",
|
||||
"checkout label exists"
|
||||
);
|
||||
assert.equal(
|
||||
window.groupOrderShop.labels["confirm_order"],
|
||||
"Confirm Order",
|
||||
"confirm_order label exists"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('cart handles decimal quantities correctly', function(assert) {
|
||||
QUnit.test("cart handles decimal quantities correctly", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
window.groupOrderShop.cart['123'] = {
|
||||
name: 'Weight Product',
|
||||
window.groupOrderShop.cart["123"] = {
|
||||
name: "Weight Product",
|
||||
price: 8.99,
|
||||
quantity: 1.5
|
||||
quantity: 1.5,
|
||||
};
|
||||
|
||||
var item = window.groupOrderShop.cart['123'];
|
||||
var item = window.groupOrderShop.cart["123"];
|
||||
var subtotal = item.price * item.quantity;
|
||||
|
||||
assert.equal(item.quantity, 1.5, 'decimal quantity stored correctly');
|
||||
assert.equal(subtotal.toFixed(2), '13.49', 'subtotal with decimal quantity is correct');
|
||||
assert.equal(item.quantity, 1.5, "decimal quantity stored correctly");
|
||||
assert.equal(
|
||||
subtotal.toFixed(2),
|
||||
"13.49",
|
||||
"subtotal with decimal quantity is correct"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('cart handles zero quantity', function(assert) {
|
||||
QUnit.test("cart handles zero quantity", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
window.groupOrderShop.cart['123'] = {
|
||||
name: 'Test Product',
|
||||
price: 10.00,
|
||||
quantity: 0
|
||||
window.groupOrderShop.cart["123"] = {
|
||||
name: "Test Product",
|
||||
price: 10.0,
|
||||
quantity: 0,
|
||||
};
|
||||
|
||||
var item = window.groupOrderShop.cart['123'];
|
||||
var item = window.groupOrderShop.cart["123"];
|
||||
var subtotal = item.price * item.quantity;
|
||||
|
||||
assert.equal(subtotal, 0, 'zero quantity results in zero subtotal');
|
||||
assert.equal(subtotal, 0, "zero quantity results in zero subtotal");
|
||||
});
|
||||
|
||||
QUnit.test('cart handles multiple items with same price', function(assert) {
|
||||
QUnit.test("cart handles multiple items with same price", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
window.groupOrderShop.cart['123'] = {
|
||||
name: 'Product A',
|
||||
price: 10.00,
|
||||
quantity: 2
|
||||
window.groupOrderShop.cart["123"] = {
|
||||
name: "Product A",
|
||||
price: 10.0,
|
||||
quantity: 2,
|
||||
};
|
||||
|
||||
window.groupOrderShop.cart['456'] = {
|
||||
name: 'Product B',
|
||||
price: 10.00,
|
||||
quantity: 3
|
||||
window.groupOrderShop.cart["456"] = {
|
||||
name: "Product B",
|
||||
price: 10.0,
|
||||
quantity: 3,
|
||||
};
|
||||
|
||||
var total = 0;
|
||||
Object.keys(window.groupOrderShop.cart).forEach(function(productId) {
|
||||
Object.keys(window.groupOrderShop.cart).forEach(function (productId) {
|
||||
var item = window.groupOrderShop.cart[productId];
|
||||
total += item.price * item.quantity;
|
||||
});
|
||||
|
||||
assert.equal(Object.keys(window.groupOrderShop.cart).length, 2, 'cart has 2 items');
|
||||
assert.equal(total.toFixed(2), '50.00', 'total is correct with same prices');
|
||||
});
|
||||
assert.equal(Object.keys(window.groupOrderShop.cart).length, 2, "cart has 2 items");
|
||||
assert.equal(total.toFixed(2), "50.00", "total is correct with same prices");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,15 +3,17 @@
|
|||
* Tests product filtering and search behavior
|
||||
*/
|
||||
|
||||
odoo.define('website_sale_aplicoop.test_realtime_search', function (require) {
|
||||
'use strict';
|
||||
odoo.define("website_sale_aplicoop.test_realtime_search", function (require) {
|
||||
"use strict";
|
||||
|
||||
var QUnit = window.QUnit;
|
||||
|
||||
QUnit.module('website_sale_aplicoop.realtime_search', {
|
||||
beforeEach: function() {
|
||||
QUnit.module(
|
||||
"website_sale_aplicoop.realtime_search",
|
||||
{
|
||||
beforeEach: function () {
|
||||
// Setup: Create test DOM with product cards
|
||||
this.$fixture = $('#qunit-fixture');
|
||||
this.$fixture = $("#qunit-fixture");
|
||||
|
||||
this.$fixture.append(
|
||||
'<input type="text" id="realtime-search-input" />' +
|
||||
|
|
@ -19,7 +21,7 @@ odoo.define('website_sale_aplicoop.test_realtime_search', function (require) {
|
|||
'<option value="">All Categories</option>' +
|
||||
'<option value="1">Category 1</option>' +
|
||||
'<option value="2">Category 2</option>' +
|
||||
'</select>' +
|
||||
"</select>" +
|
||||
'<div class="product-card" data-product-name="Cabbage" data-category-id="1"></div>' +
|
||||
'<div class="product-card" data-product-name="Carrot" data-category-id="1"></div>' +
|
||||
'<div class="product-card" data-product-name="Apple" data-category-id="2"></div>' +
|
||||
|
|
@ -28,214 +30,220 @@ odoo.define('website_sale_aplicoop.test_realtime_search', function (require) {
|
|||
|
||||
// Initialize search object
|
||||
window.realtimeSearch = {
|
||||
searchInput: document.getElementById('realtime-search-input'),
|
||||
categorySelect: document.getElementById('realtime-category-select'),
|
||||
productCards: document.querySelectorAll('.product-card'),
|
||||
searchInput: document.getElementById("realtime-search-input"),
|
||||
categorySelect: document.getElementById("realtime-category-select"),
|
||||
productCards: document.querySelectorAll(".product-card"),
|
||||
|
||||
filterProducts: function() {
|
||||
filterProducts: function () {
|
||||
var searchTerm = this.searchInput.value.toLowerCase().trim();
|
||||
var selectedCategory = this.categorySelect.value;
|
||||
|
||||
var visibleCount = 0;
|
||||
var hiddenCount = 0;
|
||||
|
||||
this.productCards.forEach(function(card) {
|
||||
var productName = card.getAttribute('data-product-name').toLowerCase();
|
||||
var categoryId = card.getAttribute('data-category-id');
|
||||
this.productCards.forEach(function (card) {
|
||||
var productName = card.getAttribute("data-product-name").toLowerCase();
|
||||
var categoryId = card.getAttribute("data-category-id");
|
||||
|
||||
var matchesSearch = !searchTerm || productName.includes(searchTerm);
|
||||
var matchesCategory = !selectedCategory || categoryId === selectedCategory;
|
||||
var matchesCategory =
|
||||
!selectedCategory || categoryId === selectedCategory;
|
||||
|
||||
if (matchesSearch && matchesCategory) {
|
||||
card.classList.remove('d-none');
|
||||
card.classList.remove("d-none");
|
||||
visibleCount++;
|
||||
} else {
|
||||
card.classList.add('d-none');
|
||||
card.classList.add("d-none");
|
||||
hiddenCount++;
|
||||
}
|
||||
});
|
||||
|
||||
return { visible: visibleCount, hidden: hiddenCount };
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
afterEach: function() {
|
||||
afterEach: function () {
|
||||
// Cleanup
|
||||
this.$fixture.empty();
|
||||
delete window.realtimeSearch;
|
||||
}
|
||||
}, function() {
|
||||
|
||||
QUnit.test('search input element exists', function(assert) {
|
||||
},
|
||||
},
|
||||
function () {
|
||||
QUnit.test("search input element exists", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var searchInput = document.getElementById('realtime-search-input');
|
||||
assert.ok(searchInput, 'search input element exists');
|
||||
var searchInput = document.getElementById("realtime-search-input");
|
||||
assert.ok(searchInput, "search input element exists");
|
||||
});
|
||||
|
||||
QUnit.test('category select element exists', function(assert) {
|
||||
QUnit.test("category select element exists", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var categorySelect = document.getElementById('realtime-category-select');
|
||||
assert.ok(categorySelect, 'category select element exists');
|
||||
var categorySelect = document.getElementById("realtime-category-select");
|
||||
assert.ok(categorySelect, "category select element exists");
|
||||
});
|
||||
|
||||
QUnit.test('product cards are found', function(assert) {
|
||||
QUnit.test("product cards are found", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var productCards = document.querySelectorAll('.product-card');
|
||||
assert.equal(productCards.length, 4, 'found 4 product cards');
|
||||
var productCards = document.querySelectorAll(".product-card");
|
||||
assert.equal(productCards.length, 4, "found 4 product cards");
|
||||
});
|
||||
|
||||
QUnit.test('search filters by product name', function(assert) {
|
||||
QUnit.test("search filters by product name", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// Search for "cab"
|
||||
window.realtimeSearch.searchInput.value = 'cab';
|
||||
window.realtimeSearch.searchInput.value = "cab";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
assert.equal(result.visible, 1, '1 product visible (Cabbage)');
|
||||
assert.equal(result.hidden, 3, '3 products hidden');
|
||||
assert.equal(result.visible, 1, "1 product visible (Cabbage)");
|
||||
assert.equal(result.hidden, 3, "3 products hidden");
|
||||
});
|
||||
|
||||
QUnit.test('search is case insensitive', function(assert) {
|
||||
QUnit.test("search is case insensitive", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// Search for "CARROT" in uppercase
|
||||
window.realtimeSearch.searchInput.value = 'CARROT';
|
||||
window.realtimeSearch.searchInput.value = "CARROT";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
assert.equal(result.visible, 1, '1 product visible (Carrot)');
|
||||
assert.equal(result.hidden, 3, '3 products hidden');
|
||||
assert.equal(result.visible, 1, "1 product visible (Carrot)");
|
||||
assert.equal(result.hidden, 3, "3 products hidden");
|
||||
});
|
||||
|
||||
QUnit.test('empty search shows all products', function(assert) {
|
||||
QUnit.test("empty search shows all products", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
window.realtimeSearch.searchInput.value = '';
|
||||
window.realtimeSearch.searchInput.value = "";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
assert.equal(result.visible, 4, 'all 4 products visible');
|
||||
assert.equal(result.hidden, 0, 'no products hidden');
|
||||
assert.equal(result.visible, 4, "all 4 products visible");
|
||||
assert.equal(result.hidden, 0, "no products hidden");
|
||||
});
|
||||
|
||||
QUnit.test('category filter works', function(assert) {
|
||||
QUnit.test("category filter works", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// Select category 1
|
||||
window.realtimeSearch.categorySelect.value = '1';
|
||||
window.realtimeSearch.categorySelect.value = "1";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
assert.equal(result.visible, 2, '2 products visible (Cabbage, Carrot)');
|
||||
assert.equal(result.hidden, 2, '2 products hidden (Apple, Banana)');
|
||||
assert.equal(result.visible, 2, "2 products visible (Cabbage, Carrot)");
|
||||
assert.equal(result.hidden, 2, "2 products hidden (Apple, Banana)");
|
||||
});
|
||||
|
||||
QUnit.test('search and category filter work together', function(assert) {
|
||||
QUnit.test("search and category filter work together", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// Search for "ca" in category 1
|
||||
window.realtimeSearch.searchInput.value = 'ca';
|
||||
window.realtimeSearch.categorySelect.value = '1';
|
||||
window.realtimeSearch.searchInput.value = "ca";
|
||||
window.realtimeSearch.categorySelect.value = "1";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
// Should show: Cabbage, Carrot (both in category 1 and match "ca")
|
||||
assert.equal(result.visible, 2, '2 products visible');
|
||||
assert.equal(result.hidden, 2, '2 products hidden');
|
||||
assert.equal(result.visible, 2, "2 products visible");
|
||||
assert.equal(result.hidden, 2, "2 products hidden");
|
||||
});
|
||||
|
||||
QUnit.test('search for non-existent product shows none', function(assert) {
|
||||
QUnit.test("search for non-existent product shows none", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
window.realtimeSearch.searchInput.value = 'xyz123';
|
||||
window.realtimeSearch.searchInput.value = "xyz123";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
assert.equal(result.visible, 0, 'no products visible');
|
||||
assert.equal(result.hidden, 4, 'all 4 products hidden');
|
||||
assert.equal(result.visible, 0, "no products visible");
|
||||
assert.equal(result.hidden, 4, "all 4 products hidden");
|
||||
});
|
||||
|
||||
QUnit.test('partial match works', function(assert) {
|
||||
QUnit.test("partial match works", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// Search for "an" should match "Banana"
|
||||
window.realtimeSearch.searchInput.value = 'an';
|
||||
window.realtimeSearch.searchInput.value = "an";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
assert.equal(result.visible, 1, '1 product visible (Banana)');
|
||||
assert.equal(result.hidden, 3, '3 products hidden');
|
||||
assert.equal(result.visible, 1, "1 product visible (Banana)");
|
||||
assert.equal(result.hidden, 3, "3 products hidden");
|
||||
});
|
||||
|
||||
QUnit.test('search trims whitespace', function(assert) {
|
||||
QUnit.test("search trims whitespace", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// Search with extra whitespace
|
||||
window.realtimeSearch.searchInput.value = ' apple ';
|
||||
window.realtimeSearch.searchInput.value = " apple ";
|
||||
var result = window.realtimeSearch.filterProducts();
|
||||
|
||||
assert.equal(result.visible, 1, '1 product visible (Apple)');
|
||||
assert.equal(result.hidden, 3, '3 products hidden');
|
||||
assert.equal(result.visible, 1, "1 product visible (Apple)");
|
||||
assert.equal(result.hidden, 3, "3 products hidden");
|
||||
});
|
||||
|
||||
QUnit.test('d-none class is added to hidden products', function(assert) {
|
||||
QUnit.test("d-none class is added to hidden products", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
window.realtimeSearch.searchInput.value = 'cabbage';
|
||||
window.realtimeSearch.searchInput.value = "cabbage";
|
||||
window.realtimeSearch.filterProducts();
|
||||
|
||||
var productCards = document.querySelectorAll('.product-card');
|
||||
var hiddenCards = Array.from(productCards).filter(function(card) {
|
||||
return card.classList.contains('d-none');
|
||||
var productCards = document.querySelectorAll(".product-card");
|
||||
var hiddenCards = Array.from(productCards).filter(function (card) {
|
||||
return card.classList.contains("d-none");
|
||||
});
|
||||
|
||||
assert.equal(hiddenCards.length, 3, '3 cards have d-none class');
|
||||
assert.equal(hiddenCards.length, 3, "3 cards have d-none class");
|
||||
});
|
||||
|
||||
QUnit.test('d-none class is removed from visible products', function(assert) {
|
||||
QUnit.test("d-none class is removed from visible products", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// First hide all
|
||||
window.realtimeSearch.searchInput.value = 'xyz';
|
||||
window.realtimeSearch.searchInput.value = "xyz";
|
||||
window.realtimeSearch.filterProducts();
|
||||
|
||||
var allHidden = Array.from(window.realtimeSearch.productCards).every(function(card) {
|
||||
return card.classList.contains('d-none');
|
||||
var allHidden = Array.from(window.realtimeSearch.productCards).every(function (
|
||||
card
|
||||
) {
|
||||
return card.classList.contains("d-none");
|
||||
});
|
||||
assert.ok(allHidden, 'all cards hidden initially');
|
||||
assert.ok(allHidden, "all cards hidden initially");
|
||||
|
||||
// Then show all
|
||||
window.realtimeSearch.searchInput.value = '';
|
||||
window.realtimeSearch.searchInput.value = "";
|
||||
window.realtimeSearch.filterProducts();
|
||||
|
||||
var allVisible = Array.from(window.realtimeSearch.productCards).every(function(card) {
|
||||
return !card.classList.contains('d-none');
|
||||
var allVisible = Array.from(window.realtimeSearch.productCards).every(function (
|
||||
card
|
||||
) {
|
||||
return !card.classList.contains("d-none");
|
||||
});
|
||||
assert.ok(allVisible, 'all cards visible after clearing search');
|
||||
assert.ok(allVisible, "all cards visible after clearing search");
|
||||
});
|
||||
|
||||
QUnit.test('filterProducts returns correct counts', function(assert) {
|
||||
QUnit.test("filterProducts returns correct counts", function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
// All visible
|
||||
window.realtimeSearch.searchInput.value = '';
|
||||
window.realtimeSearch.searchInput.value = "";
|
||||
var result1 = window.realtimeSearch.filterProducts();
|
||||
assert.equal(result1.visible + result1.hidden, 4, 'total count is 4');
|
||||
assert.equal(result1.visible + result1.hidden, 4, "total count is 4");
|
||||
|
||||
// 1 visible
|
||||
window.realtimeSearch.searchInput.value = 'apple';
|
||||
window.realtimeSearch.searchInput.value = "apple";
|
||||
var result2 = window.realtimeSearch.filterProducts();
|
||||
assert.equal(result2.visible, 1, 'visible count is 1');
|
||||
assert.equal(result2.visible, 1, "visible count is 1");
|
||||
|
||||
// None visible
|
||||
window.realtimeSearch.searchInput.value = 'xyz';
|
||||
window.realtimeSearch.searchInput.value = "xyz";
|
||||
var result3 = window.realtimeSearch.filterProducts();
|
||||
assert.equal(result3.visible, 0, 'visible count is 0');
|
||||
assert.equal(result3.visible, 0, "visible count is 0");
|
||||
|
||||
// Category filter
|
||||
window.realtimeSearch.searchInput.value = '';
|
||||
window.realtimeSearch.categorySelect.value = '2';
|
||||
window.realtimeSearch.searchInput.value = "";
|
||||
window.realtimeSearch.categorySelect.value = "2";
|
||||
var result4 = window.realtimeSearch.filterProducts();
|
||||
assert.equal(result4.visible, 2, 'category filter shows 2 products');
|
||||
});
|
||||
assert.equal(result4.visible, 2, "category filter shows 2 products");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
odoo.define('website_sale_aplicoop.test_suite', function (require) {
|
||||
'use strict';
|
||||
odoo.define("website_sale_aplicoop.test_suite", function (require) {
|
||||
"use strict";
|
||||
|
||||
// Import all test modules
|
||||
require('website_sale_aplicoop.test_cart_functions');
|
||||
require('website_sale_aplicoop.test_tooltips_labels');
|
||||
require('website_sale_aplicoop.test_realtime_search');
|
||||
require("website_sale_aplicoop.test_cart_functions");
|
||||
require("website_sale_aplicoop.test_tooltips_labels");
|
||||
require("website_sale_aplicoop.test_realtime_search");
|
||||
|
||||
// Test suite is automatically registered by importing modules
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,15 +3,17 @@
|
|||
* Tests tooltip initialization and label loading
|
||||
*/
|
||||
|
||||
odoo.define('website_sale_aplicoop.test_tooltips_labels', function (require) {
|
||||
'use strict';
|
||||
odoo.define("website_sale_aplicoop.test_tooltips_labels", function (require) {
|
||||
"use strict";
|
||||
|
||||
var QUnit = window.QUnit;
|
||||
|
||||
QUnit.module('website_sale_aplicoop.tooltips_labels', {
|
||||
beforeEach: function() {
|
||||
QUnit.module(
|
||||
"website_sale_aplicoop.tooltips_labels",
|
||||
{
|
||||
beforeEach: function () {
|
||||
// Setup: Create test DOM elements
|
||||
this.$fixture = $('#qunit-fixture');
|
||||
this.$fixture = $("#qunit-fixture");
|
||||
|
||||
// Add test buttons with tooltip labels
|
||||
this.$fixture.append(
|
||||
|
|
@ -22,166 +24,193 @@ odoo.define('website_sale_aplicoop.test_tooltips_labels', function (require) {
|
|||
|
||||
// Initialize groupOrderShop
|
||||
window.groupOrderShop = {
|
||||
orderId: '1',
|
||||
orderId: "1",
|
||||
cart: {},
|
||||
labels: {
|
||||
'save_cart': 'Guardar Carrito',
|
||||
'reload_cart': 'Recargar Carrito',
|
||||
'checkout': 'Proceder al Pago',
|
||||
'confirm_order': 'Confirmar Pedido',
|
||||
'back_to_cart': 'Volver al Carrito'
|
||||
save_cart: "Guardar Carrito",
|
||||
reload_cart: "Recargar Carrito",
|
||||
checkout: "Proceder al Pago",
|
||||
confirm_order: "Confirmar Pedido",
|
||||
back_to_cart: "Volver al Carrito",
|
||||
},
|
||||
_initTooltips: function() {
|
||||
_initTooltips: function () {
|
||||
var labels = window.groupOrderShop.labels || this.labels || {};
|
||||
var tooltipElements = document.querySelectorAll('[data-tooltip-label]');
|
||||
var tooltipElements = document.querySelectorAll("[data-tooltip-label]");
|
||||
|
||||
tooltipElements.forEach(function(el) {
|
||||
var labelKey = el.getAttribute('data-tooltip-label');
|
||||
tooltipElements.forEach(function (el) {
|
||||
var labelKey = el.getAttribute("data-tooltip-label");
|
||||
if (labelKey && labels[labelKey]) {
|
||||
el.setAttribute('title', labels[labelKey]);
|
||||
el.setAttribute("title", labels[labelKey]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
afterEach: function() {
|
||||
afterEach: function () {
|
||||
// Cleanup
|
||||
this.$fixture.empty();
|
||||
delete window.groupOrderShop;
|
||||
}
|
||||
}, function() {
|
||||
|
||||
QUnit.test('tooltips are initialized from labels', function(assert) {
|
||||
},
|
||||
},
|
||||
function () {
|
||||
QUnit.test("tooltips are initialized from labels", function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
// Initialize tooltips
|
||||
window.groupOrderShop._initTooltips();
|
||||
|
||||
var btn1 = document.getElementById('test-btn-1');
|
||||
var btn2 = document.getElementById('test-btn-2');
|
||||
var btn3 = document.getElementById('test-btn-3');
|
||||
var btn1 = document.getElementById("test-btn-1");
|
||||
var btn2 = document.getElementById("test-btn-2");
|
||||
var btn3 = document.getElementById("test-btn-3");
|
||||
|
||||
assert.equal(btn1.getAttribute('title'), 'Guardar Carrito', 'save_cart tooltip is correct');
|
||||
assert.equal(btn2.getAttribute('title'), 'Proceder al Pago', 'checkout tooltip is correct');
|
||||
assert.equal(btn3.getAttribute('title'), 'Recargar Carrito', 'reload_cart tooltip is correct');
|
||||
assert.equal(
|
||||
btn1.getAttribute("title"),
|
||||
"Guardar Carrito",
|
||||
"save_cart tooltip is correct"
|
||||
);
|
||||
assert.equal(
|
||||
btn2.getAttribute("title"),
|
||||
"Proceder al Pago",
|
||||
"checkout tooltip is correct"
|
||||
);
|
||||
assert.equal(
|
||||
btn3.getAttribute("title"),
|
||||
"Recargar Carrito",
|
||||
"reload_cart tooltip is correct"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('tooltips handle missing labels gracefully', function(assert) {
|
||||
QUnit.test("tooltips handle missing labels gracefully", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
// Add button with non-existent label
|
||||
this.$fixture.append('<button id="test-btn-4" data-tooltip-label="non_existent">Test</button>');
|
||||
this.$fixture.append(
|
||||
'<button id="test-btn-4" data-tooltip-label="non_existent">Test</button>'
|
||||
);
|
||||
|
||||
window.groupOrderShop._initTooltips();
|
||||
|
||||
var btn4 = document.getElementById('test-btn-4');
|
||||
var title = btn4.getAttribute('title');
|
||||
var btn4 = document.getElementById("test-btn-4");
|
||||
var title = btn4.getAttribute("title");
|
||||
|
||||
// Should be null or empty since label doesn't exist
|
||||
assert.ok(!title || title === '', 'missing label does not set tooltip');
|
||||
assert.ok(!title || title === "", "missing label does not set tooltip");
|
||||
});
|
||||
|
||||
QUnit.test('labels object contains expected keys', function(assert) {
|
||||
QUnit.test("labels object contains expected keys", function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var labels = window.groupOrderShop.labels;
|
||||
|
||||
assert.ok('save_cart' in labels, 'has save_cart label');
|
||||
assert.ok('reload_cart' in labels, 'has reload_cart label');
|
||||
assert.ok('checkout' in labels, 'has checkout label');
|
||||
assert.ok('confirm_order' in labels, 'has confirm_order label');
|
||||
assert.ok('back_to_cart' in labels, 'has back_to_cart label');
|
||||
assert.ok("save_cart" in labels, "has save_cart label");
|
||||
assert.ok("reload_cart" in labels, "has reload_cart label");
|
||||
assert.ok("checkout" in labels, "has checkout label");
|
||||
assert.ok("confirm_order" in labels, "has confirm_order label");
|
||||
assert.ok("back_to_cart" in labels, "has back_to_cart label");
|
||||
});
|
||||
|
||||
QUnit.test('labels are strings', function(assert) {
|
||||
QUnit.test("labels are strings", function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var labels = window.groupOrderShop.labels;
|
||||
|
||||
assert.equal(typeof labels.save_cart, 'string', 'save_cart is string');
|
||||
assert.equal(typeof labels.reload_cart, 'string', 'reload_cart is string');
|
||||
assert.equal(typeof labels.checkout, 'string', 'checkout is string');
|
||||
assert.equal(typeof labels.confirm_order, 'string', 'confirm_order is string');
|
||||
assert.equal(typeof labels.back_to_cart, 'string', 'back_to_cart is string');
|
||||
assert.equal(typeof labels.save_cart, "string", "save_cart is string");
|
||||
assert.equal(typeof labels.reload_cart, "string", "reload_cart is string");
|
||||
assert.equal(typeof labels.checkout, "string", "checkout is string");
|
||||
assert.equal(typeof labels.confirm_order, "string", "confirm_order is string");
|
||||
assert.equal(typeof labels.back_to_cart, "string", "back_to_cart is string");
|
||||
});
|
||||
|
||||
QUnit.test('_initTooltips uses window.groupOrderShop.labels', function(assert) {
|
||||
QUnit.test("_initTooltips uses window.groupOrderShop.labels", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
// Update global labels
|
||||
window.groupOrderShop.labels = {
|
||||
'save_cart': 'Updated Label',
|
||||
'checkout': 'Updated Checkout',
|
||||
'reload_cart': 'Updated Reload'
|
||||
save_cart: "Updated Label",
|
||||
checkout: "Updated Checkout",
|
||||
reload_cart: "Updated Reload",
|
||||
};
|
||||
|
||||
window.groupOrderShop._initTooltips();
|
||||
|
||||
var btn1 = document.getElementById('test-btn-1');
|
||||
assert.equal(btn1.getAttribute('title'), 'Updated Label', 'uses updated global labels');
|
||||
var btn1 = document.getElementById("test-btn-1");
|
||||
assert.equal(
|
||||
btn1.getAttribute("title"),
|
||||
"Updated Label",
|
||||
"uses updated global labels"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('tooltips can be reinitialized', function(assert) {
|
||||
QUnit.test("tooltips can be reinitialized", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// First initialization
|
||||
window.groupOrderShop._initTooltips();
|
||||
var btn1 = document.getElementById('test-btn-1');
|
||||
assert.equal(btn1.getAttribute('title'), 'Guardar Carrito', 'first init correct');
|
||||
var btn1 = document.getElementById("test-btn-1");
|
||||
assert.equal(btn1.getAttribute("title"), "Guardar Carrito", "first init correct");
|
||||
|
||||
// Update labels and reinitialize
|
||||
window.groupOrderShop.labels.save_cart = 'New Translation';
|
||||
window.groupOrderShop.labels.save_cart = "New Translation";
|
||||
window.groupOrderShop._initTooltips();
|
||||
|
||||
assert.equal(btn1.getAttribute('title'), 'New Translation', 'reinitialized with new label');
|
||||
assert.equal(
|
||||
btn1.getAttribute("title"),
|
||||
"New Translation",
|
||||
"reinitialized with new label"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('elements without data-tooltip-label are ignored', function(assert) {
|
||||
QUnit.test("elements without data-tooltip-label are ignored", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.$fixture.append('<button id="test-btn-no-label">No Label</button>');
|
||||
|
||||
window.groupOrderShop._initTooltips();
|
||||
|
||||
var btnNoLabel = document.getElementById('test-btn-no-label');
|
||||
var title = btnNoLabel.getAttribute('title');
|
||||
var btnNoLabel = document.getElementById("test-btn-no-label");
|
||||
var title = btnNoLabel.getAttribute("title");
|
||||
|
||||
assert.ok(!title || title === '', 'button without data-tooltip-label has no title');
|
||||
assert.ok(!title || title === "", "button without data-tooltip-label has no title");
|
||||
});
|
||||
|
||||
QUnit.test('querySelectorAll finds all tooltip elements', function(assert) {
|
||||
QUnit.test("querySelectorAll finds all tooltip elements", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var tooltipElements = document.querySelectorAll('[data-tooltip-label]');
|
||||
var tooltipElements = document.querySelectorAll("[data-tooltip-label]");
|
||||
|
||||
// We have 3 buttons with data-tooltip-label
|
||||
assert.equal(tooltipElements.length, 3, 'finds all 3 elements with data-tooltip-label');
|
||||
assert.equal(
|
||||
tooltipElements.length,
|
||||
3,
|
||||
"finds all 3 elements with data-tooltip-label"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('labels survive JSON serialization', function(assert) {
|
||||
QUnit.test("labels survive JSON serialization", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var labels = window.groupOrderShop.labels;
|
||||
var serialized = JSON.stringify(labels);
|
||||
var deserialized = JSON.parse(serialized);
|
||||
|
||||
assert.ok(serialized, 'labels can be serialized to JSON');
|
||||
assert.deepEqual(deserialized, labels, 'deserialized labels match original');
|
||||
assert.ok(serialized, "labels can be serialized to JSON");
|
||||
assert.deepEqual(deserialized, labels, "deserialized labels match original");
|
||||
});
|
||||
|
||||
QUnit.test('empty labels object does not break initialization', function(assert) {
|
||||
QUnit.test("empty labels object does not break initialization", function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
window.groupOrderShop.labels = {};
|
||||
|
||||
try {
|
||||
window.groupOrderShop._initTooltips();
|
||||
assert.ok(true, 'initialization with empty labels does not throw error');
|
||||
assert.ok(true, "initialization with empty labels does not throw error");
|
||||
} catch (e) {
|
||||
assert.ok(false, 'initialization threw error: ' + e.message);
|
||||
assert.ok(false, "initialization threw error: " + e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ Coverage:
|
|||
- Draft timeline (very old draft, recent draft)
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
|
@ -23,91 +24,117 @@ class TestSaveDraftOrder(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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.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.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.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.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,
|
||||
})
|
||||
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 = 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,
|
||||
}),
|
||||
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(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': [],
|
||||
})
|
||||
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())
|
||||
|
|
@ -116,15 +143,23 @@ class TestSaveDraftOrder(TransactionCase):
|
|||
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 = 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
|
||||
|
||||
|
|
@ -132,29 +167,33 @@ class TestSaveDraftOrder(TransactionCase):
|
|||
draft.order_line[0].product_qty = 5
|
||||
|
||||
# Should be same draft, not new one
|
||||
updated_draft = self.env['sale.order'].browse(draft_id)
|
||||
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',
|
||||
})
|
||||
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',
|
||||
})
|
||||
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)
|
||||
|
||||
|
|
@ -164,63 +203,83 @@ class TestLoadDraftOrder(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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.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.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,
|
||||
})
|
||||
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 = 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,
|
||||
})],
|
||||
})
|
||||
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'),
|
||||
])
|
||||
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)
|
||||
|
|
@ -228,29 +287,37 @@ class TestLoadDraftOrder(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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',
|
||||
})
|
||||
other_partner = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Other Member",
|
||||
"email": "other@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
other_user = self.env['res.users'].create({
|
||||
'name': 'Other User',
|
||||
'login': 'other@test.com',
|
||||
'partner_id': other_partner.id,
|
||||
})
|
||||
other_user = 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),
|
||||
])
|
||||
other_drafts = self.env["sale.order"].search(
|
||||
[
|
||||
("id", "=", draft.id),
|
||||
("partner_id", "=", other_partner.id),
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(len(other_drafts), 0)
|
||||
|
||||
|
|
@ -260,14 +327,16 @@ class TestLoadDraftOrder(TransactionCase):
|
|||
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 = 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)
|
||||
loaded = self.env["sale.order"].browse(draft.id)
|
||||
self.assertTrue(loaded.exists())
|
||||
# Controller should check: group_order.state and warn if closed
|
||||
|
||||
|
|
@ -277,41 +346,51 @@ class TestDraftConsistency(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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.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.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,
|
||||
})
|
||||
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 = 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):
|
||||
|
|
@ -319,16 +398,24 @@ class TestDraftConsistency(TransactionCase):
|
|||
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,
|
||||
})],
|
||||
})
|
||||
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
|
||||
|
||||
|
|
@ -342,18 +429,26 @@ class TestDraftConsistency(TransactionCase):
|
|||
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,
|
||||
})],
|
||||
})
|
||||
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)
|
||||
reloaded = self.env["sale.order"].browse(draft.id)
|
||||
self.assertEqual(reloaded.order_line[0].product_qty, 5)
|
||||
|
||||
|
||||
|
|
@ -362,62 +457,80 @@ class TestProductArchivedInDraft(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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.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.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,
|
||||
})
|
||||
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 = 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,
|
||||
})],
|
||||
})
|
||||
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)
|
||||
loaded = self.env["sale.order"].browse(draft.id)
|
||||
self.assertTrue(loaded.exists())
|
||||
# But product may not be editable/accessible
|
||||
|
||||
|
|
@ -427,108 +540,128 @@ class TestDraftTimeline(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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.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,
|
||||
})
|
||||
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 = 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',
|
||||
})
|
||||
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')
|
||||
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 = 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',
|
||||
})
|
||||
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')
|
||||
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',
|
||||
})
|
||||
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',
|
||||
})
|
||||
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'),
|
||||
])
|
||||
user_drafts = self.env["sale.order"].search(
|
||||
[
|
||||
("partner_id", "=", self.member_partner.id),
|
||||
("state", "=", "draft"),
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(len(user_drafts), 3)
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ Coverage:
|
|||
- Extreme dates (year 1900, year 2099)
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, date
|
||||
from datetime import date
|
||||
from datetime import timedelta
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestLeapYearHandling(TransactionCase):
|
||||
|
|
@ -25,10 +27,12 @@ class TestLeapYearHandling(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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)."""
|
||||
|
|
@ -36,16 +40,18 @@ class TestLeapYearHandling(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -57,16 +63,18 @@ class TestLeapYearHandling(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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))
|
||||
|
||||
|
|
@ -76,16 +84,18 @@ class TestLeapYearHandling(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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)
|
||||
|
|
@ -97,26 +107,30 @@ class TestLongDurationOrders(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -128,16 +142,18 @@ class TestLongDurationOrders(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -147,16 +163,18 @@ class TestLongDurationOrders(TransactionCase):
|
|||
"""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',
|
||||
})
|
||||
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())
|
||||
|
||||
|
|
@ -166,10 +184,12 @@ class TestPickupDayBoundary(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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)."""
|
||||
|
|
@ -177,16 +197,18 @@ class TestPickupDayBoundary(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -198,16 +220,18 @@ class TestPickupDayBoundary(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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())
|
||||
|
||||
|
|
@ -217,16 +241,18 @@ class TestPickupDayBoundary(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -238,16 +264,18 @@ class TestPickupDayBoundary(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -259,10 +287,12 @@ class TestFutureStartDateOrders(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Group",
|
||||
"is_company": True,
|
||||
}
|
||||
)
|
||||
|
||||
def test_order_starts_tomorrow(self):
|
||||
"""Test order starting tomorrow."""
|
||||
|
|
@ -270,16 +300,18 @@ class TestFutureStartDateOrders(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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)
|
||||
|
|
@ -290,16 +322,18 @@ class TestFutureStartDateOrders(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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())
|
||||
|
||||
|
|
@ -309,26 +343,30 @@ class TestExtremeDate(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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',
|
||||
})
|
||||
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())
|
||||
|
||||
|
|
@ -337,16 +375,18 @@ class TestExtremeDate(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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())
|
||||
|
||||
|
|
@ -355,16 +395,18 @@ class TestExtremeDate(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -377,25 +419,29 @@ class TestOrderWithoutEndDate(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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()
|
||||
|
||||
order = 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',
|
||||
})
|
||||
order = 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
|
||||
|
|
@ -406,10 +452,12 @@ class TestPickupCalculationAccuracy(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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."""
|
||||
|
|
@ -417,16 +465,18 @@ class TestPickupCalculationAccuracy(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
@ -438,16 +488,18 @@ class TestPickupCalculationAccuracy(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ Coverage:
|
|||
- /eskaera/labels (GET) - Get translated labels
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase, HttpCase
|
||||
from odoo.exceptions import ValidationError, AccessError
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import HttpCase
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestEskaearaListEndpoint(TransactionCase):
|
||||
|
|
@ -28,63 +30,75 @@ class TestEskaearaListEndpoint(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
'email': 'group@test.com',
|
||||
})
|
||||
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.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.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 = 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',
|
||||
})
|
||||
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 = 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()
|
||||
|
||||
|
|
@ -92,10 +106,12 @@ class TestEskaearaListEndpoint(TransactionCase):
|
|||
"""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),
|
||||
])
|
||||
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)
|
||||
|
|
@ -103,30 +119,36 @@ class TestEskaearaListEndpoint(TransactionCase):
|
|||
|
||||
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_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 = 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),
|
||||
])
|
||||
visible_orders = self.env["group.order"].search(
|
||||
[
|
||||
("state", "in", ["open", "draft"]),
|
||||
("group_ids", "in", user_groups.ids),
|
||||
]
|
||||
)
|
||||
|
||||
self.assertNotIn(other_order, visible_orders)
|
||||
|
||||
|
|
@ -136,61 +158,75 @@ class TestAddToCartEndpoint(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
'email': 'group@test.com',
|
||||
})
|
||||
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.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.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.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,
|
||||
})
|
||||
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,
|
||||
})
|
||||
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 = 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)]
|
||||
|
||||
|
|
@ -198,13 +234,13 @@ class TestAddToCartEndpoint(TransactionCase):
|
|||
"""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,
|
||||
"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'])
|
||||
self.assertTrue(cart_line["product_id"])
|
||||
|
||||
def test_add_to_cart_zero_quantity(self):
|
||||
"""Test that adding zero quantity is rejected."""
|
||||
|
|
@ -228,11 +264,13 @@ class TestAddToCartEndpoint(TransactionCase):
|
|||
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,
|
||||
})
|
||||
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)
|
||||
|
|
@ -241,7 +279,7 @@ class TestAddToCartEndpoint(TransactionCase):
|
|||
"""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')
|
||||
self.assertEqual(self.group_order.state, "closed")
|
||||
|
||||
|
||||
class TestCheckoutEndpoint(TransactionCase):
|
||||
|
|
@ -249,38 +287,46 @@ class TestCheckoutEndpoint(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
'email': 'group@test.com',
|
||||
})
|
||||
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.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.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 = 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):
|
||||
|
|
@ -301,16 +347,18 @@ class TestCheckoutEndpoint(TransactionCase):
|
|||
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 = 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
|
||||
|
|
@ -322,95 +370,115 @@ class TestConfirmOrderEndpoint(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
'email': 'group@test.com',
|
||||
})
|
||||
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.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.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.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,
|
||||
})
|
||||
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 = 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',
|
||||
})
|
||||
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')
|
||||
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',
|
||||
})
|
||||
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,
|
||||
})
|
||||
other_group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Other Group",
|
||||
"is_company": True,
|
||||
}
|
||||
)
|
||||
|
||||
other_order = 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',
|
||||
})
|
||||
other_order = 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)
|
||||
|
|
@ -421,76 +489,94 @@ class TestLoadDraftEndpoint(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
'email': 'group@test.com',
|
||||
})
|
||||
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.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.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.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,
|
||||
})
|
||||
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 = 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',
|
||||
})
|
||||
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_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',
|
||||
})
|
||||
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)
|
||||
|
|
@ -500,24 +586,28 @@ class TestLoadDraftEndpoint(TransactionCase):
|
|||
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 = 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()
|
||||
|
||||
old_sale = self.env['sale.order'].create({
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': expired_order.id,
|
||||
'state': 'draft',
|
||||
})
|
||||
old_sale = 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')
|
||||
self.assertEqual(expired_order.state, "closed")
|
||||
|
|
|
|||
|
|
@ -1,127 +1,158 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestEskaerShop(TransactionCase):
|
||||
'''Test suite para la lógica de eskaera_shop (descubrimiento de productos).'''
|
||||
"""Test suite para la lógica de eskaera_shop (descubrimiento de productos)."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Crear un grupo (res.partner)
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Grupo Test Eskaera',
|
||||
'is_company': True,
|
||||
'email': 'grupo@test.com',
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo Test Eskaera",
|
||||
"is_company": True,
|
||||
"email": "grupo@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear usuario miembro del grupo
|
||||
user_partner = self.env['res.partner'].create({
|
||||
'name': 'Usuario Test Partner',
|
||||
'email': 'usuario_test@test.com',
|
||||
})
|
||||
user_partner = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Usuario Test Partner",
|
||||
"email": "usuario_test@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
self.user = self.env['res.users'].create({
|
||||
'name': 'Usuario Test',
|
||||
'login': 'usuario_test@test.com',
|
||||
'email': 'usuario_test@test.com',
|
||||
'partner_id': user_partner.id,
|
||||
})
|
||||
self.user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Usuario Test",
|
||||
"login": "usuario_test@test.com",
|
||||
"email": "usuario_test@test.com",
|
||||
"partner_id": user_partner.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Añadir el partner del usuario como miembro del grupo
|
||||
self.group.member_ids = [(4, user_partner.id)]
|
||||
|
||||
# Crear categorías de producto
|
||||
self.category1 = self.env['product.category'].create({
|
||||
'name': 'Categoría Test 1',
|
||||
})
|
||||
self.category1 = self.env["product.category"].create(
|
||||
{
|
||||
"name": "Categoría Test 1",
|
||||
}
|
||||
)
|
||||
|
||||
self.category2 = self.env['product.category'].create({
|
||||
'name': 'Categoría Test 2',
|
||||
})
|
||||
self.category2 = self.env["product.category"].create(
|
||||
{
|
||||
"name": "Categoría Test 2",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear proveedor
|
||||
self.supplier = self.env['res.partner'].create({
|
||||
'name': 'Proveedor Test',
|
||||
'is_company': True,
|
||||
'supplier_rank': 1,
|
||||
'email': 'proveedor@test.com',
|
||||
})
|
||||
self.supplier = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Proveedor Test",
|
||||
"is_company": True,
|
||||
"supplier_rank": 1,
|
||||
"email": "proveedor@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear productos
|
||||
self.product_cat1 = self.env['product.product'].create({
|
||||
'name': 'Producto Categoría 1',
|
||||
'type': 'consu',
|
||||
'list_price': 10.0,
|
||||
'categ_id': self.category1.id,
|
||||
'active': True,
|
||||
})
|
||||
self.product_cat1.product_tmpl_id.write({
|
||||
'is_published': True,
|
||||
'sale_ok': True,
|
||||
})
|
||||
self.product_cat1 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Producto Categoría 1",
|
||||
"type": "consu",
|
||||
"list_price": 10.0,
|
||||
"categ_id": self.category1.id,
|
||||
"active": True,
|
||||
}
|
||||
)
|
||||
self.product_cat1.product_tmpl_id.write(
|
||||
{
|
||||
"is_published": True,
|
||||
"sale_ok": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.product_cat2 = self.env['product.product'].create({
|
||||
'name': 'Producto Categoría 2',
|
||||
'type': 'consu',
|
||||
'list_price': 20.0,
|
||||
'categ_id': self.category2.id,
|
||||
'active': True,
|
||||
})
|
||||
self.product_cat2.product_tmpl_id.write({
|
||||
'is_published': True,
|
||||
'sale_ok': True,
|
||||
})
|
||||
self.product_cat2 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Producto Categoría 2",
|
||||
"type": "consu",
|
||||
"list_price": 20.0,
|
||||
"categ_id": self.category2.id,
|
||||
"active": True,
|
||||
}
|
||||
)
|
||||
self.product_cat2.product_tmpl_id.write(
|
||||
{
|
||||
"is_published": True,
|
||||
"sale_ok": True,
|
||||
}
|
||||
)
|
||||
|
||||
# Crear producto con relación a proveedor
|
||||
self.product_supplier_template = self.env['product.template'].create({
|
||||
'name': 'Producto Proveedor',
|
||||
'type': 'consu',
|
||||
'list_price': 30.0,
|
||||
'categ_id': self.category1.id,
|
||||
'is_published': True,
|
||||
'sale_ok': True,
|
||||
})
|
||||
self.product_supplier_template = self.env["product.template"].create(
|
||||
{
|
||||
"name": "Producto Proveedor",
|
||||
"type": "consu",
|
||||
"list_price": 30.0,
|
||||
"categ_id": self.category1.id,
|
||||
"is_published": True,
|
||||
"sale_ok": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.product_supplier = self.product_supplier_template.product_variant_ids[0]
|
||||
self.product_supplier.active = True
|
||||
|
||||
# Crear relación con proveedor
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.product_supplier_template.id,
|
||||
'partner_id': self.supplier.id,
|
||||
'min_qty': 1.0,
|
||||
})
|
||||
self.env["product.supplierinfo"].create(
|
||||
{
|
||||
"product_tmpl_id": self.product_supplier_template.id,
|
||||
"partner_id": self.supplier.id,
|
||||
"min_qty": 1.0,
|
||||
}
|
||||
)
|
||||
|
||||
self.product_direct = self.env['product.product'].create({
|
||||
'name': 'Producto Directo',
|
||||
'type': 'consu',
|
||||
'list_price': 40.0,
|
||||
'categ_id': self.category1.id,
|
||||
'active': True,
|
||||
})
|
||||
self.product_direct.product_tmpl_id.write({
|
||||
'is_published': True,
|
||||
'sale_ok': True,
|
||||
})
|
||||
self.product_direct = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Producto Directo",
|
||||
"type": "consu",
|
||||
"list_price": 40.0,
|
||||
"categ_id": self.category1.id,
|
||||
"active": True,
|
||||
}
|
||||
)
|
||||
self.product_direct.product_tmpl_id.write(
|
||||
{
|
||||
"is_published": True,
|
||||
"sale_ok": True,
|
||||
}
|
||||
)
|
||||
|
||||
def test_product_discovery_direct(self):
|
||||
'''Test que los productos directos se descubren correctamente.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Directo',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'product_ids': [(6, 0, [self.product_direct.id])],
|
||||
})
|
||||
"""Test que los productos directos se descubren correctamente."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Directo",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"product_ids": [(6, 0, [self.product_direct.id])],
|
||||
}
|
||||
)
|
||||
|
||||
order.action_open()
|
||||
|
||||
|
|
@ -131,96 +162,124 @@ class TestEskaerShop(TransactionCase):
|
|||
self.assertEqual(len(products), 1)
|
||||
self.assertIn(self.product_direct, products)
|
||||
|
||||
products = self.env['product.product']._get_products_for_group_order(order.id)
|
||||
self.assertIn(self.product_direct.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
products = self.env["product.product"]._get_products_for_group_order(order.id)
|
||||
self.assertIn(
|
||||
self.product_direct.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
def test_product_discovery_by_category(self):
|
||||
'''Test que los productos se descubren por categoría cuando no hay directos.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido por Categoría',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'category_ids': [(6, 0, [self.category1.id])],
|
||||
})
|
||||
"""Test que los productos se descubren por categoría cuando no hay directos."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido por Categoría",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"category_ids": [(6, 0, [self.category1.id])],
|
||||
}
|
||||
)
|
||||
|
||||
order.action_open()
|
||||
|
||||
# Simular lo que hace eskaera_shop (fallback a categorías)
|
||||
products = order.product_ids
|
||||
if not products:
|
||||
products = self.env['product.product'].search([
|
||||
('categ_id', 'in', order.category_ids.ids),
|
||||
])
|
||||
products = self.env["product.product"].search(
|
||||
[
|
||||
("categ_id", "in", order.category_ids.ids),
|
||||
]
|
||||
)
|
||||
|
||||
# Debe incluir todos los productos de la categoría 1
|
||||
self.assertGreaterEqual(len(products), 2)
|
||||
self.assertIn(self.product_cat1.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertIn(self.product_supplier.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertIn(self.product_direct.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertIn(
|
||||
self.product_cat1.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
self.assertIn(
|
||||
self.product_supplier.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
self.assertIn(
|
||||
self.product_direct.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
order.write({'category_ids': [(4, self.category1.id)]})
|
||||
products = self.env['product.product']._get_products_for_group_order(order.id)
|
||||
self.assertIn(self.product_cat1.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertNotIn(self.product_cat2.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
order.write({"category_ids": [(4, self.category1.id)]})
|
||||
products = self.env["product.product"]._get_products_for_group_order(order.id)
|
||||
self.assertIn(
|
||||
self.product_cat1.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
self.assertNotIn(
|
||||
self.product_cat2.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
def test_product_discovery_by_supplier(self):
|
||||
'''Test que los productos se descubren por proveedor cuando no hay directos ni categorías.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido por Proveedor',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'supplier_ids': [(6, 0, [self.supplier.id])],
|
||||
})
|
||||
"""Test que los productos se descubren por proveedor cuando no hay directos ni categorías."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido por Proveedor",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"supplier_ids": [(6, 0, [self.supplier.id])],
|
||||
}
|
||||
)
|
||||
|
||||
order.action_open()
|
||||
|
||||
# Simular lo que hace eskaera_shop (fallback a proveedores)
|
||||
products = order.product_ids
|
||||
if not products and order.category_ids:
|
||||
products = self.env['product.product'].search([
|
||||
('categ_id', 'in', order.category_ids.ids),
|
||||
])
|
||||
products = self.env["product.product"].search(
|
||||
[
|
||||
("categ_id", "in", order.category_ids.ids),
|
||||
]
|
||||
)
|
||||
|
||||
if not products and order.supplier_ids:
|
||||
# Buscar productos que tienen estos proveedores en seller_ids
|
||||
product_templates = self.env['product.template'].search([
|
||||
('seller_ids.partner_id', 'in', order.supplier_ids.ids),
|
||||
])
|
||||
products = product_templates.mapped('product_variant_ids')
|
||||
product_templates = self.env["product.template"].search(
|
||||
[
|
||||
("seller_ids.partner_id", "in", order.supplier_ids.ids),
|
||||
]
|
||||
)
|
||||
products = product_templates.mapped("product_variant_ids")
|
||||
|
||||
# Debe incluir el producto del proveedor
|
||||
self.assertEqual(len(products), 1)
|
||||
self.assertIn(self.product_supplier.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertIn(
|
||||
self.product_supplier.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
order.write({'supplier_ids': [(4, self.supplier.id)]})
|
||||
products = self.env['product.product']._get_products_for_group_order(order.id)
|
||||
self.assertIn(self.product_supplier.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
order.write({"supplier_ids": [(4, self.supplier.id)]})
|
||||
products = self.env["product.product"]._get_products_for_group_order(order.id)
|
||||
self.assertIn(
|
||||
self.product_supplier.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
def test_product_discovery_priority(self):
|
||||
'''Test que la prioridad de descubrimiento es: directos > categorías > proveedores.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido con Todos',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'product_ids': [(6, 0, [self.product_direct.id])],
|
||||
'category_ids': [(6, 0, [self.category1.id, self.category2.id])],
|
||||
'supplier_ids': [(6, 0, [self.supplier.id])],
|
||||
})
|
||||
"""Test que la prioridad de descubrimiento es: directos > categorías > proveedores."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido con Todos",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"product_ids": [(6, 0, [self.product_direct.id])],
|
||||
"category_ids": [(6, 0, [self.category1.id, self.category2.id])],
|
||||
"supplier_ids": [(6, 0, [self.supplier.id])],
|
||||
}
|
||||
)
|
||||
|
||||
order.action_open()
|
||||
|
||||
|
|
@ -229,94 +288,122 @@ class TestEskaerShop(TransactionCase):
|
|||
|
||||
# Debe retornar los productos directos, no los de categoría/proveedor
|
||||
self.assertEqual(len(products), 1)
|
||||
self.assertIn(self.product_direct.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertNotIn(self.product_cat1.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertNotIn(self.product_cat2.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertNotIn(self.product_supplier.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertIn(
|
||||
self.product_direct.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
self.assertNotIn(
|
||||
self.product_cat1.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
self.assertNotIn(
|
||||
self.product_cat2.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
self.assertNotIn(
|
||||
self.product_supplier.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
# 2. The canonical helper now returns the UNION of all association
|
||||
# sources (direct products, categories, suppliers). Assert all are
|
||||
# present to reflect the new behaviour.
|
||||
products = self.env['product.product']._get_products_for_group_order(order.id)
|
||||
tmpl_ids = products.mapped('product_tmpl_id')
|
||||
products = self.env["product.product"]._get_products_for_group_order(order.id)
|
||||
tmpl_ids = products.mapped("product_tmpl_id")
|
||||
self.assertIn(self.product_direct.product_tmpl_id, tmpl_ids)
|
||||
self.assertIn(self.product_cat1.product_tmpl_id, tmpl_ids)
|
||||
self.assertIn(self.product_supplier.product_tmpl_id, tmpl_ids)
|
||||
|
||||
def test_product_discovery_fallback_from_category_to_supplier(self):
|
||||
'''Test que si no hay directos ni categorías, usa proveedores.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Fallback',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
"""Test que si no hay directos ni categorías, usa proveedores."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Fallback",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
# Sin product_ids
|
||||
# Sin category_ids
|
||||
'supplier_ids': [(6, 0, [self.supplier.id])],
|
||||
})
|
||||
"supplier_ids": [(6, 0, [self.supplier.id])],
|
||||
}
|
||||
)
|
||||
|
||||
order.action_open()
|
||||
|
||||
# Simular lo que hace eskaera_shop
|
||||
products = order.product_ids
|
||||
if not products and order.category_ids:
|
||||
products = self.env['product.product'].search([
|
||||
('categ_id', 'in', order.category_ids.ids),
|
||||
])
|
||||
products = self.env["product.product"].search(
|
||||
[
|
||||
("categ_id", "in", order.category_ids.ids),
|
||||
]
|
||||
)
|
||||
|
||||
if not products and order.supplier_ids:
|
||||
# Buscar productos que tienen estos proveedores en seller_ids
|
||||
product_templates = self.env['product.template'].search([
|
||||
('seller_ids.partner_id', 'in', order.supplier_ids.ids),
|
||||
])
|
||||
products = product_templates.mapped('product_variant_ids')
|
||||
product_templates = self.env["product.template"].search(
|
||||
[
|
||||
("seller_ids.partner_id", "in", order.supplier_ids.ids),
|
||||
]
|
||||
)
|
||||
products = product_templates.mapped("product_variant_ids")
|
||||
|
||||
# Debe retornar productos del proveedor
|
||||
self.assertEqual(len(products), 1)
|
||||
self.assertIn(self.product_supplier.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertIn(
|
||||
self.product_supplier.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
# Clear categories so supplier-only fallback remains active
|
||||
order.write({
|
||||
'category_ids': [(5, 0, 0)],
|
||||
'supplier_ids': [(4, self.supplier.id)],
|
||||
})
|
||||
products = self.env['product.product']._get_products_for_group_order(order.id)
|
||||
self.assertIn(self.product_supplier.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
self.assertNotIn(self.product_direct.product_tmpl_id, products.mapped('product_tmpl_id'))
|
||||
order.write(
|
||||
{
|
||||
"category_ids": [(5, 0, 0)],
|
||||
"supplier_ids": [(4, self.supplier.id)],
|
||||
}
|
||||
)
|
||||
products = self.env["product.product"]._get_products_for_group_order(order.id)
|
||||
self.assertIn(
|
||||
self.product_supplier.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
self.assertNotIn(
|
||||
self.product_direct.product_tmpl_id, products.mapped("product_tmpl_id")
|
||||
)
|
||||
|
||||
def test_no_products_available(self):
|
||||
'''Test que retorna vacío si no hay productos definidos de ninguna forma.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Sin Productos',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
"""Test que retorna vacío si no hay productos definidos de ninguna forma."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Sin Productos",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
# Sin product_ids, category_ids, supplier_ids
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
order.action_open()
|
||||
|
||||
# Simular lo que hace eskaera_shop
|
||||
products = order.product_ids
|
||||
if not products and order.category_ids:
|
||||
products = self.env['product.product'].search([
|
||||
('categ_id', 'in', order.category_ids.ids),
|
||||
])
|
||||
products = self.env["product.product"].search(
|
||||
[
|
||||
("categ_id", "in", order.category_ids.ids),
|
||||
]
|
||||
)
|
||||
|
||||
if not products and order.supplier_ids:
|
||||
# Buscar productos que tienen estos proveedores en seller_ids
|
||||
product_templates = self.env['product.template'].search([
|
||||
('seller_ids.partner_id', 'in', order.supplier_ids.ids),
|
||||
])
|
||||
products = product_templates.mapped('product_variant_ids')
|
||||
product_templates = self.env["product.template"].search(
|
||||
[
|
||||
("seller_ids.partner_id", "in", order.supplier_ids.ids),
|
||||
]
|
||||
)
|
||||
products = product_templates.mapped("product_variant_ids")
|
||||
|
||||
# Debe estar vacío
|
||||
self.assertEqual(len(products), 0)
|
||||
|
|
|
|||
|
|
@ -1,310 +1,354 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo import fields
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestGroupOrder(TransactionCase):
|
||||
'''Test suite para el modelo group.order.'''
|
||||
"""Test suite para el modelo group.order."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Crear un grupo (res.partner)
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Grupo Test',
|
||||
'is_company': True,
|
||||
'email': 'grupo@test.com',
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo Test",
|
||||
"is_company": True,
|
||||
"email": "grupo@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear productos
|
||||
self.product1 = self.env['product.product'].create({
|
||||
'name': 'Producto Test 1',
|
||||
'type': 'consu',
|
||||
'list_price': 10.0,
|
||||
})
|
||||
self.product1 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Producto Test 1",
|
||||
"type": "consu",
|
||||
"list_price": 10.0,
|
||||
}
|
||||
)
|
||||
|
||||
self.product2 = self.env['product.product'].create({
|
||||
'name': 'Producto Test 2',
|
||||
'type': 'consu',
|
||||
'list_price': 20.0,
|
||||
})
|
||||
self.product2 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Producto Test 2",
|
||||
"type": "consu",
|
||||
"list_price": 20.0,
|
||||
}
|
||||
)
|
||||
|
||||
def test_create_group_order(self):
|
||||
'''Test crear un pedido de grupo.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Semanal Test',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test crear un pedido de grupo."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Semanal Test",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(order.exists())
|
||||
self.assertEqual(order.state, 'draft')
|
||||
self.assertEqual(order.state, "draft")
|
||||
self.assertIn(self.group, order.group_ids)
|
||||
|
||||
def test_group_order_dates_validation(self):
|
||||
""" Test that start_date must be before end_date """
|
||||
"""Test that start_date must be before end_date"""
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['group.order'].create({
|
||||
'name': 'Pedido Invalid',
|
||||
'start_date': fields.Date.today() + timedelta(days=7),
|
||||
'end_date': fields.Date.today(),
|
||||
})
|
||||
self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Invalid",
|
||||
"start_date": fields.Date.today() + timedelta(days=7),
|
||||
"end_date": fields.Date.today(),
|
||||
}
|
||||
)
|
||||
|
||||
def test_group_order_state_transitions(self):
|
||||
'''Test transiciones de estado.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido State Test',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test transiciones de estado."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido State Test",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
# Draft -> Open
|
||||
order.action_open()
|
||||
self.assertEqual(order.state, 'open')
|
||||
self.assertEqual(order.state, "open")
|
||||
|
||||
# Open -> Closed
|
||||
order.action_close()
|
||||
self.assertEqual(order.state, 'closed')
|
||||
self.assertEqual(order.state, "closed")
|
||||
|
||||
def test_group_order_action_cancel(self):
|
||||
'''Test cancelar un pedido.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Cancel Test',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test cancelar un pedido."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Cancel Test",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
order.action_cancel()
|
||||
self.assertEqual(order.state, 'cancelled')
|
||||
self.assertEqual(order.state, "cancelled")
|
||||
|
||||
def test_get_active_orders_for_week(self):
|
||||
'''Test obtener pedidos activos para la semana.'''
|
||||
"""Test obtener pedidos activos para la semana."""
|
||||
today = datetime.now().date()
|
||||
week_start = today - timedelta(days=today.weekday())
|
||||
week_end = week_start + timedelta(days=6)
|
||||
|
||||
# Crear pedido activo esta semana
|
||||
active_order = self.env['group.order'].create({
|
||||
'name': 'Pedido Activo',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': week_start,
|
||||
'end_date': week_end,
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'state': 'open',
|
||||
})
|
||||
active_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Activo",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": week_start,
|
||||
"end_date": week_end,
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"state": "open",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear pedido inactivo (futuro)
|
||||
future_order = self.env['group.order'].create({
|
||||
'name': 'Pedido Futuro',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': week_end + timedelta(days=1),
|
||||
'end_date': week_end + timedelta(days=8),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'state': 'open',
|
||||
})
|
||||
future_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Futuro",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": week_end + timedelta(days=1),
|
||||
"end_date": week_end + timedelta(days=8),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"state": "open",
|
||||
}
|
||||
)
|
||||
|
||||
active_orders = self.env['group.order'].search([
|
||||
('state', '=', 'open'),
|
||||
'|',
|
||||
('end_date', '>=', week_start),
|
||||
('end_date', '=', False),
|
||||
('start_date', '<=', week_end),
|
||||
])
|
||||
active_orders = self.env["group.order"].search(
|
||||
[
|
||||
("state", "=", "open"),
|
||||
"|",
|
||||
("end_date", ">=", week_start),
|
||||
("end_date", "=", False),
|
||||
("start_date", "<=", week_end),
|
||||
]
|
||||
)
|
||||
|
||||
self.assertIn(active_order, active_orders)
|
||||
self.assertNotIn(future_order, active_orders)
|
||||
|
||||
def test_permanent_group_order(self):
|
||||
'''Test crear un pedido permanente (sin end_date).'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Permanente',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': False,
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test crear un pedido permanente (sin end_date)."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Permanente",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": False,
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(order.exists())
|
||||
self.assertFalse(order.end_date)
|
||||
|
||||
def test_get_active_orders_excludes_draft(self):
|
||||
'''Test que get_active_orders_for_week NO incluye pedidos en draft.'''
|
||||
"""Test que get_active_orders_for_week NO incluye pedidos en draft."""
|
||||
today = datetime.now().date()
|
||||
|
||||
# Crear pedido en draft (no abierto)
|
||||
draft_order = self.env['group.order'].create({
|
||||
'name': 'Pedido Draft',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': today,
|
||||
'end_date': today + timedelta(days=7),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'state': 'draft',
|
||||
})
|
||||
draft_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Draft",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": today,
|
||||
"end_date": today + timedelta(days=7),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"state": "draft",
|
||||
}
|
||||
)
|
||||
|
||||
today = datetime.now().date()
|
||||
week_start = today - timedelta(days=today.weekday())
|
||||
week_end = week_start + timedelta(days=6)
|
||||
active_orders = self.env['group.order'].search([
|
||||
('state', '=', 'open'),
|
||||
'|',
|
||||
('end_date', '>=', week_start),
|
||||
('end_date', '=', False),
|
||||
('start_date', '<=', week_end),
|
||||
])
|
||||
active_orders = self.env["group.order"].search(
|
||||
[
|
||||
("state", "=", "open"),
|
||||
"|",
|
||||
("end_date", ">=", week_start),
|
||||
("end_date", "=", False),
|
||||
("start_date", "<=", week_end),
|
||||
]
|
||||
)
|
||||
self.assertNotIn(draft_order, active_orders)
|
||||
|
||||
def test_get_active_orders_excludes_closed(self):
|
||||
'''Test que get_active_orders_for_week NO incluye pedidos cerrados.'''
|
||||
"""Test que get_active_orders_for_week NO incluye pedidos cerrados."""
|
||||
today = datetime.now().date()
|
||||
|
||||
closed_order = self.env['group.order'].create({
|
||||
'name': 'Pedido Cerrado',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': today,
|
||||
'end_date': today + timedelta(days=7),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'state': 'closed',
|
||||
})
|
||||
closed_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Cerrado",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": today,
|
||||
"end_date": today + timedelta(days=7),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"state": "closed",
|
||||
}
|
||||
)
|
||||
|
||||
today = datetime.now().date()
|
||||
week_start = today - timedelta(days=today.weekday())
|
||||
week_end = week_start + timedelta(days=6)
|
||||
active_orders = self.env['group.order'].search([
|
||||
('state', '=', 'open'),
|
||||
'|',
|
||||
('end_date', '>=', week_start),
|
||||
('end_date', '=', False),
|
||||
('start_date', '<=', week_end),
|
||||
])
|
||||
active_orders = self.env["group.order"].search(
|
||||
[
|
||||
("state", "=", "open"),
|
||||
"|",
|
||||
("end_date", ">=", week_start),
|
||||
("end_date", "=", False),
|
||||
("start_date", "<=", week_end),
|
||||
]
|
||||
)
|
||||
self.assertNotIn(closed_order, active_orders)
|
||||
|
||||
def test_get_active_orders_excludes_cancelled(self):
|
||||
'''Test que get_active_orders_for_week NO incluye pedidos cancelados.'''
|
||||
"""Test que get_active_orders_for_week NO incluye pedidos cancelados."""
|
||||
today = datetime.now().date()
|
||||
|
||||
cancelled_order = self.env['group.order'].create({
|
||||
'name': 'Pedido Cancelado',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': today,
|
||||
'end_date': today + timedelta(days=7),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
'state': 'cancelled',
|
||||
})
|
||||
cancelled_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Cancelado",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": today,
|
||||
"end_date": today + timedelta(days=7),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
"state": "cancelled",
|
||||
}
|
||||
)
|
||||
|
||||
today = datetime.now().date()
|
||||
week_start = today - timedelta(days=today.weekday())
|
||||
week_end = week_start + timedelta(days=6)
|
||||
active_orders = self.env['group.order'].search([
|
||||
('state', '=', 'open'),
|
||||
'|',
|
||||
('end_date', '>=', week_start),
|
||||
('end_date', '=', False),
|
||||
('start_date', '<=', week_end),
|
||||
])
|
||||
active_orders = self.env["group.order"].search(
|
||||
[
|
||||
("state", "=", "open"),
|
||||
"|",
|
||||
("end_date", ">=", week_start),
|
||||
("end_date", "=", False),
|
||||
("start_date", "<=", week_end),
|
||||
]
|
||||
)
|
||||
self.assertNotIn(cancelled_order, active_orders)
|
||||
|
||||
def test_state_transition_draft_to_open(self):
|
||||
'''Test que un pedido pasa de draft a open.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Estado Test',
|
||||
'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': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test que un pedido pasa de draft a open."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Estado Test",
|
||||
"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": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(order.state, 'draft')
|
||||
self.assertEqual(order.state, "draft")
|
||||
order.action_open()
|
||||
self.assertEqual(order.state, 'open')
|
||||
self.assertEqual(order.state, "open")
|
||||
|
||||
def test_state_transition_open_to_closed(self):
|
||||
'''Test transición válida open -> closed.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Estado Test',
|
||||
'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': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test transición válida open -> closed."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Estado Test",
|
||||
"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": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
order.action_open()
|
||||
self.assertEqual(order.state, 'open')
|
||||
self.assertEqual(order.state, "open")
|
||||
|
||||
order.action_close()
|
||||
self.assertEqual(order.state, 'closed')
|
||||
self.assertEqual(order.state, "closed")
|
||||
|
||||
def test_state_transition_any_to_cancelled(self):
|
||||
'''Test cancelar desde cualquier estado.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Estado Test',
|
||||
'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': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test cancelar desde cualquier estado."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Estado Test",
|
||||
"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": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
# Desde draft
|
||||
order.action_cancel()
|
||||
self.assertEqual(order.state, 'cancelled')
|
||||
self.assertEqual(order.state, "cancelled")
|
||||
|
||||
# Crear otro desde open
|
||||
order2 = self.env['group.order'].create({
|
||||
'name': 'Pedido Estado Test 2',
|
||||
'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': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
order2 = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Estado Test 2",
|
||||
"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": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
order2.action_open()
|
||||
order2.action_cancel()
|
||||
self.assertEqual(order2.state, 'cancelled')
|
||||
self.assertEqual(order2.state, "cancelled")
|
||||
|
|
|
|||
|
|
@ -1,147 +1,178 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestMultiCompanyGroupOrder(TransactionCase):
|
||||
'''Test suite para el soporte multicompañía en group.order.'''
|
||||
"""Test suite para el soporte multicompañía en group.order."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Crear dos compañías
|
||||
self.company1 = self.env['res.company'].create({
|
||||
'name': 'Company 1',
|
||||
})
|
||||
self.company2 = self.env['res.company'].create({
|
||||
'name': 'Company 2',
|
||||
})
|
||||
self.company1 = self.env["res.company"].create(
|
||||
{
|
||||
"name": "Company 1",
|
||||
}
|
||||
)
|
||||
self.company2 = self.env["res.company"].create(
|
||||
{
|
||||
"name": "Company 2",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear grupos en diferentes compañías
|
||||
self.group1 = self.env['res.partner'].create({
|
||||
'name': 'Grupo Company 1',
|
||||
'is_company': True,
|
||||
'email': 'grupo1@test.com',
|
||||
'company_id': self.company1.id,
|
||||
})
|
||||
self.group1 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo Company 1",
|
||||
"is_company": True,
|
||||
"email": "grupo1@test.com",
|
||||
"company_id": self.company1.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.group2 = self.env['res.partner'].create({
|
||||
'name': 'Grupo Company 2',
|
||||
'is_company': True,
|
||||
'email': 'grupo2@test.com',
|
||||
'company_id': self.company2.id,
|
||||
})
|
||||
self.group2 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo Company 2",
|
||||
"is_company": True,
|
||||
"email": "grupo2@test.com",
|
||||
"company_id": self.company2.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Crear productos en cada compañía
|
||||
self.product1 = self.env['product.product'].create({
|
||||
'name': 'Producto Company 1',
|
||||
'type': 'consu',
|
||||
'list_price': 10.0,
|
||||
'company_id': self.company1.id,
|
||||
})
|
||||
self.product1 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Producto Company 1",
|
||||
"type": "consu",
|
||||
"list_price": 10.0,
|
||||
"company_id": self.company1.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.product2 = self.env['product.product'].create({
|
||||
'name': 'Producto Company 2',
|
||||
'type': 'consu',
|
||||
'list_price': 20.0,
|
||||
'company_id': self.company2.id,
|
||||
})
|
||||
self.product2 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Producto Company 2",
|
||||
"type": "consu",
|
||||
"list_price": 20.0,
|
||||
"company_id": self.company2.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_group_order_has_company_id(self):
|
||||
'''Test que group.order tenga el campo company_id.'''
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido Company 1',
|
||||
'group_ids': [(6, 0, [self.group1.id])],
|
||||
'company_id': self.company1.id,
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
"""Test que group.order tenga el campo company_id."""
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Company 1",
|
||||
"group_ids": [(6, 0, [self.group1.id])],
|
||||
"company_id": self.company1.id,
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(order.exists())
|
||||
self.assertEqual(order.company_id, self.company1)
|
||||
|
||||
def test_group_order_default_company(self):
|
||||
'''Test que company_id por defecto sea la compañía del usuario.'''
|
||||
"""Test que company_id por defecto sea la compañía del usuario."""
|
||||
# Crear usuario con compañía específica
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'Test User',
|
||||
'login': 'testuser',
|
||||
'password': 'test123',
|
||||
'company_id': self.company1.id,
|
||||
'company_ids': [(6, 0, [self.company1.id])],
|
||||
})
|
||||
user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Test User",
|
||||
"login": "testuser",
|
||||
"password": "test123",
|
||||
"company_id": self.company1.id,
|
||||
"company_ids": [(6, 0, [self.company1.id])],
|
||||
}
|
||||
)
|
||||
|
||||
order = self.env['group.order'].with_user(user).create({
|
||||
'name': 'Pedido Default Company',
|
||||
'group_ids': [(6, 0, [self.group1.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
order = (
|
||||
self.env["group.order"]
|
||||
.with_user(user)
|
||||
.create(
|
||||
{
|
||||
"name": "Pedido Default Company",
|
||||
"group_ids": [(6, 0, [self.group1.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# Verificar que se asignó la compañía del usuario
|
||||
self.assertEqual(order.company_id, self.company1)
|
||||
|
||||
def test_group_order_company_constraint(self):
|
||||
'''Test que solo grupos de la misma compañía se puedan asignar.'''
|
||||
"""Test que solo grupos de la misma compañía se puedan asignar."""
|
||||
# Intentar asignar un grupo de otra compañía
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['group.order'].create({
|
||||
'name': 'Pedido Mixed Companies',
|
||||
'group_ids': [(6, 0, [self.group1.id, self.group2.id])],
|
||||
'company_id': self.company1.id,
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Mixed Companies",
|
||||
"group_ids": [(6, 0, [self.group1.id, self.group2.id])],
|
||||
"company_id": self.company1.id,
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
def test_group_order_multi_company_filter(self):
|
||||
'''Test que get_active_orders_for_week() respete company_id.'''
|
||||
"""Test que get_active_orders_for_week() respete company_id."""
|
||||
# Crear órdenes en diferentes compañías
|
||||
order1 = self.env['group.order'].create({
|
||||
'name': 'Pedido Company 1',
|
||||
'group_ids': [(6, 0, [self.group1.id])],
|
||||
'company_id': self.company1.id,
|
||||
'type': 'regular',
|
||||
'state': 'open',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
order1 = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Company 1",
|
||||
"group_ids": [(6, 0, [self.group1.id])],
|
||||
"company_id": self.company1.id,
|
||||
"type": "regular",
|
||||
"state": "open",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
order2 = self.env['group.order'].create({
|
||||
'name': 'Pedido Company 2',
|
||||
'group_ids': [(6, 0, [self.group2.id])],
|
||||
'company_id': self.company2.id,
|
||||
'type': 'regular',
|
||||
'state': 'open',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
order2 = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Company 2",
|
||||
"group_ids": [(6, 0, [self.group2.id])],
|
||||
"company_id": self.company2.id,
|
||||
"type": "regular",
|
||||
"state": "open",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
# Obtener órdenes activas de company1
|
||||
active_orders = self.env['group.order'].with_context(
|
||||
allowed_company_ids=[self.company1.id]
|
||||
).get_active_orders_for_week()
|
||||
active_orders = (
|
||||
self.env["group.order"]
|
||||
.with_context(allowed_company_ids=[self.company1.id])
|
||||
.get_active_orders_for_week()
|
||||
)
|
||||
|
||||
# Debería contener solo order1
|
||||
self.assertIn(order1, active_orders)
|
||||
|
|
@ -149,24 +180,28 @@ class TestMultiCompanyGroupOrder(TransactionCase):
|
|||
# el filtro de compañía correctamente
|
||||
|
||||
def test_product_company_isolation(self):
|
||||
'''Test que los productos de diferentes compañías estén aislados.'''
|
||||
"""Test que los productos de diferentes compañías estén aislados."""
|
||||
# Crear categoría para products
|
||||
category = self.env['product.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
category = self.env["product.category"].create(
|
||||
{
|
||||
"name": "Test Category",
|
||||
}
|
||||
)
|
||||
|
||||
order = self.env['group.order'].create({
|
||||
'name': 'Pedido con Categoría',
|
||||
'group_ids': [(6, 0, [self.group1.id])],
|
||||
'category_ids': [(6, 0, [category.id])],
|
||||
'company_id': self.company1.id,
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido con Categoría",
|
||||
"group_ids": [(6, 0, [self.group1.id])],
|
||||
"category_ids": [(6, 0, [category.id])],
|
||||
"company_id": self.company1.id,
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(order.exists())
|
||||
self.assertEqual(order.company_id, self.company1)
|
||||
|
|
|
|||
|
|
@ -13,12 +13,11 @@ Coverage:
|
|||
- Product price info structure in eskaera_shop
|
||||
"""
|
||||
|
||||
import json
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestPricingWithPricelist(TransactionCase):
|
||||
"""Test pricing calculations using OCA product_get_price_helper addon."""
|
||||
|
||||
|
|
@ -26,118 +25,154 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
super().setUp()
|
||||
|
||||
# Create test company
|
||||
self.company = self.env['res.company'].create({
|
||||
'name': 'Test Company Pricing',
|
||||
})
|
||||
self.company = self.env["res.company"].create(
|
||||
{
|
||||
"name": "Test Company Pricing",
|
||||
}
|
||||
)
|
||||
|
||||
# Create test group
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group Pricing',
|
||||
'is_company': True,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Group Pricing",
|
||||
"is_company": True,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create test user
|
||||
self.user = self.env['res.users'].create({
|
||||
'name': 'Test User Pricing',
|
||||
'login': 'testpricing@example.com',
|
||||
'company_id': self.company.id,
|
||||
'company_ids': [(6, 0, [self.company.id])],
|
||||
})
|
||||
self.user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Test User Pricing",
|
||||
"login": "testpricing@example.com",
|
||||
"company_id": self.company.id,
|
||||
"company_ids": [(6, 0, [self.company.id])],
|
||||
}
|
||||
)
|
||||
|
||||
# Get or create default tax group
|
||||
tax_group = self.env['account.tax.group'].search([
|
||||
('company_id', '=', self.company.id)
|
||||
], limit=1)
|
||||
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,
|
||||
})
|
||||
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')
|
||||
country_es = self.env.ref("base.es")
|
||||
|
||||
# Create tax (21% IVA)
|
||||
self.tax_21 = self.env['account.tax'].create({
|
||||
'name': 'IVA 21%',
|
||||
'amount': 21.0,
|
||||
'amount_type': 'percent',
|
||||
'type_tax_use': 'sale',
|
||||
'company_id': self.company.id,
|
||||
'country_id': country_es.id,
|
||||
'tax_group_id': tax_group.id,
|
||||
})
|
||||
self.tax_21 = self.env["account.tax"].create(
|
||||
{
|
||||
"name": "IVA 21%",
|
||||
"amount": 21.0,
|
||||
"amount_type": "percent",
|
||||
"type_tax_use": "sale",
|
||||
"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',
|
||||
'company_id': self.company.id,
|
||||
'country_id': country_es.id,
|
||||
'tax_group_id': tax_group.id,
|
||||
})
|
||||
self.tax_10 = self.env["account.tax"].create(
|
||||
{
|
||||
"name": "IVA 10%",
|
||||
"amount": 10.0,
|
||||
"amount_type": "percent",
|
||||
"type_tax_use": "sale",
|
||||
"company_id": self.company.id,
|
||||
"country_id": country_es.id,
|
||||
"tax_group_id": tax_group.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create fiscal position (maps 21% to 10%)
|
||||
self.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': self.fiscal_position.id,
|
||||
'tax_src_id': self.tax_21.id,
|
||||
'tax_dest_id': self.tax_10.id,
|
||||
})
|
||||
self.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": self.fiscal_position.id,
|
||||
"tax_src_id": self.tax_21.id,
|
||||
"tax_dest_id": self.tax_10.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create product category
|
||||
self.category = self.env['product.category'].create({
|
||||
'name': 'Test Category Pricing',
|
||||
})
|
||||
self.category = self.env["product.category"].create(
|
||||
{
|
||||
"name": "Test Category Pricing",
|
||||
}
|
||||
)
|
||||
|
||||
# Create test products with different tax configurations
|
||||
self.product_with_tax = 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_with_tax = 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_without_tax = self.env['product.product'].create({
|
||||
'name': 'Product Without Tax',
|
||||
'list_price': 50.0,
|
||||
'categ_id': self.category.id,
|
||||
'taxes_id': False,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
self.product_without_tax = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Product Without Tax",
|
||||
"list_price": 50.0,
|
||||
"categ_id": self.category.id,
|
||||
"taxes_id": False,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create pricelist with discount
|
||||
self.pricelist_with_discount = self.env['product.pricelist'].create({
|
||||
'name': 'Test Pricelist 10% Discount',
|
||||
'company_id': self.company.id,
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 10.0, # 10% discount
|
||||
'applied_on': '3_global',
|
||||
})],
|
||||
})
|
||||
self.pricelist_with_discount = self.env["product.pricelist"].create(
|
||||
{
|
||||
"name": "Test Pricelist 10% Discount",
|
||||
"company_id": self.company.id,
|
||||
"item_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"compute_price": "percentage",
|
||||
"percent_price": 10.0, # 10% discount
|
||||
"applied_on": "3_global",
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
# Create pricelist without discount
|
||||
self.pricelist_no_discount = self.env['product.pricelist'].create({
|
||||
'name': 'Test Pricelist No Discount',
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
self.pricelist_no_discount = self.env["product.pricelist"].create(
|
||||
{
|
||||
"name": "Test Pricelist No Discount",
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create group order
|
||||
self.group_order = self.env['group.order'].create({
|
||||
'name': 'Test Order Pricing',
|
||||
'state': 'open',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'product_ids': [(6, 0, [self.product_with_tax.id, self.product_without_tax.id])],
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
self.group_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Test Order Pricing",
|
||||
"state": "open",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"product_ids": [
|
||||
(6, 0, [self.product_with_tax.id, self.product_without_tax.id])
|
||||
],
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_add_to_cart_basic_price_without_tax(self):
|
||||
"""Test basic price calculation for product without taxes."""
|
||||
|
|
@ -148,10 +183,14 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
fposition=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result['value'], 50.0,
|
||||
"Product without tax should have price = list_price")
|
||||
self.assertEqual(result.get('discount', 0), 0,
|
||||
"No discount pricelist should have 0% discount")
|
||||
self.assertEqual(
|
||||
result["value"], 50.0, "Product without tax should have price = list_price"
|
||||
)
|
||||
self.assertEqual(
|
||||
result.get("discount", 0),
|
||||
0,
|
||||
"No discount pricelist should have 0% discount",
|
||||
)
|
||||
|
||||
def test_add_to_cart_with_pricelist_discount(self):
|
||||
"""Test that discounted prices are calculated correctly."""
|
||||
|
|
@ -165,10 +204,14 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
# OCA addon returns price without taxes by default
|
||||
expected_price = 100.0 * 0.9 # 90.0
|
||||
|
||||
self.assertIn('value', result, "Result must contain 'value' key")
|
||||
self.assertIn('tax_included', result, "Result must contain 'tax_included' key")
|
||||
self.assertAlmostEqual(result['value'], expected_price, places=2,
|
||||
msg=f"Expected {expected_price}, got {result['value']}")
|
||||
self.assertIn("value", result, "Result must contain 'value' key")
|
||||
self.assertIn("tax_included", result, "Result must contain 'tax_included' key")
|
||||
self.assertAlmostEqual(
|
||||
result["value"],
|
||||
expected_price,
|
||||
places=2,
|
||||
msg=f"Expected {expected_price}, got {result['value']}",
|
||||
)
|
||||
|
||||
def test_add_to_cart_with_fiscal_position(self):
|
||||
"""Test fiscal position maps taxes correctly (21% -> 10%)."""
|
||||
|
|
@ -187,10 +230,10 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
|
||||
# Both should return base price (100.0) without tax by default
|
||||
# Tax mapping only affects tax calculation, not the base price returned
|
||||
self.assertIn('value', result_without_fp, "Result must contain 'value'")
|
||||
self.assertIn('value', result_with_fp, "Result must contain 'value'")
|
||||
self.assertEqual(result_without_fp['value'], 100.0)
|
||||
self.assertEqual(result_with_fp['value'], 100.0)
|
||||
self.assertIn("value", result_without_fp, "Result must contain 'value'")
|
||||
self.assertIn("value", result_with_fp, "Result must contain 'value'")
|
||||
self.assertEqual(result_without_fp["value"], 100.0)
|
||||
self.assertEqual(result_with_fp["value"], 100.0)
|
||||
|
||||
def test_add_to_cart_with_tax_included(self):
|
||||
"""Test price calculation returns tax_included flag correctly."""
|
||||
|
|
@ -202,22 +245,32 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
)
|
||||
|
||||
# By default, tax is not included in price
|
||||
self.assertIn('tax_included', result)
|
||||
self.assertEqual(result['value'], 100.0, "Price should be base price without tax")
|
||||
self.assertIn("tax_included", result)
|
||||
self.assertEqual(
|
||||
result["value"], 100.0, "Price should be base price without tax"
|
||||
)
|
||||
|
||||
def test_add_to_cart_with_quantity_discount(self):
|
||||
"""Test quantity-based discounts if applicable."""
|
||||
# Create pricelist with quantity-based rule
|
||||
pricelist_qty = self.env['product.pricelist'].create({
|
||||
'name': 'Quantity Discount Pricelist',
|
||||
'company_id': self.company.id,
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 20.0, # 20% discount
|
||||
'min_quantity': 5.0,
|
||||
'applied_on': '3_global',
|
||||
})],
|
||||
})
|
||||
pricelist_qty = self.env["product.pricelist"].create(
|
||||
{
|
||||
"name": "Quantity Discount Pricelist",
|
||||
"company_id": self.company.id,
|
||||
"item_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"compute_price": "percentage",
|
||||
"percent_price": 20.0, # 20% discount
|
||||
"min_quantity": 5.0,
|
||||
"applied_on": "3_global",
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
# Quantity 1: No discount
|
||||
result_qty_1 = self.product_with_tax._get_price(
|
||||
|
|
@ -235,8 +288,8 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
|
||||
# Qty 1: 100.0 (no discount, no tax in value)
|
||||
# Qty 5: 100 * 0.8 = 80.0 (with 20% discount, no tax in value)
|
||||
self.assertAlmostEqual(result_qty_1['value'], 100.0, places=2)
|
||||
self.assertAlmostEqual(result_qty_5['value'], 80.0, places=2)
|
||||
self.assertAlmostEqual(result_qty_1["value"], 100.0, places=2)
|
||||
self.assertAlmostEqual(result_qty_5["value"], 80.0, places=2)
|
||||
|
||||
def test_add_to_cart_price_fallback_no_pricelist(self):
|
||||
"""Test fallback to list_price when pricelist is not available."""
|
||||
|
|
@ -251,21 +304,25 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
# Should return list_price with taxes (fallback behavior)
|
||||
# This depends on OCA addon implementation
|
||||
self.assertIsNotNone(result, "Should not fail when pricelist is False")
|
||||
self.assertIn('value', result, "Result should contain 'value' key")
|
||||
self.assertIn("value", result, "Result should contain 'value' key")
|
||||
|
||||
def test_add_to_cart_price_fallback_no_variant(self):
|
||||
"""Test handling when product has no variants."""
|
||||
# Create product template without variants
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Product Without Variant',
|
||||
'list_price': 75.0,
|
||||
'categ_id': self.category.id,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
product_template = self.env["product.template"].create(
|
||||
{
|
||||
"name": "Product Without Variant",
|
||||
"list_price": 75.0,
|
||||
"categ_id": self.category.id,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Should have auto-created variant
|
||||
self.assertTrue(product_template.product_variant_ids,
|
||||
"Product template should have at least one variant")
|
||||
self.assertTrue(
|
||||
product_template.product_variant_ids,
|
||||
"Product template should have at least one variant",
|
||||
)
|
||||
|
||||
variant = product_template.product_variant_ids[0]
|
||||
result = variant._get_price(
|
||||
|
|
@ -275,7 +332,7 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
)
|
||||
|
||||
self.assertIsNotNone(result, "Should handle product with auto-created variant")
|
||||
self.assertAlmostEqual(result['value'], 75.0, places=2)
|
||||
self.assertAlmostEqual(result["value"], 75.0, places=2)
|
||||
|
||||
def test_product_price_info_structure(self):
|
||||
"""Test product_price_info dict structure returned by _get_price."""
|
||||
|
|
@ -286,16 +343,17 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
)
|
||||
|
||||
# Verify structure
|
||||
self.assertIn('value', result, "Result must contain 'value' key")
|
||||
self.assertIsInstance(result['value'], (int, float),
|
||||
"Price value must be numeric")
|
||||
self.assertIn("value", result, "Result must contain 'value' key")
|
||||
self.assertIsInstance(
|
||||
result["value"], (int, float), "Price value must be numeric"
|
||||
)
|
||||
|
||||
# Optional keys (depends on OCA addon version)
|
||||
if 'discount' in result:
|
||||
self.assertIsInstance(result['discount'], (int, float))
|
||||
if "discount" in result:
|
||||
self.assertIsInstance(result["discount"], (int, float))
|
||||
|
||||
if 'original_value' in result:
|
||||
self.assertIsInstance(result['original_value'], (int, float))
|
||||
if "original_value" in result:
|
||||
self.assertIsInstance(result["original_value"], (int, float))
|
||||
|
||||
def test_discounted_price_visual_comparison(self):
|
||||
"""Test comparison of original vs discounted price for UI display."""
|
||||
|
|
@ -306,11 +364,14 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
)
|
||||
|
||||
# When there's a discount, original_value should be higher than value
|
||||
if result.get('discount', 0) > 0:
|
||||
original = result.get('original_value', result['value'])
|
||||
discounted = result['value']
|
||||
self.assertGreater(original, discounted,
|
||||
"Original price should be higher than discounted price")
|
||||
if result.get("discount", 0) > 0:
|
||||
original = result.get("original_value", result["value"])
|
||||
discounted = result["value"]
|
||||
self.assertGreater(
|
||||
original,
|
||||
discounted,
|
||||
"Original price should be higher than discounted price",
|
||||
)
|
||||
|
||||
def test_price_calculation_with_multiple_taxes(self):
|
||||
"""Test product with multiple taxes applied."""
|
||||
|
|
@ -319,23 +380,27 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
country = self.tax_21.country_id
|
||||
|
||||
# Create additional tax
|
||||
tax_extra = self.env['account.tax'].create({
|
||||
'name': 'Extra Tax 5%',
|
||||
'amount': 5.0,
|
||||
'amount_type': 'percent',
|
||||
'type_tax_use': 'sale',
|
||||
'company_id': self.company.id,
|
||||
'country_id': country.id,
|
||||
'tax_group_id': tax_group.id,
|
||||
})
|
||||
tax_extra = self.env["account.tax"].create(
|
||||
{
|
||||
"name": "Extra Tax 5%",
|
||||
"amount": 5.0,
|
||||
"amount_type": "percent",
|
||||
"type_tax_use": "sale",
|
||||
"company_id": self.company.id,
|
||||
"country_id": country.id,
|
||||
"tax_group_id": tax_group.id,
|
||||
}
|
||||
)
|
||||
|
||||
product_multi_tax = 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, tax_extra.id])],
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
product_multi_tax = 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, tax_extra.id])],
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
result = product_multi_tax._get_price(
|
||||
qty=1.0,
|
||||
|
|
@ -344,22 +409,27 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
)
|
||||
|
||||
# Base price 100.0 (taxes not included in value by default)
|
||||
self.assertEqual(result['value'], 100.0,
|
||||
msg="Should return base price (taxes applied separately)")
|
||||
self.assertEqual(
|
||||
result["value"],
|
||||
100.0,
|
||||
msg="Should return base price (taxes applied separately)",
|
||||
)
|
||||
|
||||
def test_price_currency_handling(self):
|
||||
"""Test price calculation with different currencies."""
|
||||
# Get or use existing EUR currency
|
||||
eur = self.env['res.currency'].search([('name', '=', 'EUR')], limit=1)
|
||||
eur = self.env["res.currency"].search([("name", "=", "EUR")], limit=1)
|
||||
if not eur:
|
||||
self.skipTest("EUR currency not available in test database")
|
||||
|
||||
# Create pricelist with EUR
|
||||
pricelist_eur = self.env['product.pricelist'].create({
|
||||
'name': 'EUR Pricelist',
|
||||
'currency_id': eur.id,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
pricelist_eur = self.env["product.pricelist"].create(
|
||||
{
|
||||
"name": "EUR Pricelist",
|
||||
"currency_id": eur.id,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
result = self.product_with_tax._get_price(
|
||||
qty=1.0,
|
||||
|
|
@ -368,7 +438,7 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
)
|
||||
|
||||
self.assertIsNotNone(result, "Should handle different currency pricelist")
|
||||
self.assertIn('value', result)
|
||||
self.assertIn("value", result)
|
||||
|
||||
def test_price_consistency_across_calls(self):
|
||||
"""Test that multiple calls with same params return same price."""
|
||||
|
|
@ -384,17 +454,22 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
fposition=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result1['value'], result2['value'],
|
||||
"Price calculation should be deterministic")
|
||||
self.assertEqual(
|
||||
result1["value"],
|
||||
result2["value"],
|
||||
"Price calculation should be deterministic",
|
||||
)
|
||||
|
||||
def test_zero_price_product(self):
|
||||
"""Test handling of free products (price = 0)."""
|
||||
free_product = self.env['product.product'].create({
|
||||
'name': 'Free Product',
|
||||
'list_price': 0.0,
|
||||
'categ_id': self.category.id,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
free_product = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Free Product",
|
||||
"list_price": 0.0,
|
||||
"categ_id": self.category.id,
|
||||
"company_id": self.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
result = free_product._get_price(
|
||||
qty=1.0,
|
||||
|
|
@ -402,8 +477,7 @@ class TestPricingWithPricelist(TransactionCase):
|
|||
fposition=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result['value'], 0.0,
|
||||
"Free product should have price = 0")
|
||||
self.assertEqual(result["value"], 0.0, "Free product should have price = 0")
|
||||
|
||||
def test_negative_quantity_handling(self):
|
||||
"""Test that negative quantities are handled properly."""
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ Coverage:
|
|||
- Ordering and deduplication
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
|
@ -27,81 +28,105 @@ class TestProductDiscoveryUnion(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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,
|
||||
})
|
||||
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.category1 = self.env["product.category"].create(
|
||||
{
|
||||
"name": "Category 1",
|
||||
}
|
||||
)
|
||||
|
||||
self.category2 = self.env['product.category'].create({
|
||||
'name': 'Category 2',
|
||||
})
|
||||
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,
|
||||
})
|
||||
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,
|
||||
})
|
||||
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,
|
||||
})
|
||||
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,
|
||||
})
|
||||
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',
|
||||
})
|
||||
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."""
|
||||
|
|
@ -145,14 +170,16 @@ class TestProductDiscoveryUnion(TransactionCase):
|
|||
|
||||
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,
|
||||
})
|
||||
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
|
||||
|
|
@ -162,14 +189,16 @@ class TestProductDiscoveryUnion(TransactionCase):
|
|||
|
||||
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,
|
||||
})
|
||||
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
|
||||
|
|
@ -183,76 +212,96 @@ class TestDeepCategoryHierarchies(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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_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_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_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_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,
|
||||
})
|
||||
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_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_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,
|
||||
})
|
||||
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',
|
||||
})
|
||||
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."""
|
||||
|
|
@ -307,33 +356,41 @@ class TestEmptySourcesDiscovery(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Group",
|
||||
"is_company": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.category = self.env['product.category'].create({
|
||||
'name': 'Empty Category',
|
||||
})
|
||||
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,
|
||||
})
|
||||
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',
|
||||
})
|
||||
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."""
|
||||
|
|
@ -371,39 +428,47 @@ class TestProductDiscoveryOrdering(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Group",
|
||||
"is_company": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.category = self.env['product.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
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,
|
||||
})
|
||||
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',
|
||||
})
|
||||
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."""
|
||||
|
|
@ -413,10 +478,7 @@ class TestProductDiscoveryOrdering(TransactionCase):
|
|||
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]
|
||||
)
|
||||
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."""
|
||||
|
|
@ -427,6 +489,6 @@ class TestProductDiscoveryOrdering(TransactionCase):
|
|||
# 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 = set(p.id for p in discovered)
|
||||
expected_ids = set(p.id for p in self.products)
|
||||
discovered_ids = {p.id for p in discovered}
|
||||
expected_ids = {p.id for p in self.products}
|
||||
self.assertEqual(discovered_ids, expected_ids)
|
||||
|
|
|
|||
|
|
@ -1,91 +1,106 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestProductExtension(TransactionCase):
|
||||
'''Test suite para las extensiones de product.template.'''
|
||||
"""Test suite para las extensiones de product.template."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestProductExtension, self).setUp()
|
||||
self.product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
})
|
||||
self.order = self.env['group.order'].create({
|
||||
'name': 'Test Order',
|
||||
'product_ids': [(4, self.product.id)]
|
||||
})
|
||||
super().setUp()
|
||||
self.product = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product",
|
||||
}
|
||||
)
|
||||
self.order = self.env["group.order"].create(
|
||||
{"name": "Test Order", "product_ids": [(4, self.product.id)]}
|
||||
)
|
||||
|
||||
def test_product_template_group_order_ids_field_exists(self):
|
||||
'''Test que el campo group_order_ids existe en product.template.'''
|
||||
"""Test que el campo group_order_ids existe en product.template."""
|
||||
product_template = self.product.product_tmpl_id
|
||||
|
||||
# El campo debe existir y ser readonly
|
||||
self.assertTrue(hasattr(product_template, 'group_order_ids'))
|
||||
self.assertTrue(hasattr(product_template, "group_order_ids"))
|
||||
|
||||
def test_product_group_order_ids_readonly(self):
|
||||
""" Test that group_order_ids is a readonly field """
|
||||
field = self.env['product.template']._fields['group_order_ids']
|
||||
"""Test that group_order_ids is a readonly field"""
|
||||
field = self.env["product.template"]._fields["group_order_ids"]
|
||||
self.assertTrue(field.readonly)
|
||||
|
||||
def test_product_group_order_ids_reverse_lookup(self):
|
||||
""" Test that adding a product to an order reflects in group_order_ids """
|
||||
"""Test that adding a product to an order reflects in group_order_ids"""
|
||||
related_orders = self.product.product_tmpl_id.group_order_ids
|
||||
self.assertIn(self.order, related_orders)
|
||||
|
||||
def test_product_group_order_ids_empty_by_default(self):
|
||||
""" Test that a new product has no group orders """
|
||||
new_product = self.env['product.product'].create({'name': 'New Product'})
|
||||
"""Test that a new product has no group orders"""
|
||||
new_product = self.env["product.product"].create({"name": "New Product"})
|
||||
self.assertFalse(new_product.product_tmpl_id.group_order_ids)
|
||||
|
||||
def test_product_group_order_ids_multiple_orders(self):
|
||||
""" Test that group_order_ids can contain multiple orders """
|
||||
order2 = self.env['group.order'].create({
|
||||
'name': 'Test Order 2',
|
||||
'product_ids': [(4, self.product.id)]
|
||||
})
|
||||
"""Test that group_order_ids can contain multiple orders"""
|
||||
order2 = self.env["group.order"].create(
|
||||
{"name": "Test Order 2", "product_ids": [(4, self.product.id)]}
|
||||
)
|
||||
self.assertIn(self.order, self.product.product_tmpl_id.group_order_ids)
|
||||
self.assertIn(order2, self.product.product_tmpl_id.group_order_ids)
|
||||
|
||||
def test_product_group_order_ids_empty_after_remove_from_order(self):
|
||||
""" Test that group_order_ids is empty after removing the product from all orders """
|
||||
self.order.write({'product_ids': [(3, self.product.id)]})
|
||||
"""Test that group_order_ids is empty after removing the product from all orders"""
|
||||
self.order.write({"product_ids": [(3, self.product.id)]})
|
||||
self.assertFalse(self.product.product_tmpl_id.group_order_ids)
|
||||
|
||||
def test_product_group_order_ids_with_multiple_products(self):
|
||||
""" Test group_order_ids with multiple products in one order """
|
||||
product2 = self.env['product.product'].create({'name': 'Test Product 2'})
|
||||
self.order.write({'product_ids': [
|
||||
(4, self.product.id),
|
||||
(4, product2.id)
|
||||
]})
|
||||
"""Test group_order_ids with multiple products in one order"""
|
||||
product2 = self.env["product.product"].create({"name": "Test Product 2"})
|
||||
self.order.write({"product_ids": [(4, self.product.id), (4, product2.id)]})
|
||||
self.assertIn(self.order, self.product.product_tmpl_id.group_order_ids)
|
||||
self.assertIn(self.order, product2.product_tmpl_id.group_order_ids)
|
||||
|
||||
def test_product_with_variants_group_order_ids(self):
|
||||
""" Test that group_order_ids works correctly with product variants """
|
||||
"""Test that group_order_ids works correctly with product variants"""
|
||||
# Create a product template with two variants
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Product with Variants',
|
||||
'attribute_line_ids': [(0, 0, {
|
||||
'attribute_id': self.env.ref('product.product_attribute_1').id,
|
||||
'value_ids': [
|
||||
(4, self.env.ref('product.product_attribute_value_1').id),
|
||||
(4, self.env.ref('product.product_attribute_value_2').id)
|
||||
]
|
||||
})]
|
||||
})
|
||||
product_template = self.env["product.template"].create(
|
||||
{
|
||||
"name": "Product with Variants",
|
||||
"attribute_line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"attribute_id": self.env.ref(
|
||||
"product.product_attribute_1"
|
||||
).id,
|
||||
"value_ids": [
|
||||
(
|
||||
4,
|
||||
self.env.ref(
|
||||
"product.product_attribute_value_1"
|
||||
).id,
|
||||
),
|
||||
(
|
||||
4,
|
||||
self.env.ref(
|
||||
"product.product_attribute_value_2"
|
||||
).id,
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
variant1 = product_template.product_variant_ids[0]
|
||||
variant2 = product_template.product_variant_ids[1]
|
||||
|
||||
# Add one variant to an order (store variant id, not template id)
|
||||
order_with_variant = self.env['group.order'].create({
|
||||
'name': 'Order with Variant',
|
||||
'product_ids': [(4, variant1.id)]
|
||||
})
|
||||
order_with_variant = self.env["group.order"].create(
|
||||
{"name": "Order with Variant", "product_ids": [(4, variant1.id)]}
|
||||
)
|
||||
|
||||
# Check that the order appears in the group_order_ids of the template
|
||||
self.assertIn(order_with_variant, product_template.group_order_ids)
|
||||
|
|
|
|||
|
|
@ -1,145 +1,170 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestGroupOrderRecordRules(TransactionCase):
|
||||
'''Test suite para record rules de multicompañía en group.order.'''
|
||||
"""Test suite para record rules de multicompañía en group.order."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Crear dos compañías
|
||||
self.company1 = self.env['res.company'].create({
|
||||
'name': 'Company 1',
|
||||
})
|
||||
self.company2 = self.env['res.company'].create({
|
||||
'name': 'Company 2',
|
||||
})
|
||||
self.company1 = self.env["res.company"].create(
|
||||
{
|
||||
"name": "Company 1",
|
||||
}
|
||||
)
|
||||
self.company2 = self.env["res.company"].create(
|
||||
{
|
||||
"name": "Company 2",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear usuarios para cada compañía
|
||||
self.user_company1 = self.env['res.users'].create({
|
||||
'name': 'User Company 1',
|
||||
'login': 'user_c1',
|
||||
'password': 'pass123',
|
||||
'company_id': self.company1.id,
|
||||
'company_ids': [(6, 0, [self.company1.id])],
|
||||
})
|
||||
self.user_company1 = self.env["res.users"].create(
|
||||
{
|
||||
"name": "User Company 1",
|
||||
"login": "user_c1",
|
||||
"password": "pass123",
|
||||
"company_id": self.company1.id,
|
||||
"company_ids": [(6, 0, [self.company1.id])],
|
||||
}
|
||||
)
|
||||
|
||||
self.user_company2 = self.env['res.users'].create({
|
||||
'name': 'User Company 2',
|
||||
'login': 'user_c2',
|
||||
'password': 'pass123',
|
||||
'company_id': self.company2.id,
|
||||
'company_ids': [(6, 0, [self.company2.id])],
|
||||
})
|
||||
self.user_company2 = self.env["res.users"].create(
|
||||
{
|
||||
"name": "User Company 2",
|
||||
"login": "user_c2",
|
||||
"password": "pass123",
|
||||
"company_id": self.company2.id,
|
||||
"company_ids": [(6, 0, [self.company2.id])],
|
||||
}
|
||||
)
|
||||
|
||||
# Crear admin con acceso a ambas compañías
|
||||
self.admin_user = self.env['res.users'].create({
|
||||
'name': 'Admin Both',
|
||||
'login': 'admin_both',
|
||||
'password': 'pass123',
|
||||
'company_id': self.company1.id,
|
||||
'company_ids': [(6, 0, [self.company1.id, self.company2.id])],
|
||||
})
|
||||
self.admin_user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Admin Both",
|
||||
"login": "admin_both",
|
||||
"password": "pass123",
|
||||
"company_id": self.company1.id,
|
||||
"company_ids": [(6, 0, [self.company1.id, self.company2.id])],
|
||||
}
|
||||
)
|
||||
|
||||
# Crear grupos en cada compañía
|
||||
self.group1 = self.env['res.partner'].create({
|
||||
'name': 'Grupo Company 1',
|
||||
'is_company': True,
|
||||
'email': 'grupo1@test.com',
|
||||
'company_id': self.company1.id,
|
||||
})
|
||||
self.group1 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo Company 1",
|
||||
"is_company": True,
|
||||
"email": "grupo1@test.com",
|
||||
"company_id": self.company1.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.group2 = self.env['res.partner'].create({
|
||||
'name': 'Grupo Company 2',
|
||||
'is_company': True,
|
||||
'email': 'grupo2@test.com',
|
||||
'company_id': self.company2.id,
|
||||
})
|
||||
self.group2 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo Company 2",
|
||||
"is_company": True,
|
||||
"email": "grupo2@test.com",
|
||||
"company_id": self.company2.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Crear órdenes en cada compañía
|
||||
self.order1 = self.env['group.order'].create({
|
||||
'name': 'Pedido Company 1',
|
||||
'group_ids': [(6, 0, [self.group1.id])],
|
||||
'company_id': self.company1.id,
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
self.order1 = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Company 1",
|
||||
"group_ids": [(6, 0, [self.group1.id])],
|
||||
"company_id": self.company1.id,
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
self.order2 = self.env['group.order'].create({
|
||||
'name': 'Pedido Company 2',
|
||||
'group_ids': [(6, 0, [self.group2.id])],
|
||||
'company_id': self.company2.id,
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date(),
|
||||
'end_date': (datetime.now() + timedelta(days=7)).date(),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
self.order2 = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Pedido Company 2",
|
||||
"group_ids": [(6, 0, [self.group2.id])],
|
||||
"company_id": self.company2.id,
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date(),
|
||||
"end_date": (datetime.now() + timedelta(days=7)).date(),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
def test_user_company1_can_read_own_orders(self):
|
||||
'''Test que usuario de Company 1 puede leer sus propias órdenes.'''
|
||||
orders = self.env['group.order'].with_user(
|
||||
self.user_company1
|
||||
).search([('company_id', '=', self.company1.id)])
|
||||
"""Test que usuario de Company 1 puede leer sus propias órdenes."""
|
||||
orders = (
|
||||
self.env["group.order"]
|
||||
.with_user(self.user_company1)
|
||||
.search([("company_id", "=", self.company1.id)])
|
||||
)
|
||||
|
||||
self.assertIn(self.order1, orders)
|
||||
|
||||
def test_user_company1_cannot_read_company2_orders(self):
|
||||
'''Test que usuario de Company 1 NO puede leer órdenes de Company 2.'''
|
||||
orders = self.env['group.order'].with_user(
|
||||
self.user_company1
|
||||
).search([('company_id', '=', self.company2.id)])
|
||||
"""Test que usuario de Company 1 NO puede leer órdenes de Company 2."""
|
||||
orders = (
|
||||
self.env["group.order"]
|
||||
.with_user(self.user_company1)
|
||||
.search([("company_id", "=", self.company2.id)])
|
||||
)
|
||||
|
||||
self.assertNotIn(self.order2, orders)
|
||||
self.assertEqual(len(orders), 0)
|
||||
|
||||
def test_admin_can_read_all_orders(self):
|
||||
'''Test que admin con acceso a ambas compañías ve todas las órdenes.'''
|
||||
orders = self.env['group.order'].with_user(
|
||||
self.admin_user
|
||||
).search([])
|
||||
"""Test que admin con acceso a ambas compañías ve todas las órdenes."""
|
||||
orders = self.env["group.order"].with_user(self.admin_user).search([])
|
||||
|
||||
self.assertIn(self.order1, orders)
|
||||
self.assertIn(self.order2, orders)
|
||||
|
||||
def test_user_cannot_write_other_company_order(self):
|
||||
'''Test que usuario no puede escribir en orden de otra compañía.'''
|
||||
"""Test que usuario no puede escribir en orden de otra compañía."""
|
||||
with self.assertRaises(AccessError):
|
||||
self.order2.with_user(self.user_company1).write({
|
||||
'name': 'Intentando cambiar nombre',
|
||||
})
|
||||
self.order2.with_user(self.user_company1).write(
|
||||
{
|
||||
"name": "Intentando cambiar nombre",
|
||||
}
|
||||
)
|
||||
|
||||
def test_record_rule_filters_search(self):
|
||||
'''Test que búsqueda automáticamente filtra por record rule.'''
|
||||
"""Test que búsqueda automáticamente filtra por record rule."""
|
||||
# Usuario de Company 1 busca todas las órdenes
|
||||
orders_c1 = self.env['group.order'].with_user(
|
||||
self.user_company1
|
||||
).search([('state', '=', 'draft')])
|
||||
orders_c1 = (
|
||||
self.env["group.order"]
|
||||
.with_user(self.user_company1)
|
||||
.search([("state", "=", "draft")])
|
||||
)
|
||||
|
||||
# Solo debe ver su orden
|
||||
self.assertEqual(len(orders_c1), 1)
|
||||
self.assertEqual(orders_c1[0], self.order1)
|
||||
|
||||
def test_cross_company_access_denied(self):
|
||||
'''Test que acceso entre compañías es denegado.'''
|
||||
"""Test que acceso entre compañías es denegado."""
|
||||
# Usuario company1 intenta acceder a orden de company2
|
||||
with self.assertRaises(AccessError):
|
||||
self.order2.with_user(self.user_company1).read()
|
||||
|
||||
def test_admin_can_bypass_company_restriction(self):
|
||||
'''Test que admin puede acceder a órdenes de cualquier compañía.'''
|
||||
"""Test que admin puede acceder a órdenes de cualquier compañía."""
|
||||
# Admin lee orden de company2 sin problema
|
||||
order2_admin = self.order2.with_user(self.admin_user)
|
||||
self.assertEqual(order2_admin.name, 'Pedido Company 2')
|
||||
self.assertEqual(order2_admin.name, "Pedido Company 2")
|
||||
self.assertEqual(order2_admin.company_id, self.company2)
|
||||
|
|
|
|||
|
|
@ -5,32 +5,38 @@ from odoo.tests.common import TransactionCase
|
|||
|
||||
|
||||
class TestResPartnerExtension(TransactionCase):
|
||||
'''Test suite para la extensión res.partner (user-group relationship).'''
|
||||
"""Test suite para la extensión res.partner (user-group relationship)."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Crear grupos (res.partner with is_company=True)
|
||||
self.group1 = self.env['res.partner'].create({
|
||||
'name': 'Grupo 1',
|
||||
'is_company': True,
|
||||
'email': 'grupo1@test.com',
|
||||
})
|
||||
self.group1 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo 1",
|
||||
"is_company": True,
|
||||
"email": "grupo1@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
self.group2 = self.env['res.partner'].create({
|
||||
'name': 'Grupo 2',
|
||||
'is_company': True,
|
||||
'email': 'grupo2@test.com',
|
||||
})
|
||||
self.group2 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Grupo 2",
|
||||
"is_company": True,
|
||||
"email": "grupo2@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Crear usuario
|
||||
self.user = self.env['res.users'].create({
|
||||
'name': 'Test User',
|
||||
'login': 'testuser@test.com',
|
||||
'email': 'testuser@test.com',
|
||||
})
|
||||
self.user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Test User",
|
||||
"login": "testuser@test.com",
|
||||
"email": "testuser@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
def test_partner_can_belong_to_groups(self):
|
||||
'''Test que un partner (usuario) puede pertenecer a múltiples grupos.'''
|
||||
"""Test que un partner (usuario) puede pertenecer a múltiples grupos."""
|
||||
partner = self.user.partner_id
|
||||
|
||||
# Agregar partner a grupos (usar campo member_ids)
|
||||
|
|
@ -42,12 +48,14 @@ class TestResPartnerExtension(TransactionCase):
|
|||
self.assertEqual(len(partner.member_ids), 2)
|
||||
|
||||
def test_group_can_have_multiple_users(self):
|
||||
'''Test que un grupo puede tener múltiples usuarios.'''
|
||||
user2 = self.env['res.users'].create({
|
||||
'name': 'Test User 2',
|
||||
'login': 'testuser2@test.com',
|
||||
'email': 'testuser2@test.com',
|
||||
})
|
||||
"""Test que un grupo puede tener múltiples usuarios."""
|
||||
user2 = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Test User 2",
|
||||
"login": "testuser2@test.com",
|
||||
"email": "testuser2@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Agregar usuarios al grupo
|
||||
self.group1.user_ids = [(6, 0, [self.user.id, user2.id])]
|
||||
|
|
@ -58,7 +66,7 @@ class TestResPartnerExtension(TransactionCase):
|
|||
self.assertEqual(len(self.group1.user_ids), 2)
|
||||
|
||||
def test_user_group_relationship_is_bidirectional(self):
|
||||
'''Test que se puede modificar la relación desde el lado del partner o el grupo.'''
|
||||
"""Test que se puede modificar la relación desde el lado del partner o el grupo."""
|
||||
partner = self.user.partner_id
|
||||
|
||||
# Opción 1: Agregar grupo al usuario (desde el lado del usuario/partner)
|
||||
|
|
@ -67,16 +75,18 @@ class TestResPartnerExtension(TransactionCase):
|
|||
|
||||
# Opción 2: Agregar usuario al grupo (desde el lado del grupo)
|
||||
# Nota: Esto es una relación Many2many independiente
|
||||
user2 = self.env['res.users'].create({
|
||||
'name': 'Test User 2',
|
||||
'login': 'testuser2@test.com',
|
||||
'email': 'testuser2@test.com',
|
||||
})
|
||||
user2 = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Test User 2",
|
||||
"login": "testuser2@test.com",
|
||||
"email": "testuser2@test.com",
|
||||
}
|
||||
)
|
||||
self.group2.user_ids = [(6, 0, [user2.id])]
|
||||
self.assertIn(user2, self.group2.user_ids)
|
||||
|
||||
def test_empty_group_ids(self):
|
||||
'''Test que un partner sin grupos tiene group_ids vacío.'''
|
||||
"""Test que un partner sin grupos tiene group_ids vacío."""
|
||||
partner = self.user.partner_id
|
||||
|
||||
# Sin agregar a ningún grupo
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ draft sale orders.
|
|||
See: website_sale_aplicoop/controllers/website_sale.py
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
|
@ -23,60 +24,72 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
super().setUp()
|
||||
|
||||
# Create a consumer group
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
'email': 'group@test.com',
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Group",
|
||||
"is_company": True,
|
||||
"email": "group@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Create a group member (user partner)
|
||||
self.member_partner = self.env['res.partner'].create({
|
||||
'name': 'Group Member Partner',
|
||||
'email': 'member@test.com',
|
||||
})
|
||||
self.member_partner = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Group Member Partner",
|
||||
"email": "member@test.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Add member to group
|
||||
self.group.member_ids = [(4, self.member_partner.id)]
|
||||
|
||||
# Create test user
|
||||
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.user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Test User",
|
||||
"login": "testuser@test.com",
|
||||
"email": "testuser@test.com",
|
||||
"partner_id": self.member_partner.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create a group order
|
||||
start_date = datetime.now().date()
|
||||
end_date = start_date + timedelta(days=7)
|
||||
|
||||
self.group_order = self.env['group.order'].create({
|
||||
'name': 'Test Group Order',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': start_date,
|
||||
'end_date': end_date,
|
||||
'period': 'weekly',
|
||||
'pickup_day': '3', # Wednesday
|
||||
'pickup_date': start_date + timedelta(days=3),
|
||||
'home_delivery': False,
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
self.group_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Test Group Order",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"period": "weekly",
|
||||
"pickup_day": "3", # Wednesday
|
||||
"pickup_date": start_date + timedelta(days=3),
|
||||
"home_delivery": False,
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
# Open the group order
|
||||
self.group_order.action_open()
|
||||
|
||||
# Create products for the order
|
||||
self.category = self.env['product.category'].create({
|
||||
'name': 'Test Category',
|
||||
})
|
||||
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,
|
||||
})
|
||||
self.product = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product",
|
||||
"type": "consu",
|
||||
"list_price": 10.0,
|
||||
"categ_id": self.category.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Associate product with group order
|
||||
self.group_order.product_ids = [(4, self.product.id)]
|
||||
|
|
@ -90,16 +103,16 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
"""
|
||||
# Simulate what the controller does: create order with group_order_id
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': self.group_order.id,
|
||||
'pickup_day': self.group_order.pickup_day,
|
||||
'pickup_date': self.group_order.pickup_date,
|
||||
'home_delivery': self.group_order.home_delivery,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": self.group_order.id,
|
||||
"pickup_day": self.group_order.pickup_day,
|
||||
"pickup_date": self.group_order.pickup_date,
|
||||
"home_delivery": self.group_order.home_delivery,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify the order was created with group_order_id
|
||||
self.assertIsNotNone(sale_order.id)
|
||||
|
|
@ -109,34 +122,34 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
def test_save_eskaera_draft_propagates_pickup_day(self):
|
||||
"""Test that save_eskaera_draft() propagates pickup_day correctly."""
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': self.group_order.id,
|
||||
'pickup_day': self.group_order.pickup_day,
|
||||
'pickup_date': self.group_order.pickup_date,
|
||||
'home_delivery': self.group_order.home_delivery,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": self.group_order.id,
|
||||
"pickup_day": self.group_order.pickup_day,
|
||||
"pickup_date": self.group_order.pickup_date,
|
||||
"home_delivery": self.group_order.home_delivery,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify pickup_day was propagated
|
||||
self.assertEqual(sale_order.pickup_day, '3')
|
||||
self.assertEqual(sale_order.pickup_day, "3")
|
||||
self.assertEqual(sale_order.pickup_day, self.group_order.pickup_day)
|
||||
|
||||
def test_save_eskaera_draft_propagates_pickup_date(self):
|
||||
"""Test that save_eskaera_draft() propagates pickup_date correctly."""
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': self.group_order.id,
|
||||
'pickup_day': self.group_order.pickup_day,
|
||||
'pickup_date': self.group_order.pickup_date,
|
||||
'home_delivery': self.group_order.home_delivery,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": self.group_order.id,
|
||||
"pickup_day": self.group_order.pickup_day,
|
||||
"pickup_date": self.group_order.pickup_date,
|
||||
"home_delivery": self.group_order.home_delivery,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify pickup_date was propagated
|
||||
self.assertEqual(sale_order.pickup_date, self.group_order.pickup_date)
|
||||
|
|
@ -144,33 +157,35 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
def test_save_eskaera_draft_propagates_home_delivery(self):
|
||||
"""Test that save_eskaera_draft() propagates home_delivery correctly."""
|
||||
# Create a group order with home_delivery=True
|
||||
group_order_home = self.env['group.order'].create({
|
||||
'name': 'Test Group Order with Home Delivery',
|
||||
'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',
|
||||
'pickup_date': datetime.now().date() + timedelta(days=3),
|
||||
'home_delivery': True, # Enable home delivery
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
group_order_home = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Test Group Order with Home Delivery",
|
||||
"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",
|
||||
"pickup_date": datetime.now().date() + timedelta(days=3),
|
||||
"home_delivery": True, # Enable home delivery
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
group_order_home.action_open()
|
||||
|
||||
# Test with home_delivery=True
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': group_order_home.id,
|
||||
'pickup_day': group_order_home.pickup_day,
|
||||
'pickup_date': group_order_home.pickup_date,
|
||||
'home_delivery': group_order_home.home_delivery,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": group_order_home.id,
|
||||
"pickup_day": group_order_home.pickup_day,
|
||||
"pickup_date": group_order_home.pickup_date,
|
||||
"home_delivery": group_order_home.home_delivery,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify home_delivery was propagated
|
||||
self.assertTrue(sale_order.home_delivery)
|
||||
|
|
@ -179,19 +194,19 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
def test_save_eskaera_draft_order_is_draft_state(self):
|
||||
"""Test that save_eskaera_draft() creates order in draft state."""
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': self.group_order.id,
|
||||
'pickup_day': self.group_order.pickup_day,
|
||||
'pickup_date': self.group_order.pickup_date,
|
||||
'home_delivery': self.group_order.home_delivery,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": self.group_order.id,
|
||||
"pickup_day": self.group_order.pickup_day,
|
||||
"pickup_date": self.group_order.pickup_date,
|
||||
"home_delivery": self.group_order.home_delivery,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify order is in draft state
|
||||
self.assertEqual(sale_order.state, 'draft')
|
||||
self.assertEqual(sale_order.state, "draft")
|
||||
|
||||
def test_save_eskaera_draft_multiple_fields_together(self):
|
||||
"""
|
||||
|
|
@ -201,23 +216,23 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
all group_order-related fields are propagated together.
|
||||
"""
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': self.group_order.id,
|
||||
'pickup_day': self.group_order.pickup_day,
|
||||
'pickup_date': self.group_order.pickup_date,
|
||||
'home_delivery': self.group_order.home_delivery,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": self.group_order.id,
|
||||
"pickup_day": self.group_order.pickup_day,
|
||||
"pickup_date": self.group_order.pickup_date,
|
||||
"home_delivery": self.group_order.home_delivery,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify all fields together
|
||||
self.assertEqual(sale_order.group_order_id.id, self.group_order.id)
|
||||
self.assertEqual(sale_order.pickup_day, self.group_order.pickup_day)
|
||||
self.assertEqual(sale_order.pickup_date, self.group_order.pickup_date)
|
||||
self.assertEqual(sale_order.home_delivery, self.group_order.home_delivery)
|
||||
self.assertEqual(sale_order.state, 'draft')
|
||||
self.assertEqual(sale_order.state, "draft")
|
||||
|
||||
def test_save_cart_draft_also_saves_group_order_id(self):
|
||||
"""
|
||||
|
|
@ -228,16 +243,16 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
"""
|
||||
# save_cart_draft should also include group_order_id
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': self.group_order.id,
|
||||
'pickup_day': self.group_order.pickup_day,
|
||||
'pickup_date': self.group_order.pickup_date,
|
||||
'home_delivery': self.group_order.home_delivery,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": self.group_order.id,
|
||||
"pickup_day": self.group_order.pickup_day,
|
||||
"pickup_date": self.group_order.pickup_date,
|
||||
"home_delivery": self.group_order.home_delivery,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify all fields
|
||||
self.assertEqual(sale_order.group_order_id.id, self.group_order.id)
|
||||
|
|
@ -252,13 +267,13 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
sale orders without associating them to a group order.
|
||||
"""
|
||||
order_vals = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
# No group_order_id
|
||||
}
|
||||
|
||||
sale_order = self.env['sale.order'].create(order_vals)
|
||||
sale_order = self.env["sale.order"].create(order_vals)
|
||||
|
||||
# Verify order was created without group_order_id
|
||||
self.assertIsNotNone(sale_order.id)
|
||||
|
|
@ -271,13 +286,13 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
This is a sanity check to ensure the field is properly defined in the model.
|
||||
"""
|
||||
# Verify the field exists in the model
|
||||
sale_order_model = self.env['sale.order']
|
||||
self.assertIn('group_order_id', sale_order_model._fields)
|
||||
sale_order_model = self.env["sale.order"]
|
||||
self.assertIn("group_order_id", sale_order_model._fields)
|
||||
|
||||
# Verify it's a Many2one field
|
||||
field = sale_order_model._fields['group_order_id']
|
||||
self.assertEqual(field.type, 'many2one')
|
||||
self.assertEqual(field.comodel_name, 'group.order')
|
||||
field = sale_order_model._fields["group_order_id"]
|
||||
self.assertEqual(field.type, "many2one")
|
||||
self.assertEqual(field.comodel_name, "group.order")
|
||||
|
||||
def test_different_group_orders_map_to_different_sale_orders(self):
|
||||
"""
|
||||
|
|
@ -287,42 +302,44 @@ class TestSaveOrderEndpoints(TransactionCase):
|
|||
don't accidentally share the same sale.order.
|
||||
"""
|
||||
# Create a second group order
|
||||
group_order_2 = self.env['group.order'].create({
|
||||
'name': 'Test Group Order 2',
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'type': 'regular',
|
||||
'start_date': datetime.now().date() + timedelta(days=10),
|
||||
'end_date': datetime.now().date() + timedelta(days=17),
|
||||
'period': 'weekly',
|
||||
'pickup_day': '5',
|
||||
'pickup_date': datetime.now().date() + timedelta(days=12),
|
||||
'home_delivery': True,
|
||||
'cutoff_day': '0',
|
||||
})
|
||||
group_order_2 = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Test Group Order 2",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"type": "regular",
|
||||
"start_date": datetime.now().date() + timedelta(days=10),
|
||||
"end_date": datetime.now().date() + timedelta(days=17),
|
||||
"period": "weekly",
|
||||
"pickup_day": "5",
|
||||
"pickup_date": datetime.now().date() + timedelta(days=12),
|
||||
"home_delivery": True,
|
||||
"cutoff_day": "0",
|
||||
}
|
||||
)
|
||||
|
||||
group_order_2.action_open()
|
||||
|
||||
# Create order for first group order
|
||||
order_vals_1 = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': self.group_order.id,
|
||||
'pickup_day': self.group_order.pickup_day,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": self.group_order.id,
|
||||
"pickup_day": self.group_order.pickup_day,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order_1 = self.env['sale.order'].create(order_vals_1)
|
||||
sale_order_1 = self.env["sale.order"].create(order_vals_1)
|
||||
|
||||
# Create order for second group order
|
||||
order_vals_2 = {
|
||||
'partner_id': self.member_partner.id,
|
||||
'group_order_id': group_order_2.id,
|
||||
'pickup_day': group_order_2.pickup_day,
|
||||
'order_line': [],
|
||||
'state': 'draft',
|
||||
"partner_id": self.member_partner.id,
|
||||
"group_order_id": group_order_2.id,
|
||||
"pickup_day": group_order_2.pickup_day,
|
||||
"order_line": [],
|
||||
"state": "draft",
|
||||
}
|
||||
|
||||
sale_order_2 = self.env['sale.order'].create(order_vals_2)
|
||||
sale_order_2 = self.env["sale.order"].create(order_vals_2)
|
||||
|
||||
# Verify they're different orders with different group_order_ids
|
||||
self.assertNotEqual(sale_order_1.id, sale_order_2.id)
|
||||
|
|
|
|||
|
|
@ -1,77 +1,90 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from datetime import date, timedelta
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
from datetime import date
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import _
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestTemplatesRendering(TransactionCase):
|
||||
'''Test suite to verify QWeb templates work with day_names context.
|
||||
"""Test suite to verify QWeb templates work with day_names context.
|
||||
|
||||
This test covers the fix for the issue where _() function calls
|
||||
in QWeb t-value attributes caused TypeError: 'NoneType' object is not callable.
|
||||
The fix moves day_names definition to Python controller and passes it as context.
|
||||
'''
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
'''Set up test data: create a test group order.'''
|
||||
"""Set up test data: create a test group order."""
|
||||
super().setUp()
|
||||
|
||||
# Create a test supplier
|
||||
self.supplier = self.env['res.partner'].create({
|
||||
'name': 'Test Supplier',
|
||||
'is_company': True,
|
||||
})
|
||||
self.supplier = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Supplier",
|
||||
"is_company": True,
|
||||
}
|
||||
)
|
||||
|
||||
# Create test products
|
||||
self.product = self.env['product.product'].create({
|
||||
'name': 'Test Product',
|
||||
'type': 'consu', # consumable (consu), service, or storable
|
||||
})
|
||||
self.product = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product",
|
||||
"type": "consu", # consumable (consu), service, or storable
|
||||
}
|
||||
)
|
||||
|
||||
# Create a test group
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Group",
|
||||
"is_company": True,
|
||||
}
|
||||
)
|
||||
|
||||
# Create a group order
|
||||
self.group_order = self.env['group.order'].create({
|
||||
'name': 'Test Order',
|
||||
'state': 'open',
|
||||
'supplier_ids': [(6, 0, [self.supplier.id])],
|
||||
'product_ids': [(6, 0, [self.product.id])],
|
||||
'group_ids': [(6, 0, [self.group.id])],
|
||||
'start_date': date.today(),
|
||||
'end_date': date.today() + timedelta(days=7),
|
||||
'pickup_day': '5', # Saturday
|
||||
'cutoff_day': '3', # Thursday
|
||||
})
|
||||
self.group_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Test Order",
|
||||
"state": "open",
|
||||
"supplier_ids": [(6, 0, [self.supplier.id])],
|
||||
"product_ids": [(6, 0, [self.product.id])],
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
"start_date": date.today(),
|
||||
"end_date": date.today() + timedelta(days=7),
|
||||
"pickup_day": "5", # Saturday
|
||||
"cutoff_day": "3", # Thursday
|
||||
}
|
||||
)
|
||||
|
||||
def test_eskaera_page_template_exists(self):
|
||||
'''Test that eskaera_page template compiles without errors.'''
|
||||
template = self.env.ref('website_sale_aplicoop.eskaera_page')
|
||||
"""Test that eskaera_page template compiles without errors."""
|
||||
template = self.env.ref("website_sale_aplicoop.eskaera_page")
|
||||
self.assertIsNotNone(template)
|
||||
self.assertEqual(template.type, 'qweb')
|
||||
self.assertEqual(template.type, "qweb")
|
||||
|
||||
def test_eskaera_shop_template_exists(self):
|
||||
'''Test that eskaera_shop template compiles without errors.'''
|
||||
template = self.env.ref('website_sale_aplicoop.eskaera_shop')
|
||||
"""Test that eskaera_shop template compiles without errors."""
|
||||
template = self.env.ref("website_sale_aplicoop.eskaera_shop")
|
||||
self.assertIsNotNone(template)
|
||||
self.assertEqual(template.type, 'qweb')
|
||||
self.assertEqual(template.type, "qweb")
|
||||
|
||||
def test_eskaera_checkout_template_exists(self):
|
||||
'''Test that eskaera_checkout template compiles without errors.'''
|
||||
template = self.env.ref('website_sale_aplicoop.eskaera_checkout')
|
||||
"""Test that eskaera_checkout template compiles without errors."""
|
||||
template = self.env.ref("website_sale_aplicoop.eskaera_checkout")
|
||||
self.assertIsNotNone(template)
|
||||
self.assertEqual(template.type, 'qweb')
|
||||
self.assertEqual(template.type, "qweb")
|
||||
|
||||
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
|
||||
from odoo.addons.website_sale_aplicoop.controllers.website_sale import AplicoopWebsiteSale
|
||||
from odoo.addons.website_sale_aplicoop.controllers.website_sale import (
|
||||
AplicoopWebsiteSale,
|
||||
)
|
||||
|
||||
controller = AplicoopWebsiteSale()
|
||||
day_names = controller._get_day_names(env=self.env)
|
||||
|
|
@ -86,45 +99,61 @@ class TestTemplatesRendering(TransactionCase):
|
|||
self.assertGreater(len(day_name), 0, f"Day at index {i} is empty string")
|
||||
|
||||
def test_day_names_not_using_inline_underscore(self):
|
||||
'''Test that day_names are defined in Python, not in t-value attributes.
|
||||
"""Test that day_names are defined in Python, not in t-value attributes.
|
||||
|
||||
This test ensures the fix has been applied:
|
||||
- day_names MUST be passed from controller context
|
||||
- day_names MUST NOT be defined with _() inside t-value attributes
|
||||
- Templates use day_names[index] from context, not t-set with _()
|
||||
'''
|
||||
template = self.env.ref('website_sale_aplicoop.eskaera_page')
|
||||
"""
|
||||
template = self.env.ref("website_sale_aplicoop.eskaera_page")
|
||||
# Read the template source to verify it doesn't have inline _() in t-value
|
||||
self.assertIn('day_names', template.arch_db,
|
||||
"Template must reference day_names from context")
|
||||
self.assertIn(
|
||||
"day_names",
|
||||
template.arch_db,
|
||||
"Template must reference day_names from context",
|
||||
)
|
||||
# The fix ensures no <t t-set="day_names" t-value="[_(...)]"/> exists
|
||||
# which was causing the NoneType error
|
||||
|
||||
def test_eskaera_checkout_summary_template_exists(self):
|
||||
'''Test that eskaera_checkout_summary sub-template exists.'''
|
||||
template = self.env.ref('website_sale_aplicoop.eskaera_checkout_summary')
|
||||
"""Test that eskaera_checkout_summary sub-template exists."""
|
||||
template = self.env.ref("website_sale_aplicoop.eskaera_checkout_summary")
|
||||
self.assertIsNotNone(template)
|
||||
self.assertEqual(template.type, 'qweb')
|
||||
self.assertEqual(template.type, "qweb")
|
||||
# Verify it has the expected structure
|
||||
self.assertIn('checkout-summary-table', template.arch_db,
|
||||
"Template must have checkout-summary-table id")
|
||||
self.assertIn('Product', template.arch_db,
|
||||
"Template must have Product label for translation")
|
||||
self.assertIn('Quantity', template.arch_db,
|
||||
"Template must have Quantity label for translation")
|
||||
self.assertIn('Price', template.arch_db,
|
||||
"Template must have Price label for translation")
|
||||
self.assertIn('Subtotal', template.arch_db,
|
||||
"Template must have Subtotal label for translation")
|
||||
self.assertIn(
|
||||
"checkout-summary-table",
|
||||
template.arch_db,
|
||||
"Template must have checkout-summary-table id",
|
||||
)
|
||||
self.assertIn(
|
||||
"Product",
|
||||
template.arch_db,
|
||||
"Template must have Product label for translation",
|
||||
)
|
||||
self.assertIn(
|
||||
"Quantity",
|
||||
template.arch_db,
|
||||
"Template must have Quantity label for translation",
|
||||
)
|
||||
self.assertIn(
|
||||
"Price", template.arch_db, "Template must have Price label for translation"
|
||||
)
|
||||
self.assertIn(
|
||||
"Subtotal",
|
||||
template.arch_db,
|
||||
"Template must have Subtotal label for translation",
|
||||
)
|
||||
|
||||
def test_eskaera_checkout_summary_renders(self):
|
||||
'''Test that eskaera_checkout_summary renders without errors.'''
|
||||
template = self.env.ref('website_sale_aplicoop.eskaera_checkout_summary')
|
||||
"""Test that eskaera_checkout_summary renders without errors."""
|
||||
template = self.env.ref("website_sale_aplicoop.eskaera_checkout_summary")
|
||||
# Render the template with empty context
|
||||
html = template._render_template(template.xml_id, {})
|
||||
# Should contain the basic table structure
|
||||
self.assertIn('<table', html)
|
||||
self.assertIn('checkout-summary-table', html)
|
||||
self.assertIn('Product', html)
|
||||
self.assertIn('Quantity', html)
|
||||
self.assertIn("<table", html)
|
||||
self.assertIn("checkout-summary-table", html)
|
||||
self.assertIn("Product", html)
|
||||
self.assertIn("Quantity", html)
|
||||
self.assertIn("This order's cart is empty", html)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ Coverage:
|
|||
- group.order state transitions: illegal transitions
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
|
||||
class TestGroupOrderValidations(TransactionCase):
|
||||
|
|
@ -25,21 +27,27 @@ class TestGroupOrderValidations(TransactionCase):
|
|||
def setUp(self):
|
||||
super().setUp()
|
||||
self.company1 = self.env.company
|
||||
self.company2 = self.env['res.company'].create({
|
||||
'name': 'Company 2',
|
||||
})
|
||||
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_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,
|
||||
})
|
||||
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."""
|
||||
|
|
@ -47,32 +55,36 @@ class TestGroupOrderValidations(TransactionCase):
|
|||
|
||||
# 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',
|
||||
})
|
||||
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',
|
||||
})
|
||||
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):
|
||||
|
|
@ -81,31 +93,35 @@ class TestGroupOrderValidations(TransactionCase):
|
|||
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',
|
||||
})
|
||||
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',
|
||||
})
|
||||
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())
|
||||
|
||||
|
||||
|
|
@ -114,27 +130,31 @@ class TestGroupOrderImageFallback(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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.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=='
|
||||
test_image = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
|
||||
self.group_order.image_1920 = test_image
|
||||
self.group.image_1920 = test_image
|
||||
|
|
@ -145,7 +165,7 @@ class TestGroupOrderImageFallback(TransactionCase):
|
|||
|
||||
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=='
|
||||
test_image = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
|
||||
# Only set group image
|
||||
self.group_order.image_1920 = False
|
||||
|
|
@ -171,34 +191,42 @@ class TestGroupOrderProductCount(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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.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.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,
|
||||
})
|
||||
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."""
|
||||
|
|
@ -232,27 +260,31 @@ class TestStateTransitions(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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',
|
||||
})
|
||||
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')
|
||||
self.assertEqual(self.order.state, "draft")
|
||||
|
||||
# Calling action_close() without action_open() should fail
|
||||
with self.assertRaises((ValidationError, UserError)):
|
||||
|
|
@ -261,7 +293,7 @@ class TestStateTransitions(TransactionCase):
|
|||
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')
|
||||
self.assertEqual(self.order.state, "cancelled")
|
||||
|
||||
# Should not allow re-opening cancelled order
|
||||
with self.assertRaises((ValidationError, UserError)):
|
||||
|
|
@ -269,28 +301,28 @@ class TestStateTransitions(TransactionCase):
|
|||
|
||||
def test_legal_transition_draft_open_closed(self):
|
||||
"""Test that Draft -> Open -> Closed is allowed."""
|
||||
self.assertEqual(self.order.state, 'draft')
|
||||
self.assertEqual(self.order.state, "draft")
|
||||
|
||||
self.order.action_open()
|
||||
self.assertEqual(self.order.state, 'open')
|
||||
self.assertEqual(self.order.state, "open")
|
||||
|
||||
self.order.action_close()
|
||||
self.assertEqual(self.order.state, 'closed')
|
||||
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.assertEqual(self.order.state, "draft")
|
||||
|
||||
self.order.action_cancel()
|
||||
self.assertEqual(self.order.state, 'cancelled')
|
||||
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.assertEqual(self.order.state, "open")
|
||||
|
||||
self.order.action_cancel()
|
||||
self.assertEqual(self.order.state, 'cancelled')
|
||||
self.assertEqual(self.order.state, "cancelled")
|
||||
|
||||
|
||||
class TestUserPartnerValidation(TransactionCase):
|
||||
|
|
@ -298,31 +330,37 @@ class TestUserPartnerValidation(TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env['res.partner'].create({
|
||||
'name': 'Test Group',
|
||||
'is_company': True,
|
||||
})
|
||||
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
|
||||
})
|
||||
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()
|
||||
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',
|
||||
})
|
||||
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",
|
||||
}
|
||||
)
|
||||
|
||||
# User without partner should not have access
|
||||
# This should be validated in controller
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue