# Copyright 2023 ooops404 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import uuid from dateutil.relativedelta import relativedelta from odoo import exceptions, fields from odoo.tests import TransactionCase class TestSubscriptionOCA(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) cls.portal_user = cls.env.ref("base.demo_user0") cls.cash_journal = cls.env["account.journal"].search( [ ("type", "=", "cash"), ("company_id", "=", cls.env.ref("base.main_company").id), ] )[0] cls.sale_journal = cls.env["account.journal"].search( [ ("type", "=", "sale"), ("company_id", "=", cls.env.ref("base.main_company").id), ] )[0] cls.pricelist1 = cls.env["product.pricelist"].create( { "name": "pricelist for contract test", } ) cls.pricelist2 = cls.env["product.pricelist"].create( { "name": "pricelist for contract test 2", "discount_policy": "with_discount", } ) cls.partner = cls.env["res.partner"].create( { "name": "partner test subscription_oca", "property_product_pricelist": cls.pricelist1.id, "email": "demo1@demo.com", } ) cls.partner_2 = cls.env["res.partner"].create( { "name": "partner test subscription_oca 2", "property_product_pricelist": cls.pricelist1.id, "email": "demo2@demo.com", } ) cls.tax_10pc_incl = cls.env["account.tax"].create( { "name": "10% Tax incl", "amount_type": "percent", "amount": 10, "price_include": True, } ) cls.tax_0pc = cls.env["account.tax"].create( { "name": "0% Tax", "amount_type": "percent", "amount": 0, } ) cls.product_1 = cls.env.ref("product.product_product_1") cls.product_1.subscribable = True cls.product_1.taxes_id = [(6, 0, cls.tax_10pc_incl.ids)] cls.product_2 = cls.env.ref("product.product_product_2") cls.product_2.taxes_id = [(6, 0, cls.tax_0pc.ids)] cls.product_2.subscribable = True cls.country = cls.env["res.country"].search([], limit=1) cls.fiscal = cls.env["account.fiscal.position"].create( { "name": "Regime National", "auto_apply": True, "country_id": cls.country.id, "vat_required": True, "sequence": 10, } ) cls.tmpl1 = cls.create_sub_template({}) cls.tmpl2 = cls.create_sub_template( { "recurring_rule_boundary": "limited", "recurring_rule_type": "days", } ) cls.tmpl3 = cls.create_sub_template( { "recurring_rule_boundary": "unlimited", "recurring_rule_type": "weeks", } ) cls.tmpl4 = cls.create_sub_template( { "recurring_rule_boundary": "limited", "invoicing_mode": "invoice", "recurring_rule_type": "years", } ) cls.tmpl5 = cls.create_sub_template( { "recurring_rule_boundary": "unlimited", "invoicing_mode": "invoice", "recurring_rule_type": "days", } ) cls.stage = cls.env["sale.subscription.stage"].create( { "name": "Test Sub Stage", } ) cls.stage_2 = cls.env["sale.subscription.stage"].create( { "name": "Test Sub Stage 2", "type": "pre", } ) cls.tag = cls.env["sale.subscription.tag"].create( { "name": "Test Tag", } ) cls.sub1 = cls.create_sub({}) cls.sub2 = cls.create_sub( { "template_id": cls.tmpl3.id, } ) cls.sub3 = cls.create_sub( { "template_id": cls.tmpl2.id, "pricelist_id": cls.pricelist2.id, } ) cls.sub4 = cls.create_sub( { "template_id": cls.tmpl3.id, "recurring_rule_boundary": False, "date_start": fields.Date.today(), } ) cls.sub5 = cls.create_sub( { "template_id": cls.tmpl4.id, "pricelist_id": cls.pricelist2.id, "date_start": fields.Date.today(), "recurring_next_date": fields.Date.today() - relativedelta(days=1), } ) cls.sub6 = cls.create_sub( { "template_id": cls.tmpl5.id, "recurring_rule_boundary": False, "date_start": "2099-01-01", } ) cls.sub7 = cls.create_sub( { "template_id": cls.tmpl2.id, "pricelist_id": cls.pricelist2.id, "date_start": fields.Date.today() - relativedelta(days=100), "in_progress": True, } ) cls.sub8 = cls.create_sub( { "template_id": cls.tmpl2.id, "pricelist_id": cls.pricelist2.id, "date_start": fields.Date.today() - relativedelta(days=100), "in_progress": True, "journal_id": cls.cash_journal.id, } ) cls.sub_line = cls.create_sub_line(cls.sub1) cls.sub_line2 = cls.env["sale.subscription.line"].create( { "company_id": 1, "sale_subscription_id": cls.sub1.id, } ) cls.sub_line21 = cls.create_sub_line(cls.sub2) cls.sub_line22 = cls.create_sub_line(cls.sub2, cls.product_2.id) cls.sub_line31 = cls.create_sub_line(cls.sub3) cls.sub_line32 = cls.create_sub_line(cls.sub3, cls.product_2.id) cls.sub_line41 = cls.create_sub_line(cls.sub4) cls.sub_line42 = cls.create_sub_line(cls.sub4, cls.product_2.id) cls.sub_line51 = cls.create_sub_line(cls.sub5) cls.sub_line52 = cls.create_sub_line(cls.sub5, cls.product_2.id) cls.sub_line71 = cls.create_sub_line(cls.sub7) cls.sub_line72 = cls.create_sub_line(cls.sub7, cls.product_2.id) cls.close_reason = cls.env["sale.subscription.close.reason"].create( { "name": "Test Close Reason", } ) cls.sub_line2.read(["name", "price_unit"]) cls.sub_line2.unlink() # Pricelists. cls.pricelist_l1 = cls._create_price_list("Level 1") cls.pricelist_l2 = cls._create_price_list("Level 2") cls.pricelist_l3 = cls._create_price_list("Level 3") cls.env["product.pricelist.item"].create( { "pricelist_id": cls.pricelist_l3.id, "applied_on": "0_product_variant", "compute_price": "formula", "base": "pricelist", "base_pricelist_id": cls.pricelist_l1.id, "product_id": cls.product_1.id, } ) cls.env["product.pricelist.item"].create( { "pricelist_id": cls.pricelist_l2.id, "applied_on": "3_global", "compute_price": "formula", "base": "pricelist", "base_pricelist_id": cls.pricelist_l1.id, } ) cls.env["product.pricelist.item"].create( { "pricelist_id": cls.pricelist_l1.id, "applied_on": "3_global", "compute_price": "formula", "base": "standard_price", "fixed_price": 1000, } ) @classmethod def create_sub_template(cls, vals): code = str(uuid.uuid4().hex) default_vals = { "name": "Test Template " + code, "code": code, "description": "Some sort of subscription terms", "product_ids": [(6, 0, [cls.product_1.id, cls.product_2.id])], } default_vals.update(vals) rec = cls.env["sale.subscription.template"].create(default_vals) return rec @classmethod def create_sub(cls, vals): default_vals = { "company_id": 1, "partner_id": cls.partner.id, "template_id": cls.tmpl1.id, "tag_ids": [(6, 0, [cls.tag.id])], "stage_id": cls.stage.id, "pricelist_id": cls.pricelist1.id, "fiscal_position_id": cls.fiscal.id, } default_vals.update(vals) rec = cls.env["sale.subscription"].create(default_vals) return rec @classmethod def create_sub_line(cls, sub, prod=None): ssl = cls.env["sale.subscription.line"].create( { "company_id": 1, "sale_subscription_id": sub.id, "product_id": prod or cls.product_1.id, } ) return ssl @classmethod def _create_price_list(cls, name): return cls.env["product.pricelist"].create( { "name": name, "active": True, "currency_id": cls.env.ref("base.USD").id, "company_id": cls.env.user.company_id.id, } ) def test_subscription_oca_sale_order(self): # SO standard flow so = self.env["sale.order"].create( { "partner_id": self.partner.id, "partner_invoice_id": self.partner.id, "partner_shipping_id": self.partner.id, "order_line": [ ( 0, 0, { "name": self.product_1.name, "product_id": self.product_1.id, "product_uom_qty": 2, "product_uom": self.product_1.uom_id.id, "price_unit": self.product_1.list_price, }, ) ], } ) so._compute_subscriptions_count() self.assertEqual(so.subscriptions_count, 0) action = so.action_view_subscriptions() self.assertIsInstance(action, dict) so.with_context(uid=1).action_confirm() # without subs. def test_subscription_oca_sub_lines(self): # sale.subscription.line self.assertEqual(self.sub_line.name, self.sub_line.product_id.name) self.assertIsNotNone(self.sub_line.tax_ids) self.assertEqual(self.sub_line.price_unit, 30.75) self.assertEqual(self.sub_line.discount, 0) res = self.sub_line._get_display_price(self.product_2) self.assertEqual(res, 38.25) sol_res = self.sub_line._prepare_sale_order_line() self.assertIsInstance(sol_res, dict) move_res = self.sub_line._prepare_account_move_line() self.assertIsInstance(move_res, dict) def test_subscription_oca_sub_cron(self): # sale.subscription self.sub1.cron_subscription_management() # invoice should be created by cron inv_id = self.env["account.move"].search( [("subscription_id", "=", self.sub1.id)] ) self.assertEqual(len(inv_id), 1) self.assertEqual(self.sub1.recurring_total, 27.95) self.assertEqual(self.sub1.amount_total, 30.75) self.assertEqual(self.sub2.recurring_total, 66.2) self.assertEqual(self.sub2.amount_total, 69) def test_subscription_oca_sub1_workflow(self): res = self._collect_all_sub_test_results(self.sub1) self.assertTrue(res[0]) self.assertTrue(res[1]) self.assertEqual(res[3], 2) self.assertEqual(res[4], 2 * 30.75) self.assertEqual(res[5], 2) self.assertEqual(res[7], 1) self.assertEqual( res[9], fields.Date.today() + relativedelta(months=1), ) self.assertEqual(res[10], self.partner_2.property_product_pricelist.id) self.assertFalse(res[11]) self.assertEqual(res[12], "ir.actions.act_window") def test_subscription_oca_sub2_workflow(self): res = self._collect_all_sub_test_results(self.sub2) self.assertTrue(res[0]) self.assertTrue(res[1]) self.assertEqual(res[3], 2) self.assertEqual(res[4], 138) self.assertEqual(res[5], 2) self.assertEqual(res[7], 1) self.assertEqual( res[9], fields.Date.today() + relativedelta(weeks=1), ) self.assertEqual(res[10], self.partner_2.property_product_pricelist.id) self.assertFalse(res[11]) def test_subscription_oca_sub3_workflow(self): res = self._collect_all_sub_test_results(self.sub3) self.assertTrue(res[0]) self.assertTrue(res[1]) self.assertEqual(res[3], 2) self.assertEqual(res[4], 138) self.assertEqual(res[5], 2) self.assertEqual(res[6], "ir.actions.act_window") self.assertEqual(res[7], 1) self.assertEqual( res[9], fields.Date.today() + relativedelta(days=1), ) self.assertEqual(res[10], self.partner_2.property_product_pricelist.id) self.assertFalse(res[11]) def test_subscription_oca_sub4_workflow(self): res = self._collect_all_sub_test_results(self.sub4) self.assertTrue(res[0]) self.assertTrue(res[1]) self.assertEqual(res[2], "ir.actions.act_window") self.assertEqual(res[3], 2) self.assertEqual(res[4], 138) self.assertEqual(res[5], 2) self.assertEqual(res[7], 1) self.assertEqual( res[9], fields.Date.today() + relativedelta(weeks=1), ) self.assertEqual(res[10], self.partner_2.property_product_pricelist.id) self.assertFalse(res[11]) def test_subscription_oca_sub5_workflow(self): res = self._collect_all_sub_test_results(self.sub5) self.assertTrue(res[0]) self.assertTrue(res[1]) self.assertEqual(res[3], 2) self.assertEqual(res[4], 138) self.assertEqual(res[5], 2) self.assertEqual(res[7], 1) self.assertEqual( res[9], fields.Date.today() + relativedelta(years=1), ) self.assertEqual(res[10], self.partner_2.property_product_pricelist.id) self.assertFalse(res[11]) self.sub5.recurring_next_date = fields.Date.today() self.sub5.template_id = self.tmpl5 self.sub5._onchange_template_id() self.sub5.invoice_ids.unlink() self.sub5._onchange_template_id() def test_subscription_oca_sub7_workflow(self): res = self._collect_all_sub_test_results(self.sub7.with_context(uom=2)) self.assertTrue(res[0]) self.assertTrue(res[1]) self.assertEqual(res[3], 2) self.assertEqual(res[4], 138) self.assertEqual(res[5], 2) self.assertEqual(res[7], 1) self.assertEqual( res[9], fields.Date.today() + relativedelta(days=1), ) self.assertEqual(res[10], self.partner_2.property_product_pricelist.id) self.assertFalse(res[11]) def test_subscription_oca_sub8_workflow(self): subscription = self.sub8 subscription.create_sale_order() with self.assertRaises(exceptions.UserError): subscription.create_invoice() self.sub8.journal_id = self.sale_journal subscription.create_invoice() self.sub8.template_id.invoicing_mode = "invoice" with self.assertRaises(exceptions.UserError): subscription.generate_invoice() self.sub8.template_id.invoicing_mode = "invoice_send" with self.assertRaises(exceptions.UserError): subscription.generate_invoice() self.sub8.template_id.invoicing_mode = "sale_and_invoice" with self.assertRaises(exceptions.UserError): subscription.generate_invoice() # add lines and repeat self.sub_line81 = self.env["sale.subscription.line"].create( { "company_id": 1, "sale_subscription_id": self.sub8.id, "product_id": self.product_1.id, } ) self.sub_line82 = self.env["sale.subscription.line"].create( { "company_id": 1, "sale_subscription_id": self.sub8.id, "product_id": self.product_2.id, } ) subscription.create_sale_order() subscription.create_invoice() subscription.journal_id = self.sale_journal subscription.create_invoice() subscription.template_id.invoicing_mode = "invoice" subscription.generate_invoice() subscription.template_id.invoicing_mode = "invoice_send" subscription.generate_invoice() subscription.template_id.invoicing_mode = "sale_and_invoice" order = subscription.create_sale_order() order.with_context(uid=1).action_confirm() subscription.sale_subscription_line_ids.mapped("product_id").write( {"invoice_policy": "order"} ) subscription.generate_invoice() subscription._check_dates("2099-01-01", "2099-01-01") subscription._check_dates("2098-01-01", "2099-01-01") subscription._check_dates("2098-01-01", "2097-01-01") subscription._check_dates(fields.Date.today(), fields.Date.today()) subscription._check_dates(fields.Datetime.now(), fields.Datetime.now()) subscription.write({"stage_id": self.stage_2}) def test_subscription_oca_sub8_workflow_portal(self): # portal user subscription = self.sub8.with_user(self.portal_user) sale_order = subscription.create_sale_order() self.assertFalse(sale_order) move_id = subscription.with_user(self.portal_user).create_invoice() self.assertFalse(move_id) with self.assertRaises(exceptions.AccessError): subscription.manual_invoice() with self.assertRaises(exceptions.AccessError): subscription.calculate_recurring_next_date(fields.Datetime.now()) with self.assertRaises(exceptions.AccessError): subscription.partner_id = self.partner_2 def test_subscription_oca_sub_stage(self): # sale.subscription.stage self.stage._check_lot_product() # should not raise def test_x_subscription_oca_pricelist_related(self): res = self.partner.read(["subscription_count", "subscription_ids"]) self.assertEqual(res[0]["subscription_count"], 8) res = self.partner.action_view_subscription_ids() self.assertIsInstance(res, dict) sale_order = self.sub1.create_sale_order() sale_order.with_context(uid=1).create_subscription( sale_order.order_line, self.tmpl1 ) sale_order.get_next_interval( self.tmpl1.recurring_rule_type, self.tmpl1.recurring_interval ) self.sub_line.sale_subscription_id.pricelist_id.discount_policy = ( "without_discount" ) self.sub_line.product_uom_qty = 100 self.env.user.groups_id = [ (4, self.env.ref("product.group_discount_per_so_line").id) ] disc = self.sub_line.read(["discount"]) self.assertEqual(disc[0]["discount"], 0) wiz = self.env["close.reason.wizard"].create({}) wiz.with_context(active_id=self.sub1.id).button_confirm() self.assertEqual(self.sub1.stage_id.name, "Closed") self.assertFalse(self.sub1.active) self.tmpl1.action_view_subscription_ids() self.tmpl1.action_view_product_ids() self.tmpl1.read(["product_ids_count", "subscription_count"]) with self.assertRaises(exceptions.ValidationError): self.env["sale.subscription.stage"].create( { "name": "Test Sub Stage", "type": "post", } ) pricelist = self.sub_line.sale_subscription_id.pricelist_id.copy( {"currency_id": self.env.ref("base.THB").id} ) item1 = self.env["product.pricelist.item"].create( { "pricelist_id": pricelist.id, "product_id": self.product_1.product_variant_id.id, "name": "Test special rule 1", "applied_on": "0_product_variant", "price": 3, } ) self.sub_line.sale_subscription_id.pricelist_id = pricelist self.sub_line.product_uom_qty = 200 res = self.sub_line.read(["discount"]) self.assertEqual(res[0]["discount"], 100) item1.unlink() self.env["product.pricelist.item"].create( { "pricelist_id": pricelist.id, "product_id": self.product_1.product_variant_id.id, "name": "Test special rule 2", "base": "pricelist", "base_pricelist_id": self.pricelist1.id, "applied_on": "0_product_variant", } ) self.env["product.pricelist.item"].create( { "pricelist_id": self.pricelist1.id, "product_id": self.product_1.product_variant_id.id, "name": "Test special rule 3", "applied_on": "0_product_variant", "base": "standard_price", } ) self.sub_line.sale_subscription_id.pricelist_id = pricelist self.sub_line.product_uom_qty = 300 res = self.sub_line.read(["discount"]) self.assertEqual(res[0]["discount"], 100) def test_x_subscription_oca_pricelist_related_2(self): self.pricelist_l3.discount_policy = "without_discount" self.pricelist_l3.currency_id = self.env.ref("base.THB") self.sub_line.sale_subscription_id.pricelist_id = self.pricelist_l3 res = self.sub_line._get_display_price(self.product_1) self.assertAlmostEqual( int(res), round( self.product_1.standard_price * self.pricelist_l3.currency_id.rate_ids[:1].company_rate ), ) self.sub_line.product_uom_qty = 300 res = self.sub_line.read(["discount"]) self.assertEqual(res[0]["discount"], 0) def test_compute_display_name(self): stage = self.env["sale.subscription.stage"].create( { "name": "Test Stage", "type": "pre", } ) self.assertEqual(stage.display_name, "Test Stage", "display_name not computed") stage.name = "Updated Test Stage" stage._compute_display_name() self.assertEqual( stage.display_name, "Updated Test Stage", "display_name not computed" ) def _collect_all_sub_test_results(self, subscription): """Creates the invoice of a subscription and returns its data :param subscription: subscription to invoice :returns: Lists with the following data returns[0]: Created sale order record returns[1]: Created invoice record returns[2]: Type of the action to see a manually created invoice returns[3]: Number of invoices returns[4]: Amount total (wout taxes) of all the invoices returns[5]: Invoices count of the subscription returns[6]: Type of the action to the subscription invoices returns[7]: Sale order count of the subscription returns[8]: Id of the sale order returns[9]: Recurring next date of the subscription returns[10]: Id of the pricelist of the subsciption returns[11]: Fiscal position record of the subscription returns[12]: Type of the wizard action close a subscription returns[13]: Subscription stages """ test_res = [] sale_order = subscription.create_sale_order() test_res.append(sale_order) move_id = subscription.create_invoice() test_res.append(move_id) res = subscription.manual_invoice() test_res.append(res["type"]) inv_ids = self.env["account.move"].search( [("subscription_id", "=", subscription.id)] ) # self.assertEqual(len(inv_ids), 2) # self.assertEqual(sum(inv_ids.mapped("amount_total")), 2 * 30.75) # self.assertEqual(subscription.account_invoice_ids_count, 2) test_res.append(len(inv_ids)) test_res.append(sum(inv_ids.mapped("amount_total"))) test_res.append(subscription.account_invoice_ids_count) res = subscription.action_view_account_invoice_ids() # self.assertEqual(res["type"], "ir.actions.act_window") # self.assertEqual(subscription.sale_order_ids_count, 1) test_res.append(res["type"]) test_res.append(subscription.sale_order_ids_count) subscription.action_view_sale_order_ids() # self.assertIn(str(subscription.sale_order_ids.id), str(res["domain"])) test_res.append(subscription.sale_order_ids.id) subscription.calculate_recurring_next_date(fields.Datetime.now()) # self.assertEqual( # subscription.recurring_next_date, # fields.Date.today() + relativedelta(months=1), # ) test_res.append(subscription.recurring_next_date) subscription.partner_id = self.partner_2 subscription.onchange_partner_id() # self.assertEqual( # subscription.pricelist_id.id, self.partner_2.property_product_pricelist.id # ) test_res.append(subscription.pricelist_id.id) subscription.onchange_partner_id_fpos() # self.assertFalse(subscription.fiscal_position_id) test_res.append(subscription.fiscal_position_id) res = subscription.action_close_subscription() self.assertEqual(res["type"], "ir.actions.act_window") test_res.append(res["type"]) group_stage_ids = subscription._read_group_stage_ids( stages=self.env["sale.subscription.stage"].search([]), domain=[], order="id" ) test_res.append(group_stage_ids) return test_res