693 lines
38 KiB
Python
693 lines
38 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import datetime
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import Command
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.tests import freeze_time, tagged
|
|
from odoo.tools import format_date
|
|
|
|
from odoo.addons.sale_subscription.tests.common_sale_subscription import TestSubscriptionCommon
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestSubscriptionUpsell(TestSubscriptionCommon):
|
|
def test_upsell_no_start_date(self):
|
|
self.sub_product_tmpl.subscription_rule_ids = [(5, 0, 0)]
|
|
self.subscription_tmpl.write({
|
|
'sale_order_template_line_ids': [
|
|
Command.create({
|
|
'name': "Optional Products",
|
|
'display_type': 'line_section',
|
|
'is_optional': True,
|
|
}),
|
|
Command.create({
|
|
'name': "Option 1",
|
|
'product_id': self.product5.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom_id': self.product5.uom_id.id,
|
|
})
|
|
]
|
|
})
|
|
self.subscription.write({
|
|
'partner_id': self.partner.id,
|
|
'plan_id': self.plan_month.id,
|
|
'order_line': [Command.create({'product_id': self.product.id,
|
|
'name': "Monthly cheap",
|
|
'price_unit': 42,
|
|
'product_uom_qty': 2,
|
|
}),
|
|
Command.create({'product_id': self.product2.id,
|
|
'name': "Monthly expensive",
|
|
'price_unit': 420,
|
|
'product_uom_qty': 3,
|
|
}),
|
|
]
|
|
})
|
|
self.subscription.action_confirm()
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 6
|
|
upsell_so.start_date = False
|
|
upsell_so.action_confirm()
|
|
upsell_so._create_invoices()
|
|
self.assertEqual(self.subscription.order_line.sorted('id').mapped('product_uom_qty'), [7.0, 7.0, 8.0, 9.0])
|
|
|
|
def test_upsell_via_so(self):
|
|
# Test the upsell flow using an intermediary upsell quote.
|
|
self.sub_product_tmpl.subscription_rule_ids = [(5, 0, 0)]
|
|
self.subscription_tmpl.write({
|
|
'sale_order_template_line_ids': [
|
|
Command.create({
|
|
'name': "Optional Products",
|
|
'display_type': 'line_section',
|
|
'is_optional': True,
|
|
}),
|
|
Command.create({
|
|
'name': "Option 1",
|
|
'product_id': self.product5.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom_id': self.product5.uom_id.id,
|
|
})
|
|
]
|
|
})
|
|
self.product_tmpl_2.subscription_rule_ids = [(5, 0, 0)]
|
|
self.env['product.pricelist.item'].create({'plan_id': self.plan_month.id, 'product_tmpl_id': self.sub_product_tmpl.id, 'fixed_price': 42})
|
|
self.env['product.pricelist.item'].create({'plan_id': self.plan_month.id, 'product_tmpl_id': self.product_tmpl_2.id, 'fixed_price': 420})
|
|
with freeze_time("2021-01-01"):
|
|
self.subscription.order_line = False
|
|
self.subscription.start_date = False
|
|
self.subscription.next_invoice_date = False
|
|
self.subscription.write({
|
|
'partner_id': self.partner.id,
|
|
'partner_invoice_id': self.partner_a_invoice.id,
|
|
'partner_shipping_id': self.partner_a_shipping.id,
|
|
'plan_id': self.plan_month.id,
|
|
'order_line': [Command.create({'product_id': self.product.id,
|
|
'name': "Monthly cheap",
|
|
'price_unit': 42,
|
|
'product_uom_qty': 2,
|
|
}),
|
|
Command.create({'product_id': self.product2.id,
|
|
'name': "Monthly expensive",
|
|
'price_unit': 420,
|
|
'product_uom_qty': 3,
|
|
}),
|
|
]
|
|
})
|
|
self.subscription.action_confirm()
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
|
|
self.assertEqual(self.subscription.order_line.sorted('id').mapped('product_uom_qty'), [2, 3], "Quantities should be equal to 2 and 3")
|
|
with freeze_time("2021-01-15"):
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
self.assertEqual(upsell_so.partner_invoice_id, self.partner_a_invoice)
|
|
self.assertEqual(upsell_so.partner_shipping_id, self.partner_a_shipping)
|
|
self.assertEqual(upsell_so.order_line.mapped('product_uom_qty'), [0, 0, 0], 'The upsell order has 0 quantity')
|
|
note = upsell_so.order_line.filtered('display_type')
|
|
self.assertEqual(note.name, '(*) These recurring products are discounted according to the prorated period from 01/15/2021 to 01/31/2021')
|
|
self.assertEqual(upsell_so.order_line.product_id, self.subscription.order_line.product_id)
|
|
upsell_so.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 1
|
|
# When the upsell order is created, all quantities are equal to 0
|
|
# add line to quote manually, it must be taken into account in the subscription after validation
|
|
upsell_so.order_line = [(0, 0, {
|
|
'name': self.product2.name,
|
|
'order_id': upsell_so.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 2,
|
|
'price_unit': self.product2.list_price,
|
|
}), (0, 0, {
|
|
'name': self.product3.name,
|
|
'order_id': upsell_so.id,
|
|
'product_id': self.product3.id,
|
|
'product_uom_qty': 1,
|
|
'price_unit': self.product3.list_price,
|
|
})]
|
|
self.assertEqual(upsell_so.type_name, "Quotation")
|
|
upsell_so.action_confirm()
|
|
self.assertEqual(upsell_so.type_name, "Sales Order")
|
|
self.subscription._create_recurring_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
discounts = [round(v, 2) for v in upsell_so.order_line.sorted('discount').mapped('discount')]
|
|
self.assertEqual(discounts, [0.0, 45.16, 45.16, 45.16, 45.16], 'Prorated prices should be applied')
|
|
prices = [round(v, 2) for v in upsell_so.order_line.sorted('id').mapped('price_subtotal')]
|
|
self.assertEqual(prices, [23.03, 230.33, 0, 21.94, 23.03], 'Prorated prices should be applied')
|
|
|
|
with freeze_time("2021-02-01"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
|
|
with freeze_time("2021-03-01"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
upsell_so._create_invoices()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
sorted_lines = self.subscription.order_line.sorted('id')
|
|
self.assertEqual(sorted_lines.mapped('product_uom_qty'), [3.0, 4.0, 2.0, 1.0], "Quantities should be equal to 3.0, 4.0, 2.0, 1.0")
|
|
|
|
with freeze_time("2021-04-01"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
with freeze_time("2021-05-01"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
with freeze_time("2021-06-01"):
|
|
self.subscription._create_recurring_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
with freeze_time("2021-07-01"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
with freeze_time("2021-08-01"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
self.subscription.invoice_ids.filtered(lambda am: am.state == 'draft')._post()
|
|
inv = self.subscription.invoice_ids.sorted('date')[-1]
|
|
invoice_periods = inv.invoice_line_ids.sorted('id').mapped('name')
|
|
first_period = invoice_periods[0].split('\n')[2]
|
|
self.assertEqual(first_period, "1 Month 08/01/2021 to 08/31/2021")
|
|
second_period = invoice_periods[1].split('\n')[2]
|
|
self.assertEqual(second_period, "1 Month 08/01/2021 to 08/31/2021")
|
|
|
|
self.assertEqual(len(self.subscription.order_line), 4)
|
|
|
|
def test_upsell_prorata(self):
|
|
""" Test the prorated values obtained when creating an upsell. complementary to the previous one where new
|
|
lines had no existing default values.
|
|
"""
|
|
self.env['product.pricelist.item'].create({'plan_id': self.plan_2_month.id, 'product_tmpl_id': self.sub_product_tmpl.id, 'fixed_price': 42})
|
|
self.env['product.pricelist.item'].create({'plan_id': self.plan_2_month.id, 'product_tmpl_id': self.product_tmpl_2.id, 'fixed_price': 42})
|
|
with freeze_time("2021-01-01"):
|
|
self.subscription.order_line = False
|
|
self.subscription.start_date = False
|
|
self.subscription.next_invoice_date = False
|
|
self.subscription.write({
|
|
'partner_id': self.partner.id,
|
|
'plan_id': self.plan_2_month.id,
|
|
'order_line': [
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'name': "month: original",
|
|
'price_unit': 50,
|
|
'product_uom_qty': 1,
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product2.id,
|
|
'name': "2 month: original",
|
|
'price_unit': 50,
|
|
'product_uom_qty': 1,
|
|
}),
|
|
]
|
|
})
|
|
|
|
self.subscription.action_confirm()
|
|
self.subscription._create_recurring_invoice()
|
|
|
|
with freeze_time("2021-01-20"):
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
# Create new lines that should be aligned with existing ones
|
|
so_line_vals = [{
|
|
'name': 'Upsell added: 1 month',
|
|
'order_id': upsell_so.id,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 1,
|
|
'price_unit': self.product.list_price,
|
|
}, {
|
|
'name': 'Upsell added: 2 month',
|
|
'order_id': upsell_so.id,
|
|
'product_id': self.product3.id,
|
|
'product_uom_qty': 1,
|
|
'price_unit': self.product3.list_price,
|
|
}]
|
|
self.env['sale.order.line'].create(so_line_vals)
|
|
upsell_so.order_line.product_uom_qty = 1
|
|
discounts = [round(v) for v in upsell_so.order_line.sorted('discount').mapped('discount')]
|
|
# discounts for: 40/59 days
|
|
self.assertEqual(discounts, [0, 32, 32, 32, 32], 'Prorated prices should be applied')
|
|
self.assertEqual(self.subscription.order_line.ids, upsell_so.order_line.parent_line_id.ids,
|
|
"The parent line id should correspond to the first two lines")
|
|
# discounts for: 12d/31d; 40d/59d; 21d/31d (shifted); 31d/41d; 59d/78d;
|
|
self.assertEqual(discounts, [0, 32, 32, 32, 32], 'Prorated prices should be applied')
|
|
prices = [round(v, 2) for v in upsell_so.order_line.sorted('price_subtotal').mapped('price_subtotal')]
|
|
self.assertEqual(prices, [0.0, 28.48, 28.48, 28.48, 28.48], 'Prorated prices should be applied')
|
|
|
|
def test_upsell_date_check(self):
|
|
""" Test what happens when the upsell invoice is not generated before the next invoice cron call """
|
|
self.sub_product_tmpl.subscription_rule_ids = [
|
|
Command.create({'plan_id': self.plan_year.id, 'fixed_price': 100}),
|
|
]
|
|
self.product_tmpl_2.subscription_rule_ids = [
|
|
Command.create({'plan_id': self.plan_year.id, 'fixed_price': 200}),
|
|
]
|
|
self.product_tmpl_3.subscription_rule_ids = [
|
|
Command.create({'plan_id': self.plan_year.id, 'fixed_price': 300}),
|
|
]
|
|
with freeze_time("2022-01-01"):
|
|
sub = self.env['sale.order'].create({
|
|
'name': 'TestSubscription',
|
|
'is_subscription': True,
|
|
'note': "original subscription description",
|
|
'partner_id': self.user_portal.partner_id.id,
|
|
|
|
'plan_id': self.plan_year.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product.name,
|
|
'product_id': self.product.id,
|
|
'product_uom_qty': 1.0,
|
|
}),
|
|
(0, 0, {
|
|
'name': self.product2.name,
|
|
'product_id': self.product2.id,
|
|
'product_uom_qty': 1.0,
|
|
})
|
|
]
|
|
})
|
|
sub.action_confirm()
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
inv = sub.invoice_ids
|
|
line_names = inv.invoice_line_ids.mapped('name')
|
|
periods = [n.split('\n')[1] for n in line_names]
|
|
for p in periods:
|
|
self.assertEqual(p, '1 Year 01/01/2022 to 12/31/2022', 'the first year should be invoiced')
|
|
|
|
with freeze_time("2022-06-20"):
|
|
action = sub.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so.order_line[0].product_uom_qty = 2
|
|
upsell_so.order_line = [(0, 0, {
|
|
'product_id': self.product3.id,
|
|
'product_uom_qty': 1.0,
|
|
})]
|
|
self.assertEqual(upsell_so.next_invoice_date, datetime.date(2023, 1, 1), "The end date is the same than the parent sub")
|
|
discounts = upsell_so.order_line.mapped('discount')
|
|
self.assertEqual(discounts, [46.58, 46.58, 0.0, 46.58], "The discount is almost equal to 50%")
|
|
self.assertEqual(sub.next_invoice_date, datetime.date(2023, 1, 1), 'the first year should be invoiced')
|
|
upsell_so.action_confirm()
|
|
self.assertEqual(upsell_so.next_invoice_date, datetime.date(2023, 1, 1), 'the first year should be invoiced')
|
|
# We trigger the invoice cron before the generation of the upsell invoice
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
inv = sub.invoice_ids.sorted('date')[-1]
|
|
self.assertEqual(inv.date, datetime.date(2022, 1, 1), "No invoice should be created")
|
|
with freeze_time("2022-07-01"):
|
|
discount = upsell_so.order_line.mapped('discount')[0]
|
|
self.assertEqual(discount, 46.58, "The discount is almost equal to 50% and should not be updated for confirmed SO")
|
|
self.assertEqual(upsell_so.order_line.mapped('qty_to_invoice'), [2, 0, 0, 1])
|
|
upsell_invoice = upsell_so._create_invoices()
|
|
inv_line_ids = upsell_invoice.invoice_line_ids.filtered('product_id')
|
|
self.assertEqual(inv_line_ids.mapped('subscription_id'), upsell_so.subscription_id)
|
|
self.assertEqual(inv_line_ids.mapped('deferred_start_date'), [datetime.date(2022, 6, 20), datetime.date(2022, 6, 20)])
|
|
self.assertEqual(inv_line_ids.mapped('deferred_end_date'), [datetime.date(2022, 12, 31), datetime.date(2022, 12, 31)])
|
|
(upsell_so | sub)._cron_recurring_create_invoice()
|
|
inv = sub.invoice_ids.sorted('date')[-1]
|
|
self.assertEqual(inv.date, datetime.date(2022, 1, 1), "No invoice should be created")
|
|
self.assertEqual(upsell_invoice.amount_untaxed, 267.1, "The upsell amount should be equal to 267.1") # (1-0.4658)*(200+300)
|
|
|
|
with freeze_time("2023-01-01"):
|
|
(upsell_so | sub)._cron_recurring_create_invoice()
|
|
inv = sub.invoice_ids.sorted('date')[-1]
|
|
self.assertEqual(inv.date, datetime.date(2023, 1, 1), "A new invoice should be created")
|
|
self.assertEqual(inv.amount_untaxed, 800, "A new invoice should be created, all the lines should be invoiced")
|
|
|
|
with freeze_time("2023-01-01"):
|
|
(upsell_so | sub)._cron_recurring_create_invoice()
|
|
inv = sub.invoice_ids.sorted('date')[-1]
|
|
self.assertEqual(inv.date, datetime.date(2023, 1, 1), "A new invoice should be created")
|
|
self.assertEqual(inv.amount_untaxed, 800, "A new invoice should be created, all the lines should be invoiced")
|
|
|
|
def test_upsell_parent_line_id(self):
|
|
with freeze_time("2022-01-01"):
|
|
self.subscription.order_line = False
|
|
self.subscription.write({
|
|
'partner_id': self.partner.id,
|
|
'plan_id': self.plan_month.id,
|
|
'start_date': False,
|
|
'next_invoice_date': False,
|
|
'order_line': [
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'name': "month: original",
|
|
'price_unit': 50,
|
|
'product_uom_qty': 1,
|
|
})
|
|
]
|
|
})
|
|
self.subscription.action_confirm()
|
|
self.subscription._create_recurring_invoice()
|
|
|
|
with freeze_time("2022-01-20"):
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
# Create new lines that should be aligned with existing ones
|
|
parent_line_id = upsell_so.order_line.parent_line_id
|
|
self.assertEqual(self.subscription.order_line, parent_line_id, "The parent line is the one from the subscription")
|
|
first_line_id = upsell_so.order_line[0] # line 0 is the upsell line
|
|
first_line_id.product_id = self.product2
|
|
self.assertFalse(first_line_id.parent_line_id, "The new line should not have a parent line")
|
|
upsell_so.currency_id = False
|
|
self.assertFalse(first_line_id.parent_line_id, "The new line should not have a parent line even without currency_id")
|
|
self.subscription._compute_pricelist_id() # reset the currency_id
|
|
upsell_so._compute_pricelist_id()
|
|
first_line_id.product_id = self.product
|
|
upsell_so.order_line[0].price_unit = parent_line_id.price_unit + 0.004 # making sure that rounding issue will not affect computed result
|
|
self.assertEqual(upsell_so.order_line[0].parent_line_id, parent_line_id, "The parent line is the one from the subscription")
|
|
self.assertEqual(upsell_so.order_line.parent_line_id, parent_line_id,
|
|
"The parent line is still the one from the subscription")
|
|
# reset the product to another one to lose the link
|
|
first_line_id.product_id = self.product2
|
|
so_line_vals = [{
|
|
'name': 'Upsell added: 1 month',
|
|
'order_id': upsell_so.id,
|
|
'product_id': self.product3.id,
|
|
'product_uom_qty': 3,
|
|
}]
|
|
self.env['sale.order.line'].create(so_line_vals)
|
|
self.assertFalse(upsell_so.order_line[2].parent_line_id, "The new line should not have any parent line")
|
|
upsell_so.order_line[2].product_id = self.product3
|
|
upsell_so.order_line[2].product_id = self.product # it should recreate a link
|
|
upsell_so.order_line[0].product_uom_qty = 2
|
|
self.assertEqual(upsell_so.order_line.parent_line_id, parent_line_id,
|
|
"The parent line is the one from the subscription")
|
|
upsell_so.action_confirm()
|
|
self.assertEqual(self.subscription.order_line[0].product_uom_qty, 4, "The original line qty should be 4 (1 + 3 upsell line 1)")
|
|
self.assertEqual(self.subscription.order_line[1].product_uom_qty, 2, "The new line qty should be 2 (upsell line 0)")
|
|
|
|
action = self.subscription.prepare_renewal_order()
|
|
renew_so = self.env['sale.order'].browse(action['res_id'])
|
|
parent_line_id = renew_so.order_line.parent_line_id
|
|
self.assertEqual(self.subscription.order_line, parent_line_id, "The parent line is the one from the subscription")
|
|
renew_so.plan_id = self.plan_year
|
|
self.assertFalse(renew_so.order_line.parent_line_id, "The lines should not have parent lines anymore")
|
|
|
|
# test the general behavior of so when the compute_price_unit is called
|
|
self.product_tmpl_4.recurring_invoice = False
|
|
order = self.env['sale.order'].create({
|
|
'partner_id': self.partner.id,
|
|
'sale_order_template_id': self.subscription_tmpl.id,
|
|
'plan_id': self.plan_month.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product.name,
|
|
'product_id': self.product.id,
|
|
'product_uom_qty': 1.0,
|
|
'price_unit': 12,
|
|
}),
|
|
(0, 0, {
|
|
'name': self.product5.name, # non recurring product
|
|
'product_id': self.product5.id,
|
|
'product_uom_qty': 1.0,
|
|
'price_unit': 12,
|
|
})
|
|
],
|
|
})
|
|
self.assertTrue(order.is_subscription)
|
|
self.assertEqual(order.order_line[1].price_unit, 12)
|
|
order.order_line[1].product_id = self.product_tmpl_4.product_variant_id
|
|
self.assertEqual(order.order_line[1].price_unit, 15, "The price should be updated")
|
|
|
|
def test_upsell_renewal(self):
|
|
""" Upselling a invoiced renewed order before it started should create a negative discount to invoice the previous
|
|
period. If the renewal has not been invoiced yet, we should only invoice for the previous period.
|
|
"""
|
|
with freeze_time("2022-01-01"):
|
|
self.subscription.start_date = False
|
|
self.subscription.next_invoice_date = False
|
|
self.subscription.write({
|
|
'partner_id': self.partner.id,
|
|
'plan_id': self.plan_year.id,
|
|
})
|
|
subscription_2 = self.subscription.copy()
|
|
(self.subscription | subscription_2).action_confirm()
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
|
|
with freeze_time("2022-09-10"):
|
|
action = self.subscription.prepare_renewal_order()
|
|
renewal_so = self.env['sale.order'].browse(action['res_id'])
|
|
renewal_so.action_confirm()
|
|
renewal_so._create_invoices()
|
|
renewal_so.order_line.invoice_lines.move_id._post()
|
|
self.assertEqual(renewal_so.start_date, datetime.date(2023, 1, 1))
|
|
self.assertEqual(renewal_so.next_invoice_date, datetime.date(2024, 1, 1))
|
|
action = subscription_2.prepare_renewal_order()
|
|
renewal_so_2 = self.env['sale.order'].browse(action['res_id'])
|
|
renewal_so_2.action_confirm()
|
|
# We don't invoice renewal_so_2 yet to see what happens.
|
|
self.assertEqual(renewal_so_2.start_date, datetime.date(2023, 1, 1))
|
|
self.assertEqual(renewal_so_2.next_invoice_date, datetime.date(2023, 1, 1))
|
|
with freeze_time("2022-10-02"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
action = renewal_so.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 1
|
|
renewal_so_2.next_invoice_date += relativedelta(days=1) # prevent validation error
|
|
action = renewal_so_2.prepare_upsell_order()
|
|
upsell_so_2 = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so_2.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 1
|
|
parents = upsell_so.order_line.mapped('parent_line_id')
|
|
line_match = [
|
|
renewal_so.order_line[0],
|
|
renewal_so.order_line[1],
|
|
]
|
|
for idx in range(2):
|
|
self.assertEqual(parents[idx], line_match[idx])
|
|
self.assertEqual(self.subscription.order_line.mapped('product_uom_qty'), [1, 1])
|
|
self.assertEqual(renewal_so.order_line.mapped('product_uom_qty'), [1, 1])
|
|
upsell_so.action_confirm()
|
|
self.assertEqual(upsell_so.order_line.mapped('product_uom_qty'), [1.0, 1.0, 0])
|
|
self.assertEqual(renewal_so.order_line.mapped('product_uom_qty'), [2, 2])
|
|
self.assertEqual(upsell_so.order_line.mapped('discount'), [-24.93, -24.93, 0])
|
|
self.assertEqual(upsell_so.start_date, datetime.date(2022, 10, 2))
|
|
self.assertEqual(upsell_so.next_invoice_date, datetime.date(2024, 1, 1))
|
|
self.assertEqual(upsell_so_2.amount_untaxed, 30.25)
|
|
# upsell_so_2.order_line.flush()
|
|
line = upsell_so_2.order_line.filtered('display_type')
|
|
self.assertEqual(line.display_type, 'subscription_discount')
|
|
self.assertFalse(line.product_uom_qty)
|
|
self.assertFalse(line.price_unit)
|
|
self.assertFalse(line.customer_lead)
|
|
self.assertFalse(line.product_id)
|
|
|
|
self.assertEqual(upsell_so_2.order_line.mapped('product_uom_qty'), [1.0, 1.0, 0])
|
|
for discount, value in zip(upsell_so_2.order_line.mapped('discount'), [74.79, 74.79, 0.0]):
|
|
self.assertAlmostEqual(discount, value)
|
|
self.assertEqual(upsell_so_2.next_invoice_date, datetime.date(2023, 1, 2),
|
|
'We only invoice until the start of the renewal')
|
|
|
|
def test_upsell_with_different_currency_throws_error(self):
|
|
pricelist_eur = self.env['product.pricelist'].create({
|
|
'name': 'Euro pricelist',
|
|
'currency_id': self.env.ref('base.EUR').id,
|
|
})
|
|
self.subscription.action_confirm()
|
|
self.subscription._create_recurring_invoice()
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
with self.assertRaises(ValidationError):
|
|
upsell_so.pricelist_id = pricelist_eur.id
|
|
|
|
def test_modify_discount_on_upsell(self):
|
|
"""
|
|
Makes sure that you can edit the discount on an upsell, save it, and then confirm it,
|
|
and it doesn't change/reset to default
|
|
"""
|
|
with freeze_time("2022-10-31"):
|
|
self.subscription.action_confirm()
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_line = upsell_so.order_line.filtered(lambda l: not l.display_type)[0]
|
|
old_discount = upsell_line.discount
|
|
new_discount = 42
|
|
self.assertTrue(old_discount != new_discount,
|
|
"These discounts should be different, change the value of new_discount if this test fail.")
|
|
upsell_line.write({'discount': new_discount})
|
|
self.assertEqual(upsell_line.discount, new_discount,
|
|
"The line should have the new discount written.")
|
|
upsell_so.action_confirm()
|
|
self.assertEqual(upsell_line.discount, new_discount,
|
|
"The line should have the new discount after confirmation.")
|
|
|
|
def test_upsell_total_qty(self):
|
|
self.subscription.action_confirm()
|
|
self.subscription._create_recurring_invoice()
|
|
next_invoice_date = self.subscription.next_invoice_date
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 2
|
|
upsell_so.action_confirm()
|
|
for line in upsell_so.order_line.filtered(lambda l: not l.display_type):
|
|
self.assertEqual(line.upsell_total, 3)
|
|
invoice = upsell_so._create_invoices()
|
|
# simulate wrong historical data
|
|
invoice.invoice_line_ids.filtered('deferred_end_date').deferred_end_date = next_invoice_date + relativedelta(days=5)
|
|
invoice._post()
|
|
self.assertEqual(self.subscription.next_invoice_date, next_invoice_date, "The next Invoice date should not be updated by an upsell")
|
|
|
|
@freeze_time('2026-01-28')
|
|
def test_sale_subscription_upsell_does_not_copy_non_recurring_products(self):
|
|
"""Ensure non recurring product of upsells are not copied in the parent order
|
|
This test also make sure that the discount of the parent order is untouched
|
|
"""
|
|
nr_product = self.env['product.template'].create({
|
|
'name': 'Non recurring product',
|
|
'type': 'service',
|
|
'uom_id': self.product.uom_id.id,
|
|
'list_price': 25,
|
|
'invoice_policy': 'order',
|
|
})
|
|
self.assertEqual(self.subscription.order_line.mapped('price_subtotal'), [1, 20], "Undiscounted prices should be 1, 20")
|
|
self.subscription.order_line.discount = 20
|
|
self.subscription.action_confirm()
|
|
self.subscription._create_recurring_invoice()
|
|
self.assertEqual(self.subscription.order_line.mapped('discount'), [20, 20], "Discount should be applied")
|
|
self.assertEqual(self.subscription.order_line.mapped('price_subtotal'), [0.8, 16], "Discount should be applied")
|
|
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
# add non recurring product
|
|
self.env['sale.order.line'].create({
|
|
'name': nr_product.name,
|
|
'order_id': upsell_so.id,
|
|
'product_id': nr_product.product_variant_id.id,
|
|
'product_uom_qty': 1,
|
|
})
|
|
# upsell the start date to trigger discount recomputation bug
|
|
upsell_so.start_date += relativedelta(days=10)
|
|
upsell_so.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 1
|
|
self.assertEqual(upsell_so.order_line.mapped('discount'), [40, 40, 0.0, 0.0], "Upsell discount should be adapted")
|
|
upsell_so.action_confirm()
|
|
self.assertFalse(nr_product in self.subscription.order_line.product_template_id)
|
|
self.subscription.order_line.invalidate_recordset(['discount', 'price_subtotal'])
|
|
self.assertEqual(self.subscription.order_line.mapped('discount'), [20, 20], "Discount of parent sub should remains")
|
|
self.assertEqual(self.subscription.order_line.mapped('price_subtotal'), [1.6, 32], "Discount of parent sub should remains (with new quantity)")
|
|
|
|
def test_upsell_descriptions(self):
|
|
""" On invoicing upsells, only subscription-based items should display a duration. """
|
|
with freeze_time("2022-10-31"):
|
|
self.subscription.action_confirm()
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so.order_line = [Command.create({'product_id': self.product_a.id})]
|
|
upsell_so.order_line.filtered('product_id').product_uom_qty = 1
|
|
upsell_so.action_confirm()
|
|
invoice = upsell_so._create_invoices()
|
|
|
|
self.assertEqual(len(invoice.invoice_line_ids), 4)
|
|
for line in invoice.invoice_line_ids:
|
|
name = line.name
|
|
sol_name = line.sale_line_ids.name
|
|
if line.sale_line_ids.recurring_invoice:
|
|
self.assertIn("1 Month", name, "Sub lines require duration")
|
|
else:
|
|
self.assertEqual(name, sol_name, "Non-sub lines shouldn't add duration")
|
|
|
|
def test_upsell_line_note_update(self):
|
|
""""
|
|
Test that the line note is handled correctly in upsell orders:
|
|
For positive discount upsell orders, the line note remains unchanged.
|
|
For negative discount upsell orders, the line note is updated to reflect the surcharged prorated period.
|
|
"""
|
|
with freeze_time("2022-01-01"):
|
|
self.subscription.write({
|
|
'partner_id': self.partner.id,
|
|
'plan_id': self.plan_year.id,
|
|
'name': 'First order'
|
|
})
|
|
# Test the positive discount (line note remains unchanged)
|
|
# Create a second sale order (for positive discount scenario)
|
|
sale_order_2 = self.subscription.copy({'name': 'Second order'})
|
|
(self.subscription | sale_order_2).action_confirm()
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
|
|
with freeze_time("2022-09-10"):
|
|
action = sale_order_2.prepare_upsell_order()
|
|
upsell_so_2 = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so_2.name = "Upsell of second order"
|
|
upsell_so_2.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 1
|
|
self.assertEqual(upsell_so_2.order_line.mapped('product_uom_qty'), [1.0, 1.0, 0])
|
|
upsell_so_2.action_confirm()
|
|
|
|
self.assertEqual(upsell_so_2.order_line.mapped('discount'), [69.04, 69.04, 0])
|
|
expected_line_name = (
|
|
'(*) These recurring products are discounted according to the prorated period from %s to %s' % (
|
|
format_date(self.env, upsell_so_2.start_date),
|
|
format_date(self.env, upsell_so_2.next_invoice_date - relativedelta(days=1))
|
|
))
|
|
upsell_order_line_note = upsell_so_2.order_line.filtered(lambda l: l.display_type == 'subscription_discount').name
|
|
self.assertEqual(expected_line_name, upsell_order_line_note)
|
|
|
|
# Test for negative discount (line note should be updated)
|
|
# Prepare and confirm the renewal order (for negative discount scenario)
|
|
action = self.subscription.prepare_renewal_order()
|
|
renewal_so = self.env['sale.order'].browse(action['res_id'])
|
|
renewal_so.name = 'Renewal SO'
|
|
renewal_so.action_confirm()
|
|
renewal_so._create_invoices()
|
|
renewal_so.order_line.invoice_lines.move_id._post()
|
|
|
|
self.assertEqual(renewal_so.start_date, datetime.date(2023, 1, 1))
|
|
self.assertEqual(renewal_so.next_invoice_date, datetime.date(2024, 1, 1))
|
|
|
|
with freeze_time("2022-10-02"):
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
action = renewal_so.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
upsell_so.name = "Upsell of renewal"
|
|
upsell_so.order_line.filtered(lambda l: not l.display_type).product_uom_qty = 1
|
|
upsell_so.action_confirm()
|
|
|
|
# Assert that the discounts are negative and the line note is updated accordingly
|
|
self.assertEqual(upsell_so.order_line.mapped('discount'), [-24.93, -24.93, 0])
|
|
expected_line_name = (
|
|
'(*) These recurring products are surcharged according to the prorated period from %s to %s' % (
|
|
format_date(self.env, upsell_so.start_date),
|
|
format_date(self.env, upsell_so.next_invoice_date - relativedelta(days=1))
|
|
))
|
|
upsell_order_line_note = upsell_so.order_line.filtered(lambda l: l.display_type == 'subscription_discount').name
|
|
self.assertEqual(expected_line_name, upsell_order_line_note)
|
|
|
|
def test_copy_follower_on_upsell_or_renew_order_from_parent(self):
|
|
"""
|
|
Test that followers from parent sale order are getting copied to the renew or upsell order.
|
|
"""
|
|
with freeze_time("2024-11-01"):
|
|
self.subscription.action_confirm()
|
|
self.subscription.message_subscribe(partner_ids=self.sale_user.partner_id.ids)
|
|
self.env['sale.order']._cron_recurring_create_invoice()
|
|
|
|
with freeze_time("2024-11-10"):
|
|
action = self.subscription.prepare_renewal_order()
|
|
renewal_so = self.env['sale.order'].browse(action['res_id'])
|
|
renewal_so.action_confirm()
|
|
renewal_so._create_invoices()
|
|
renewal_so.order_line.invoice_lines.move_id._post()
|
|
self.assertIn(
|
|
self.subscription.message_follower_ids.partner_id.id, renewal_so.message_follower_ids.partner_id.ids,
|
|
"Parent order's followers should be copied into renew order.")
|
|
# add a new follower in the renew order
|
|
renewal_so.message_subscribe(self.partner.ids)
|
|
|
|
# create a upsell order from renewal order
|
|
action = renewal_so.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
self.assertEqual(
|
|
renewal_so.message_follower_ids.partner_id.ids, upsell_so.message_follower_ids.partner_id.ids,
|
|
"Parent order's followers should be copied into upsell order.")
|
|
|
|
def test_upsell_duplicate_warning(self):
|
|
self.subscription.write({
|
|
'partner_id': self.user_portal.partner_id.id,
|
|
'client_order_ref': 'co_ref'
|
|
})
|
|
self.subscription.action_confirm()
|
|
self.subscription._create_recurring_invoice()
|
|
action = self.subscription.prepare_upsell_order()
|
|
upsell_so = self.env['sale.order'].browse(action['res_id'])
|
|
self.assertFalse(upsell_so.duplicated_order_ids, "Upsell quotation should not be marked as duplicate order and not show warning")
|