diff --git a/subscription_oca/tests/test_subscription_oca.py b/subscription_oca/tests/test_subscription_oca.py index d02254072..70e4af7f8 100644 --- a/subscription_oca/tests/test_subscription_oca.py +++ b/subscription_oca/tests/test_subscription_oca.py @@ -1,9 +1,11 @@ # 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 fields +from odoo import exceptions, fields from odoo.tests import SavepointCase @@ -12,20 +14,41 @@ class TestSubscriptionOCA(SavepointCase): def setUpClass(cls): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - cls.pricelist = cls.env["product.pricelist"].create( - {"name": "pricelist for contract test"} + 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.pricelist.id, + "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.pricelist.id, + "property_product_pricelist": cls.pricelist1.id, "email": "demo2@demo.com", } ) @@ -38,8 +61,11 @@ class TestSubscriptionOCA(SavepointCase): } ) 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.subscribable = True + cls.country = cls.env["res.country"].search([], limit=1) cls.fiscal = cls.env["account.fiscal.position"].create( { @@ -50,47 +76,213 @@ class TestSubscriptionOCA(SavepointCase): "sequence": 10, } ) - cls.tmpl = cls.env["sale.subscription.template"].create( + + cls.tmpl1 = cls.create_sub_template({}) + cls.tmpl2 = cls.create_sub_template( { - "name": "Test Template", - "code": "OMG", - "description": "Some sort of subscription terms", - "product_ids": [(6, 0, [cls.product_1.id, cls.product_2.id])], + "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.sub = cls.env["sale.subscription"].create( + + cls.sub1 = cls.create_sub({}) + cls.sub2 = cls.create_sub( { - "company_id": 1, - "partner_id": cls.partner.id, - "template_id": cls.tmpl.id, - "tag_ids": [(6, 0, [cls.tag.id])], - "stage_id": cls.stage.id, - "pricelist_id": cls.pricelist.id, - "fiscal_position_id": cls.fiscal.id, + "template_id": cls.tmpl3.id, } ) - cls.sub_line = cls.env["sale.subscription.line"].create( + cls.sub3 = cls.create_sub( { - "company_id": 1, - "sale_subscription_id": cls.sub.id, - "product_id": cls.product_1.id, + "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_default = cls.env.ref("product.list0") + 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 @@ -118,9 +310,9 @@ class TestSubscriptionOCA(SavepointCase): self.assertEqual(so.subscriptions_count, 0) action = so.action_view_subscriptions() self.assertIsInstance(action, dict) - so.action_confirm() # without subs. + so.with_context(uid=1).action_confirm() # without subs. - def test_subscription_oca_sub_line(self): + 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) @@ -135,48 +327,317 @@ class TestSubscriptionOCA(SavepointCase): def test_subscription_oca_sub_cron(self): # sale.subscription - self.sub.cron_subscription_management() + self.sub1.cron_subscription_management() # invoice should be created by cron inv_id = self.env["account.move"].search( - [("subscription_id", "=", self.sub.id)] + [("subscription_id", "=", self.sub1.id)] ) self.assertEqual(len(inv_id), 1) - self.assertEqual(self.sub.recurring_total, 27.95) - self.assertEqual(self.sub.amount_total, 30.75) + 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_sub_workflow(self): - sale_order = self.sub.create_sale_order() - self.assertTrue(sale_order) - move_id = self.sub.create_invoice() - self.assertTrue(move_id) - res = self.sub.manual_invoice() - self.assertEqual(res["type"], "ir.actions.act_window") - inv_ids = self.env["account.move"].search( - [("subscription_id", "=", self.sub.id)] - ) - self.assertEqual(len(inv_ids), 2) - self.assertEqual(sum(inv_ids.mapped("amount_total")), 2 * 30.75) - self.assertEqual(self.sub.account_invoice_ids_count, 2) - res = self.sub.action_view_account_invoice_ids() - self.assertEqual(res["type"], "ir.actions.act_window") - self.assertEqual(self.sub.sale_order_ids_count, 1) - res = self.sub.action_view_sale_order_ids() - self.assertIn(str(self.sub.sale_order_ids.id), str(res["domain"])) - self.sub.calculate_recurring_next_date(fields.Datetime.now()) + 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( - self.sub.recurring_next_date, + res[9], fields.Date.today() + relativedelta(months=1), ) - self.sub.partner_id = self.partner_2 - self.sub.onchange_partner_id() + 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( - self.sub.pricelist_id.id, self.partner_2.property_product_pricelist.id + res[9], + fields.Date.today() + relativedelta(weeks=1), ) - self.sub.onchange_partner_id_fpos() - self.assertFalse(self.sub.fiscal_position_id) - res = self.sub.action_close_subscription() - self.assertEqual(res["type"], "ir.actions.act_window") + 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), 514) + self.sub_line.product_uom_qty = 300 + res = self.sub_line.read(["discount"]) + self.assertEqual(res[0]["discount"], 0) + + def _collect_all_sub_test_results(self, subscription): + 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