Files
enterprise-addons/sale_subscription/tests/test_subscription_renew.py
T

730 lines
40 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from unittest.mock import patch
from dateutil.relativedelta import relativedelta
from markupsafe import Markup
from odoo import Command
from odoo.exceptions import ValidationError
from odoo.tests import freeze_time, tagged
from odoo.tools import mute_logger
from odoo.addons.sale_subscription.models.sale_order import SaleOrder
from odoo.addons.sale_subscription.tests.common_sale_subscription import TestSubscriptionCommon
@tagged('post_install', '-at_install')
class TestSubscriptionRenew(TestSubscriptionCommon):
@classmethod
def default_env_context(cls):
return {}
@mute_logger('odoo.models.unlink')
def test_renewal(self):
""" Test subscription renewal """
with freeze_time("2021-11-18"):
# We reset the renew alert to make sure it will run with freezetime
self.subscription.write({
'start_date': False,
'next_invoice_date': False,
'partner_invoice_id': self.partner_a_invoice.id,
'partner_shipping_id': self.partner_a_shipping.id,
'internal_note': 'internal note',
}) # add an so line with a different uom
uom_dozen = self.env.ref('uom.product_uom_dozen').id
self.subscription_tmpl.duration_value = 2 # end after 2 months to adapt to the following line
self.subscription_tmpl.duration_unit = 'month'
self.env['sale.order.line'].create({'name': self.product.name,
'order_id': self.subscription.id,
'product_id': self.product3.id,
'product_uom_qty': 4,
'product_uom_id': uom_dozen,
'price_unit': 42})
self.subscription.action_confirm()
self.subscription._create_recurring_invoice()
self.assertEqual(self.subscription.end_date, datetime.date(2022, 1, 17), 'The end date of the subscription should be updated according to the template')
self.assertEqual(self.subscription.next_invoice_date, datetime.date(2021, 12, 18))
self.env['account.payment.register'] \
.with_context(active_model='account.move', active_ids=self.subscription.invoice_ids.ids) \
.create({
'currency_id': self.subscription.currency_id.id,
'amount': self.subscription.amount_total,
})._create_payments()
self.assertEqual(self.subscription.invoice_count, 1)
self.assertTrue(self.subscription.invoice_ids.payment_state in ['in_payment', 'paid'], "the invoice is considered paid, depending on the settings.")
with freeze_time("2021-12-18"):
action = self.subscription.prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
self.assertEqual(renewal_so.partner_invoice_id, self.partner_a_invoice)
self.assertEqual(renewal_so.partner_shipping_id, self.partner_a_shipping)
# check produt_uom_qty
self.assertEqual(renewal_so.sale_order_template_id.id, self.subscription.sale_order_template_id.id,
'sale_subscription: renewal so should have the same template')
renewal_start_date = renewal_so.start_date
with self.assertRaises(ValidationError):
# try to start the renewal before the parent next invoice date
renewal_so.start_date = self.subscription.next_invoice_date - relativedelta(days=1)
renewal_so.action_confirm()
renewal_so.start_date = renewal_start_date
renewal_so.action_confirm()
self.assertEqual(renewal_so.internal_note_display, Markup('<p>internal note</p>'), 'Internal Note should redirect to the parent')
self.assertEqual(self.subscription.recurring_monthly, 189, 'Should be closed but with an MRR')
self.assertEqual(renewal_so.subscription_state, '3_progress', 'so should now be in progress')
self.assertEqual(self.subscription.subscription_state, '5_renewed')
self.assertEqual(renewal_so.date_order.date(), self.subscription.end_date, 'renewal start date should depends on the parent end date')
self.assertEqual(renewal_so.start_date, self.subscription.end_date, 'The renewal subscription start date and the renewed end_date should be aligned')
self.assertEqual(renewal_so.plan_id, self.plan_month, 'the plan should be propagated')
self.assertEqual(renewal_so.next_invoice_date, datetime.date(2021, 12, 18))
self.assertEqual(renewal_so.start_date, datetime.date(2021, 12, 18))
self.assertTrue(renewal_so.is_subscription)
renewal_so._create_recurring_invoice()
with freeze_time("2024-11-17"):
invoice = self.subscription._create_recurring_invoice()
self.assertFalse(invoice, "Locked contract should not generate invoices")
renewal_so.internal_note_display = 'new internal note'
self.assertEqual(renewal_so.internal_note_display, Markup('<p>new internal note</p>'), 'Internal Note should be updated')
self.assertEqual(self.subscription.internal_note_display, Markup('<p>new internal note</p>'), 'Internal Note should be updated')
with freeze_time("2024-11-19"):
self.subscription._create_recurring_invoice() # it will close self.subscription
renew_close_reason_id = self.env.ref('sale_subscription.close_reason_renew').id
self.assertEqual(self.subscription.subscription_state, '5_renewed')
self.assertEqual(self.subscription.close_reason_id.id, renew_close_reason_id)
(self.subscription | renewal_so).invalidate_recordset(['invoice_ids', 'invoice_count'])
self.assertEqual(self.subscription.invoice_count, 2)
self.assertEqual(renewal_so.invoice_count, 2)
def test_renew_kpi_mrr(self):
# Test that renew with MRR transfer give correct result
# First, whe create a sub with MRR = 21
# Then we renew it with a MRR of 42
# After a few months the MRR of the renewal is 63
# We also create and renew a free subscription
with freeze_time("2021-01-01"), patch.object(SaleOrder, '_get_unpaid_subscriptions', lambda x: []):
self.subscription.plan_id.auto_close_limit = 5000 # don't close automatically contract if unpaid invoices
# so creation with mail tracking
context_mail = {'tracking_disable': False}
sub = self.env['sale.order'].with_context(context_mail).create({
'name': 'Parent Sub',
'is_subscription': True,
'note': "original subscription description",
'partner_id': self.user_portal.partner_id.id,
'sale_order_template_id': self.subscription_tmpl.id,
})
free_sub = self.env['sale.order'].with_context(context_mail).create({
'name': 'Parent free Sub',
'is_subscription': True,
'note': "original subscription description",
'partner_id': self.user_portal.partner_id.id,
'plan_id': self.plan_month.id,
'client_order_ref': 'free',
'order_line': [
(0, 0, {
'name': self.product.name,
'product_id': self.product.id,
'product_uom_qty': 3.0,
'price_unit': 0,
})],
})
future_sub = self.env['sale.order'].with_context(context_mail).create({
'name': 'FutureSub',
'is_subscription': True,
'note': "original subscription description",
'partner_id': self.user_portal.partner_id.id,
'plan_id': self.plan_month.id,
'start_date': '2021-06-01',
'order_line': [
(0, 0, {
'name': self.product.name,
'product_id': self.product.id,
'product_uom_qty': 1.0,
})],
})
self.assertFalse(free_sub.amount_total)
self.flush_tracking()
sub._onchange_sale_order_template_id()
# Same product for both lines
sub.order_line.product_uom_qty = 1
(free_sub | sub).end_date = datetime.date(2022, 1, 1)
(free_sub | sub | future_sub).action_confirm()
self.flush_tracking()
self.assertEqual(sub.recurring_monthly, 21, "20 + 1 for both lines")
self.assertEqual(sub.subscription_state, "3_progress")
self.env['sale.order'].with_context(tracking_disable=False)._cron_recurring_create_invoice()
with freeze_time("2021-02-01"):
self.env['sale.order'].with_context(tracking_disable=False)._cron_recurring_create_invoice()
with freeze_time("2021-03-01"):
self.env['sale.order'].with_context(tracking_disable=False)._cron_recurring_create_invoice()
with freeze_time("2021-04-01"):
# We create a renewal order in april for the new year
self.env['sale.order']._cron_recurring_create_invoice()
action = sub.with_context(tracking_disable=False).prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
renewal_so = renewal_so.with_context(tracking_disable=False)
renewal_so.order_line.product_uom_qty = 3
renewal_so.name = "Renewal"
self.flush_tracking()
action = free_sub.with_context(tracking_disable=False).prepare_renewal_order()
free_renewal_so = self.env['sale.order'].browse(action['res_id'])
free_renewal_so = free_renewal_so.with_context(tracking_disable=False)
free_renewal_so.order_line.write({'product_uom_qty': 2, 'price_unit': 0})
self.flush_tracking()
self.assertEqual(renewal_so.subscription_state, '2_renewal')
(sub | free_sub).pause_subscription() # we pause the contracts to make sure no parasite log are created
self.flush_tracking()
self.env['sale.order']._cron_recurring_create_invoice()
self.flush_tracking()
(renewal_so | free_renewal_so).action_confirm()
self.flush_tracking()
self.assertEqual(sub.subscription_state, '5_renewed')
self.assertEqual(renewal_so.subscription_state, '3_progress')
(sub | free_sub).resume_subscription() # we resume the contracts to make sure no parasite log are created
self.flush_tracking()
# Most of the time, the renewal invoice is created by the salesman
# before the renewal start date
renewal_invoices = (free_renewal_so | renewal_so)._create_invoices()
renewal_invoices._post()
self.flush_tracking()
# "upsell" of the simple sub that did not start yet
future_sub.order_line.product_uom_qty = 4
self.flush_tracking()
self.assertEqual(sub.recurring_monthly, 21, "MRR should still be non null")
self.assertEqual(sub.subscription_state, '5_renewed')
self.assertEqual(renewal_so.recurring_monthly, 63, "MRR of renewal should not be computed before start_date of the lines")
self.flush_tracking()
# renew is still not ongoing; Total MRR is 21 coming from the original sub
self.env['sale.order'].sudo()._cron_subscription_expiration()
self.assertEqual(sub.recurring_monthly, 21)
self.assertEqual(renewal_so.recurring_monthly, 63)
self.env['sale.order']._cron_recurring_create_invoice()
self.flush_tracking()
self.subscription._cron_update_kpi()
self.assertEqual(sub.kpi_1month_mrr_delta, 0)
self.assertEqual(sub.kpi_1month_mrr_percentage, 0)
self.assertEqual(sub.kpi_3months_mrr_delta, 0)
self.assertEqual(sub.kpi_3months_mrr_percentage, 0)
self.assertEqual(sub.subscription_state, '5_renewed')
with freeze_time("2021-04-20"):
# We upsell the renewal after it's confirmation but before its start_date The event date must be "today"
self.flush_tracking()
renewal_so.order_line[1].product_uom_qty += 1
self.flush_tracking()
with freeze_time("2021-05-05"): # We switch the cron the X of may to make sure the day of the cron does not affect the numbers
# Renewal period is from 2021-05 to 2021-06
self.env['sale.order']._cron_recurring_create_invoice()
self.assertEqual(sub.recurring_monthly, 21)
self.assertEqual(sub.subscription_state, '5_renewed')
self.assertEqual(renewal_so.next_invoice_date, datetime.date(2021, 6, 1))
self.assertEqual(renewal_so.recurring_monthly, 83)
self.flush_tracking()
with freeze_time("2021-05-15"):
self.env['sale.order']._cron_recurring_create_invoice()
sub.order_line._compute_recurring_monthly()
self.flush_tracking()
with freeze_time("2021-06-01"):
self.subscription._cron_update_kpi()
self.env['sale.order']._cron_recurring_create_invoice()
self.assertEqual(sub.recurring_monthly, 21)
self.assertEqual(renewal_so.recurring_monthly, 83)
self.flush_tracking()
with freeze_time("2021-07-01"), patch.object(SaleOrder, '_get_unpaid_subscriptions', lambda x: []):
# Total MRR is 42 coming from renew
self.subscription._cron_update_kpi()
self.env['sale.order']._cron_recurring_create_invoice()
self.env['sale.order']._cron_subscription_expiration()
# we trigger the compute because it depends on today value.
self.assertEqual(sub.recurring_monthly, 21)
self.assertEqual(renewal_so.recurring_monthly, 83)
self.flush_tracking()
with freeze_time("2021-08-03"), patch.object(SaleOrder, '_get_unpaid_subscriptions', lambda x: []):
# We switch the cron the X of august to make sure the day of the cron does not affect the numbers
renewal_so.end_date = datetime.date(2032, 1, 1)
self.flush_tracking()
# Total MRR is 80 coming from renewed sub
self.env['sale.order']._cron_recurring_create_invoice()
self.env['sale.order'].sudo()._cron_subscription_expiration()
self.assertEqual(sub.recurring_monthly, 21)
self.assertEqual(renewal_so.recurring_monthly, 83)
self.assertEqual(sub.subscription_state, '5_renewed')
self.flush_tracking()
with freeze_time("2021-09-01"), patch.object(SaleOrder, '_get_unpaid_subscriptions', lambda x: []):
renewal_so.order_line.product_uom_qty = 5
# We update the MRR of the renewed
self.env['sale.order']._cron_recurring_create_invoice()
self.env['sale.order']._cron_subscription_expiration()
self.assertEqual(renewal_so.recurring_monthly, 105)
# free subscription is not free anymore
free_renewal_so.order_line.price_unit = 10
self.flush_tracking()
self.subscription._cron_update_kpi()
self.assertEqual(sub.kpi_1month_mrr_delta, 0)
self.assertEqual(sub.kpi_1month_mrr_percentage, 0)
self.assertEqual(sub.kpi_3months_mrr_delta, 0)
self.assertEqual(sub.kpi_3months_mrr_percentage, 0)
self.assertEqual(renewal_so.kpi_1month_mrr_delta, 22)
self.assertEqual(round(renewal_so.kpi_1month_mrr_percentage, 2), 0.27)
self.assertEqual(renewal_so.kpi_3months_mrr_delta, 22)
self.assertEqual(round(renewal_so.kpi_3months_mrr_percentage, 2), 0.27)
order_log_ids = sub.order_log_ids.sorted('event_date')
sub_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log in order_log_ids]
self.assertEqual(sub_data, [('0_creation', datetime.date(2021, 1, 1), '1_draft', 21, 21),
('3_transfer', datetime.date(2021, 4, 1), '5_renewed', -21, 0)])
renew_logs = renewal_so.order_log_ids.sorted(key=lambda log: (log.event_date, log.id))
renew_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log in renew_logs]
self.assertEqual(renew_data, [('3_transfer', datetime.date(2021, 4, 1), '2_renewal', 21.0, 21.0),
('1_expansion', datetime.date(2021, 4, 1), '3_progress', 42.0, 63.0),
('1_expansion', datetime.date(2021, 4, 20), '3_progress', 20.0, 83.0),
('1_expansion', datetime.date(2021, 9, 1), '3_progress', 22, 105.0)])
self.assertEqual(renewal_so.start_date, datetime.date(2021, 5, 1), "the renewal starts on the firsts of May even if transfer occurs on first of April")
free_log_ids = free_sub.order_log_ids.sorted(key=lambda log: (log.event_date, log.id))
sub_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log in
free_log_ids]
self.assertEqual(sub_data, [('0_creation', datetime.date(2021, 1, 1), '1_draft', 0, 0),
('3_transfer', datetime.date(2021, 4, 1), '5_renewed', 0, 0)])
renew_logs = free_renewal_so.order_log_ids.sorted(key=lambda log: (log.event_date, log.id))
renew_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log
in renew_logs]
self.assertEqual(renew_data, [('3_transfer', datetime.date(2021, 4, 1), '2_renewal', 0, 0),
('1_expansion', datetime.date(2021, 9, 1), '3_progress', 20.0, 20.0)])
future_data = future_sub.order_log_ids.sorted(key=lambda log: (log.event_date, log.id)) # several events aggregated on the same date
simple_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log
in future_data]
self.assertEqual(simple_data, [('0_creation', datetime.date(2021, 1, 1), '1_draft', 1.0, 1.0),
('1_expansion', datetime.date(2021, 4, 1), '3_progress', 3.0, 4.0)])
self.assertEqual(future_sub.start_date, datetime.date(2021, 6, 1), "the start date is in june but the events are recorded as today")
def test_multiple_renew(self):
""" Prevent to confirm several renewal quotation for the same subscription """
self.subscription.write({'start_date': False, 'next_invoice_date': False})
self.subscription.action_confirm()
self.subscription._cron_recurring_create_invoice()
action = self.subscription.prepare_renewal_order()
renewal_so_1 = self.env['sale.order'].browse(action['res_id'])
action = self.subscription.prepare_renewal_order()
renewal_so_2 = self.env['sale.order'].browse(action['res_id'])
renewal_so_1.action_confirm()
self.assertEqual(renewal_so_2.state, 'cancel', 'The other quotation should be canceled')
def test_renew_different_currency(self):
with freeze_time("2023-03-28"):
self.product.subscription_rule_ids.unlink()
default_pricelist = self.pricelist
other_currency = self._enable_currency('EUR')
other_pricelist = self.env['product.pricelist'].create({
'name': 'Test Pricelist (EUR)',
'currency_id': other_currency.id,
})
other_currency.write({
'rate_ids': [(0, 0, {
'rate': 20,
})]
})
pricing_month_1 = self.env['product.pricelist.item'].create({
'plan_id': self.plan_month.id,
'fixed_price': 10,
'pricelist_id': default_pricelist.id,
})
pricing_month_2 = self.env['product.pricelist.item'].create({
'plan_id': self.plan_month.id,
'fixed_price': 200,
'pricelist_id': other_pricelist.id,
})
sub_product_tmpl = self.env['product.template'].create({
'name': 'BaseTestProduct',
'type': 'service',
'recurring_invoice': True,
'uom_id': self.env.ref('uom.product_uom_unit').id,
'subscription_rule_ids': [(6, 0, (pricing_month_1 | pricing_month_2).ids)]
})
subscription_tmpl = self.env['sale.order.template'].create({
'name': 'Subscription template without discount',
'duration_unit': 'year',
'is_unlimited': False,
'duration_value': 2,
'note': "This is the template description",
'plan_id': self.plan_month.copy(default={'auto_close_limit': 5}).id,
'sale_order_template_line_ids': [Command.create({
'name': "Product 1",
'product_id': sub_product_tmpl.product_variant_id.id,
'product_uom_qty': 1,
'product_uom_id': sub_product_tmpl.product_variant_id.uom_id.id,
})]
})
sub = self.subscription.create({
'name': 'Company1 - Currency1',
'sale_order_template_id': subscription_tmpl.id,
'partner_id': self.user_portal.partner_id.id,
'currency_id': self.company.currency_id.id,
'plan_id': self.plan_month.id,
'order_line': [(0, 0, {
'name': "Product 1",
'product_id': sub_product_tmpl.product_variant_id.id,
'product_uom_qty': 1,
})]
})
sub.pricelist_id = default_pricelist.id
sub._onchange_sale_order_template_id() # recompute the pricings
self.flush_tracking()
sub.action_confirm()
self.assertEqual(sub.recurring_monthly, 10)
self.flush_tracking()
self.env['sale.order']._cron_recurring_create_invoice()
self.flush_tracking()
with freeze_time("2023-04-29"):
action = sub.prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
renewal_so.write({
'pricelist_id': other_pricelist.id,
})
renewal_so._onchange_sale_order_template_id()
renewal_so.order_line.product_uom_qty = 3
self.flush_tracking()
renewal_so.action_confirm()
self.flush_tracking()
self.env['sale.order']._cron_recurring_create_invoice()
order_log_ids = sub.order_log_ids.sorted(key=lambda log: (log.event_date, log.id))
sub_data = [(log.event_type, log.event_date, log.amount_signed, log.recurring_monthly, log.currency_id)
for log in order_log_ids]
self.assertEqual(sub_data,
[('0_creation', datetime.date(2023, 3, 28), 10, 10, default_pricelist.currency_id),
('3_transfer', datetime.date(2023, 4, 29), -10, 0, default_pricelist.currency_id)
])
renew_logs = renewal_so.order_log_ids.sorted(key=lambda log: (log.event_date, log.id))
renew_data = [(log.event_type, log.event_date, log.amount_signed, log.recurring_monthly, log.currency_id)
for log in renew_logs]
self.assertEqual(renew_data, [
('3_transfer', datetime.date(2023, 4, 29), 200, 200, other_currency),
('1_expansion', datetime.date(2023, 4, 29), 400, 600, other_currency)
])
def test_renewal_churn(self):
# Test what we expect when we
# 1) create a renewal quote
# 2) close the parent
# 3) confirm the renewal
with freeze_time("2021-01-01"), patch.object(SaleOrder, '_get_unpaid_subscriptions', lambda x: []):
# so creation with mail tracking
context_mail = {'tracking_disable': False}
sub = self.env['sale.order'].with_context(context_mail).create({
'name': 'Parent Sub',
'is_subscription': True,
'note': "original subscription description",
'partner_id': self.user_portal.partner_id.id,
'sale_order_template_id': self.subscription_tmpl.id,
})
sub._onchange_sale_order_template_id()
# Same product for both lines
sub.order_line.product_uom_qty = 1
self.flush_tracking()
sub.action_confirm()
sub._create_recurring_invoice()
self.flush_tracking()
action = sub.with_context(tracking_disable=False).prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
renewal_so = renewal_so.with_context(tracking_disable=False)
renewal_so.order_line.product_uom_qty = 3
renewal_so.name = "Renewal"
self.flush_tracking()
sub.set_close()
self.flush_tracking()
renewal_so.action_confirm()
self.flush_tracking()
order_log_ids = sub.order_log_ids.sorted('id')
sub_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly)
for log in order_log_ids]
self.assertEqual(sub_data,
[('0_creation', datetime.date(2021, 1, 1), '1_draft', 21.0, 21.0),
('3_transfer', datetime.date(2021, 1, 1), '5_renewed', -21.0, 0.0)])
order_log_ids = renewal_so.order_log_ids.sorted('id')
renew_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log in order_log_ids]
self.assertEqual(renew_data, [('3_transfer', datetime.date(2021, 1, 1), '2_renewal', 21, 21),
('1_expansion', datetime.date(2021, 1, 1), '3_progress', 42.0, 63)])
def test_churn_log_renew(self):
""" Test the behavior of the logs when we confirm a renewal quote after the parent has been closed.
"""
self.flush_tracking()
with freeze_time("2024-01-22 08:00:00"):
today = datetime.date.today()
context_mail = {'tracking_disable': False}
sub = self.env['sale.order'].with_context(context_mail).create({
'name': 'TestSubscription',
'is_subscription': True,
'note': "original subscription description",
'partner_id': self.user_portal.partner_id.id,
'sale_order_template_id': self.subscription_tmpl.id,
})
sub._onchange_sale_order_template_id()
# Same product for both lines
sub.order_line.product_uom_qty = 1
self.flush_tracking()
sub.action_confirm()
self.flush_tracking()
sub.order_line.product_uom_qty = 2
self.flush_tracking()
self.env['sale.order'].with_context(tracking_disable=False)._cron_recurring_create_invoice()
self.flush_tracking()
action = sub.with_context(tracking_disable=False).prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
renewal_so = renewal_so.with_context(tracking_disable=False)
renewal_so.order_line.product_uom_qty = 3
renewal_so.name = "Renewal"
self.flush_tracking()
sub.set_close()
self.flush_tracking()
renewal_so.action_confirm()
self.flush_tracking()
# Most of the time, the renewal invoice is created by the salesman
# before the renewal start date
renewal_invoices = renewal_so._create_invoices()
renewal_invoices._post()
order_log_ids = sub.order_log_ids.sorted('id')
sub_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log in
order_log_ids]
self.assertEqual(sub_data, [('0_creation', today, '1_draft', 21, 21),
('1_expansion', today, '3_progress', 21.0, 42.0),
('3_transfer', today, '5_renewed', -42, 0)])
renew_logs = renewal_so.order_log_ids.sorted('id')
renew_data = [(log.event_type, log.event_date, log.subscription_state, log.amount_signed, log.recurring_monthly) for log
in renew_logs]
self.assertEqual(sub.subscription_state, '5_renewed')
self.assertEqual(renew_data, [('3_transfer', today, '2_renewal', 42, 42),
('1_expansion', today, '3_progress', 21.0, 63.0)])
def test_renewal_different_period(self):
""" When a renewal quote is negotiated for more than a month, we need to update the start date of the
renewal quote if the parent is prolonged.
"""
with freeze_time("2023-01-01"):
# We reset the renew alert to make sure it will run with freezetime
self.subscription.write({'start_date': False, 'next_invoice_date': False})
self.subscription._onchange_sale_order_template_id()
self.assertEqual(self.subscription.plan_id, self.plan_month)
self.subscription.action_confirm()
self.subscription._create_recurring_invoice()
action = self.subscription.with_context(tracking_disable=False).prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
renewal_so = renewal_so.with_context(tracking_disable=False)
renewal_so.order_line.product_uom_qty = 3
renewal_so.name = "Renewal"
renewal_so.plan_id = self.plan_year
self.assertEqual(self.subscription.next_invoice_date, datetime.date(2023, 2, 1))
self.assertEqual(renewal_so.start_date, datetime.date(2023, 2, 1))
self.assertEqual(renewal_so.next_invoice_date, datetime.date(2023, 2, 1))
with freeze_time("2023-02-01"):
# the new invoice is created and validated by the customer
self.subscription._create_recurring_invoice()
self.assertEqual(self.subscription.next_invoice_date, datetime.date(2023, 3, 1))
self.assertEqual(renewal_so.start_date, datetime.date(2023, 3, 1))
self.assertEqual(renewal_so.next_invoice_date, datetime.date(2023, 3, 1))
def test_renew_with_different_currency(self):
pricelist_eur = self.env['product.pricelist'].create({
'name': 'Euro pricelist',
'currency_id': self.env.ref('base.EUR').id,
})
self.sub_product_tmpl.subscription_rule_ids.filtered(
lambda rule: rule.plan_id == self.plan_month
).fixed_price = 42
pricing_month_eur = self.env['product.pricelist.item'].create({
'plan_id': self.plan_month.id,
'pricelist_id': pricelist_eur.id,
'fixed_price': 420,
'product_tmpl_id': self.sub_product_tmpl.id,
})
self.sub_product_tmpl.subscription_rule_ids = [Command.link(pricing_month_eur.id)]
self.subscription_tmpl.sale_order_template_line_ids[1].unlink()
self.subscription.order_line.product_id.taxes_id = [Command.clear()]
self.subscription._onchange_sale_order_template_id()
self.subscription.action_confirm()
self.assertEqual(self.subscription.amount_total, 42)
self.subscription._create_recurring_invoice()
action = self.subscription.prepare_renewal_order()
renew_so = self.env['sale.order'].browse(action['res_id'])
self.assertEqual(renew_so.amount_total, 42)
renew_so.pricelist_id = pricelist_eur.id
renew_so.action_update_prices()
self.assertEqual(renew_so.amount_total, 420)
def test_renew_pricelist_currency_update(self):
"""
Assert that after renewing a subscription, changing the pricelist
to another one will recompute the order lines pricings.
"""
with freeze_time("2023-04-04"):
default_pricelist = self.pricelist
other_currency = self._enable_currency('EUR')
other_pricelist = self.env['product.pricelist'].create({
'name': 'Test Pricelist (EUR)',
'currency_id': other_currency.id,
})
other_currency.rate_ids = [Command.create({'rate': 20})]
pricing_month_1_usd = self.env['product.pricelist.item'].create({
'plan_id': self.plan_month.id,
'fixed_price': 100,
'pricelist_id': default_pricelist.id,
})
pricing_month_2_eur = self.env['product.pricelist.item'].create({
'plan_id': self.plan_month.id,
'fixed_price': 200,
'pricelist_id': other_pricelist.id,
})
sub_product_tmpl = self.env['product.template'].create({
'name': 'BaseTestProduct',
'type': 'service',
'recurring_invoice': True,
'uom_id': self.env.ref('uom.product_uom_unit').id,
'subscription_rule_ids': [Command.set((pricing_month_1_usd | pricing_month_2_eur).ids)]
})
sub = self.subscription.create({
'name': 'Company1 - Currency1',
'partner_id': self.user_portal.partner_id.id,
'currency_id': self.company.currency_id.id,
'plan_id': self.plan_month.id,
'pricelist_id': default_pricelist.id,
'order_line': [Command.create({
'name': "Product 1",
'product_id': sub_product_tmpl.product_variant_id.id,
'product_uom_qty': 1,
})]
})
sub.action_confirm()
self.flush_tracking()
# Assert that order line was created with correct pricing and currency.
self.assertEqual(sub.order_line[0].price_unit, 100.0, "Subscription product's order line must be created with default pricelist pricing (USD) having the price unit as 100.0.")
self.assertEqual(sub.order_line[0].order_id.currency_id.id, self.company.currency_id.id, "Subscription product's order line must be created with the default company currency (USD).")
self.assertEqual(sub.pricelist_id.id, default_pricelist.id, "Subscription must be created with the default company pricelist (in USD).")
self.env['sale.order']._cron_recurring_create_invoice()
self.flush_tracking()
with freeze_time("2023-04-05"):
action = sub.prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
# Assert that parent_line_id is saved in renewed subscription.
self.assertEqual(renewal_so.order_line[0].parent_line_id.id, sub.order_line[0].id, "The parent line of the order line should have been saved after subscription renewal.")
renewal_so.pricelist_id = other_pricelist.id
# Computes the updated price unit through 'Update Prices' button.
renewal_so.action_update_prices()
renewal_so.invalidate_recordset()
# Assert that updated pricing has the correct currency, price_unit and pricelist.
self.assertEqual(renewal_so.pricelist_id.id, other_pricelist.id, "Pricelist must update to the new one (in EUR) after performing a manual update.")
self.assertEqual(renewal_so.order_line[0].currency_id.id, other_currency.id, "Order line's currency should have been updated from USD to EUR after changing the pricelist.")
self.assertEqual(renewal_so.order_line[0].price_unit, 200.0, "Order line's price unit must update to 200.0 according to the new pricelist pricing (in EUR).")
# Update prices button removes the parent_line_id of order lines to recalculate pricings.
self.assertFalse(renewal_so.order_line[0].parent_line_id, "Parent order line should not exist anymore after updating prices, it was intentionally deleted for forcing price recalculation.")
def test_reopen_parent_child_canceled(self):
""" Renew a contract a few time, invoice it, check the computed amount of invoices
Then cancel a non invoiced renewal and see if it restart the parent
"""
with freeze_time("2023-11-03"):
self.flush_tracking()
self.subscription.write({
'start_date': False,
'next_invoice_date': False,
'partner_invoice_id': self.partner_a_invoice.id,
'partner_shipping_id': self.partner_a_shipping.id,
})
self.subscription.action_confirm()
self.flush_tracking()
self.subscription._create_recurring_invoice()
self.assertEqual(self.subscription.invoice_count, 1)
self.flush_tracking()
with freeze_time("2023-12-03"):
action = self.subscription.prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
self.flush_tracking()
renewal_so.action_confirm()
self.flush_tracking()
renewal_so._create_recurring_invoice()
self.assertEqual(renewal_so.invoice_count, 2)
self.flush_tracking()
with freeze_time("2024-01-03"):
action = renewal_so.prepare_renewal_order()
renewal_so2 = self.env['sale.order'].browse(action['res_id'])
self.flush_tracking()
renewal_so2.action_confirm()
self.flush_tracking()
self.assertEqual(renewal_so.subscription_state, '5_renewed')
self.assertEqual(renewal_so2.subscription_state, '3_progress')
renewal_so2._action_cancel()
renewal_so.end_date = False
self.flush_tracking()
self.assertEqual(renewal_so.subscription_state, '3_progress')
self.assertFalse(renewal_so2.subscription_state)
with freeze_time("2024-02-03"):
renewal_so._create_recurring_invoice()
self.flush_tracking()
(self.subscription | renewal_so | renewal_so2).invalidate_recordset(['invoice_ids', 'invoice_count'])
self.assertEqual(renewal_so.invoice_count, 3, "All contracts have the same count")
self.assertEqual(renewal_so2.invoice_count, 3, "All contracts have the same count")
self.assertEqual(self.subscription.invoice_count, 3, "All contracts have the same count")
def test_renew_simple_user(self):
user_sales_salesman = self.company_data['default_user_salesman']
self.assertTrue(user_sales_salesman.has_group('sales_team.group_sale_salesman'))
subscription = self.env['sale.order'].with_user(user_sales_salesman).create({
'partner_id': self.partner_a.id,
'company_id': self.company_data['company'].id,
'plan_id': self.plan_month.id,
'order_line': [
(0, 0, {
'name': self.product.name,
'product_id': self.product.id,
'product_uom_qty': 2.0,
'price_unit': 12,
})],
})
subscription.with_user(user_sales_salesman).action_confirm()
self.env['sale.order']._cron_recurring_create_invoice()
action = subscription.with_user(user_sales_salesman).prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
renewal_so.with_user(user_sales_salesman).action_confirm()
def test_renewal_duplicate_warrning(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.with_context(tracking_disable=False).prepare_renewal_order()
renewal_so = self.env['sale.order'].browse(action['res_id'])
self.assertFalse(renewal_so.duplicated_order_ids, "Renewal qutation should not marked as duplicate order and not show warning")