diff --git a/cooperator/models/account_move.py b/cooperator/models/account_move.py index 1a4c7b2f0..383e558ec 100644 --- a/cooperator/models/account_move.py +++ b/cooperator/models/account_move.py @@ -31,46 +31,6 @@ def _get_starting_sequence(self): starting_sequence = "R" + starting_sequence return starting_sequence - def create_user(self, partner): - user_obj = self.env["res.users"] - email = partner.email - - user = user_obj.search([("login", "=", email)]) - if user: - if self.company_id not in user.company_ids: - # add the company to the user's companies - user.company_ids = [(4, self.company_id.id, 0)] - else: - # set the company as the only company of the user - company_ids = [(6, 0, [self.company_id.id])] - user = user_obj.search([("login", "=", email), ("active", "=", False)]) - if user: - user.sudo().write( - { - "active": True, - "company_id": self.company_id.id, - "company_ids": company_ids, - } - ) - else: - user_values = { - "partner_id": partner.id, - "login": email, - } - user = user_obj.sudo()._signup_create_user(user_values) - # passing these values in _signup_create_user() does not work - # if the website module is loaded, because it overrides the - # method and overwrites them. - user.sudo().write( - { - "company_id": self.company_id.id, - "company_ids": company_ids, - } - ) - user.sudo().with_context(create_user=True).action_reset_password() - - return user - def get_mail_template_certificate(self): if self.partner_id.member: return self.company_id.get_cooperator_certificate_increase_mail_template() @@ -97,31 +57,6 @@ def get_subscription_register_vals(self, line, effective_date): "company_id": self.company_id.id, } - def get_membership_vals(self): - # flag the partner as an effective member - # if not yet cooperator we generate a cooperator number - vals = {} - cooperative_membership = self.partner_id.get_cooperative_membership( - self.company_id.id - ) - if not cooperative_membership.member and not cooperative_membership.old_member: - sub_reg_num = self.company_id.get_next_cooperator_number() - vals = { - "member": True, - "old_member": False, - "cooperator_register_number": int(sub_reg_num), - } - elif cooperative_membership.old_member: - vals = {"member": True, "old_member": False} - - return vals - - def set_membership(self): - vals = self.get_membership_vals() - self.partner_id.get_cooperative_membership(self.company_id.id).write(vals) - - return True - def _send_certificate_mail(self, certificate_email_template, sub_reg_line): if self.company_id.send_certificate_email: # we send the email with the certificate in attachment @@ -135,7 +70,7 @@ def set_cooperator_effective(self, effective_date): certificate_email_template = self.get_mail_template_certificate() - self.set_membership() + self.partner_id.get_cooperative_membership(self.company_id).set_effective() sub_reg_operation = self.company_id.get_next_register_operation_number() @@ -154,9 +89,6 @@ def set_cooperator_effective(self, effective_date): self._send_certificate_mail(certificate_email_template, sub_reg_line) - if self.company_id.create_user: - self.create_user(self.partner_id) - return True def post_process_confirm_paid(self, effective_date): @@ -181,7 +113,7 @@ def _invoice_paid_hook(self): result = super()._invoice_paid_hook() for invoice in self: cooperative_membership = invoice.partner_id.get_cooperative_membership( - invoice.company_id.id + invoice.company_id ) if not ( invoice.move_type == "out_invoice" diff --git a/cooperator/models/cooperative_membership.py b/cooperator/models/cooperative_membership.py index 2d6de4add..9c60be468 100644 --- a/cooperator/models/cooperative_membership.py +++ b/cooperator/models/cooperative_membership.py @@ -243,3 +243,83 @@ def get_share_quantities(self): total_shares[line.share_product_id.id] += line.share_number return total_shares + + def set_effective(self): + """Make sure the following things are in order for all memberships in + the recordset: + + - ``member`` is set to True and ``old_member`` is set to False. + - A cooperator register number is assigned if one did not yet exist. + - A user is created if one did not yet exist. + + This should all be idempotent. + """ + self.write({"member": True, "old_member": False}) + self.assign_cooperator_register_number() + for membership in self: + if membership.company_id.create_user: + membership.create_user() + + def assign_cooperator_register_number(self): + """Set a new cooperator register number on the memberships if one is not + already assigned. + """ + for membership in self: + # Already exists; nothing to do. + if membership.cooperator_register_number: + continue + membership.cooperator_register_number = ( + self.company_id.get_next_cooperator_number() + ) + + def create_user(self): + """Create a new user for the memberships if one does not already exist. + + If a user exists, but its 'active' field is False, then the user is + reactivated and the membership's company_id becomes the user's only + company_id. + + If a new partner is created, an e-mail is sent to them with instructions + on how to set their passport. + """ + user_obj = self.env["res.users"].sudo() + + for membership in self: + partner = membership.partner_id + email = partner.email + + users = user_obj.with_context(active_test=False).search( + [("partner_id", "=", partner.id)] + ) + if users: + inactive = users.filtered(lambda user: not user.active) + active = users - inactive + inactive.write( + { + "active": True, + # set the company as the only company of the user for + # returning users + "company_id": membership.company_id.id, + "company_ids": [fields.Command.set([membership.company_id.id])], + } + ) + active.write( + # add the company to the users' companies + {"company_ids": [fields.Command.link(membership.company_id.id)]} + ) + else: + user_values = { + "partner_id": partner.id, + "login": email, + } + user = user_obj._signup_create_user(user_values) + # passing these values in _signup_create_user() does not work + # if the website module is loaded, because it overrides the + # method and overwrites them. + user.write( + { + "company_id": membership.company_id.id, + "company_ids": [fields.Command.set([membership.company_id.id])], + } + ) + user.with_context(create_user=True).action_reset_password() diff --git a/cooperator/models/operation_request.py b/cooperator/models/operation_request.py index d298428e6..542408c4e 100644 --- a/cooperator/models/operation_request.py +++ b/cooperator/models/operation_request.py @@ -367,34 +367,19 @@ def execute_operation(self): _("Converting just part of the shares is not yet implemented") ) elif self.operation_type == "transfer": - partner_vals = {"member": True} if self.receiver_not_member: partner = self.subscription_request.setup_partner() self.subscription_request.state = "done" - cooperator_number = self.company_id.get_next_cooperator_number() - # fixme: get_eater_vals() is really specific and should not be - # called from here. - partner_vals.update( - sub_request.get_eater_vals(partner, self.share_product_id) - ) - partner_vals["cooperator_register_number"] = cooperator_number - partner.write(partner_vals) self.partner_id_to = partner - else: - # means an old member or cooperator candidate - if not self.partner_id_to.member: - if self.partner_id_to.cooperator_register_number == 0: - cooperator_number = self.company_id.get_next_cooperator_number() - partner_vals["cooperator_register_number"] = cooperator_number - # fixme: get_eater_vals() is really specific and should - # not be called from here. - partner_vals.update( - sub_request.get_eater_vals( - self.partner_id_to, self.share_product_id - ) - ) - partner_vals["old_member"] = False - self.partner_id_to.write(partner_vals) + to_membership = self.partner_id_to.get_cooperative_membership( + self.company_id + ) + to_membership.set_effective() + self.partner_id_to.write( + # FIXME: get_eater_vals() is really specific and should not be + # called from here. + sub_request.get_eater_vals(self.partner_id_to, self.share_product_id) + ) # remove the parts to the giver self.hand_share_over(self.partner_id, self.share_product_id, self.quantity) # give the share to the receiver diff --git a/cooperator/models/res_partner.py b/cooperator/models/res_partner.py index 887d4d613..07ec09f68 100644 --- a/cooperator/models/res_partner.py +++ b/cooperator/models/res_partner.py @@ -74,7 +74,7 @@ def _get_share_type(self): @api.depends_context("company") def _compute_cooperative_membership_id(self): - company_id = self.env.company.id + company_id = self.env.company for record in self: record.cooperative_membership_id = record.get_cooperative_membership( company_id @@ -268,7 +268,7 @@ def get_cooperative_membership(self, company_id): self.ensure_one() return self.env["cooperative.membership"].search( [ - ("company_id", "=", company_id), + ("company_id", "=", company_id.id), ("partner_id", "=", self.id), ] ) @@ -279,13 +279,20 @@ def create_cooperative_membership(self, company_id): for record in self: result |= cooperative_membership_model.create( { - "company_id": company_id, + "company_id": company_id.id, "partner_id": record.id, "cooperator": True, } ) return result + def get_create_cooperative_membership(self, company_id): + self.ensure_one() + membership = self.get_cooperative_membership(company_id) + if not membership: + membership = self.create_cooperative_membership(company_id) + return membership + def get_share_quantities(self, company_id=None): """Return a defaultdict(int) with the amount of shares per product id, for the partner's cooperative_membership_id. @@ -298,7 +305,7 @@ def get_share_quantities(self, company_id=None): if company_id is None: company_id = self.env.company - coop_membership = self.get_cooperative_membership(company_id.id) + coop_membership = self.get_cooperative_membership(company_id) if coop_membership: return coop_membership.get_share_quantities() else: diff --git a/cooperator/models/subscription_request.py b/cooperator/models/subscription_request.py index 143073b26..603f3f5f4 100644 --- a/cooperator/models/subscription_request.py +++ b/cooperator/models/subscription_request.py @@ -113,10 +113,11 @@ def _adapt_create_vals_and_membership_from_partner(self, vals, partner): update it if needed and set vals accordingly. """ company_id = vals.get("company_id", self.env.company.id) + company_id = self.env["res.company"].browse(company_id) cooperative_membership = partner.get_cooperative_membership(company_id) member = cooperative_membership and cooperative_membership.member pending_requests_domain = [ - ("company_id", "=", company_id), + ("company_id", "=", company_id.id), ("partner_id", "=", partner.id), ("state", "in", ("draft", "waiting", "done")), ] @@ -819,12 +820,10 @@ def setup_partner(self): partner = self._find_or_create_partner() - cooperative_membership = partner.get_cooperative_membership(self.company_id.id) - if not cooperative_membership: - cooperative_membership = partner.create_cooperative_membership( - self.company_id.id - ) - elif not cooperative_membership.cooperator: + cooperative_membership = partner.get_create_cooperative_membership( + self.company_id + ) + if not cooperative_membership.cooperator: cooperative_membership.cooperator = True if self.is_company and not partner.has_representative(): diff --git a/cooperator/tests/test_cooperator.py b/cooperator/tests/test_cooperator.py index f00fd4642..a628a3b3f 100644 --- a/cooperator/tests/test_cooperator.py +++ b/cooperator/tests/test_cooperator.py @@ -106,6 +106,27 @@ def test_effective_date_from_payment_date(self): partner = self.subscription_request_1.partner_id self.assertEqual(partner.effective_date, date(2022, 6, 21)) + @users("user-cooperator") + def test_pay_invoice_sets_effective(self): + self.subscription_request_1.validate_subscription_request() + partner = self.subscription_request_1.partner_id + membership = partner.cooperative_membership_id + self.assertFalse(membership.member) + self.assertFalse(membership.cooperator_register_number) + self.assertFalse(membership.partner_id.user_ids) + # Make sure to create a user. + self.env.company.sudo().create_user = True + + invoice = self.subscription_request_1.capital_release_request + self.pay_invoice(invoice, date(2022, 6, 21)) + + self.assertTrue(membership.member) + self.assertFalse(membership.old_member) + self.assertTrue(membership.cooperator_register_number) + self.assertTrue(membership.partner_id.user_ids) + self.assertEqual(membership.partner_id.user_ids.company_id, self.env.company) + self.assertEqual(membership.partner_id.user_ids.company_ids, self.env.company) + @users("user-cooperator") def test_effective_date_from_account_move_date(self): # the effective date should also work with an account.move without an @@ -425,7 +446,7 @@ def test_create_subscription_with_multiple_matching_email(self): "email": "dummy@example.net", } ) - partner2.create_cooperative_membership(self.company.id) + partner2.create_cooperative_membership(self.company) vals = self.get_dummy_subscription_requests_vals() vals["email"] = "dummy@example.net" subscription_request = self.env["subscription.request"].create(vals) @@ -499,7 +520,7 @@ def test_create_subscription_with_multiple_matching_company_register_number(self "is_company": True, } ) - company_partner2.create_cooperative_membership(self.company.id) + company_partner2.create_cooperative_membership(self.company) company_partner2.cooperator = True vals = self.get_dummy_company_subscription_requests_vals() subscription_request = self.env["subscription.request"].create(vals) @@ -1426,3 +1447,140 @@ def test_cooperator_register_number_sequence_per_company(self): cooperative_membership_2 = cooperator_2.cooperative_membership_ids[1] self.assertEqual(cooperative_membership_2.company_id, company_2) self.assertEqual(cooperative_membership_2.cooperator_register_number, 1) + + def test_get_create_cooperative_membership_create(self): + """Create a membership if one does not yet exist.""" + partner = self.env["res.partner"].create({"name": "Jane Doe"}) + membership = partner.get_create_cooperative_membership(self.env.company) + self.assertTrue(membership) + + def test_get_create_cooperative_membership_get(self): + """Get the membership if one exists.""" + partner = self.env["res.partner"].create({"name": "Jane Doe"}) + partner.create_cooperative_membership(self.env.company) + expected = partner.get_cooperative_membership(self.env.company) + result = partner.get_create_cooperative_membership(self.env.company) + self.assertEqual(result, expected) + + def test_set_effective(self): + """Expect set_effective to do the things it says it does.""" + partner = self.env["res.partner"].create( + {"name": "Jane Doe", "email": "jane@example.com"} + ) + membership = partner.create_cooperative_membership(self.env.company) + self.assertFalse(membership.member) + self.assertFalse(membership.cooperator_register_number) + self.assertFalse(membership.partner_id.user_ids) + # Falsely set this to True. + membership.old_member = True + # Make sure to create a user. + self.env.company.create_user = True + membership.set_effective() + self.assertTrue(membership.member) + self.assertFalse(membership.old_member) + self.assertTrue(membership.cooperator_register_number) + self.assertTrue(membership.partner_id.user_ids) + self.assertEqual(membership.partner_id.user_ids.company_id, self.env.company) + self.assertEqual(membership.partner_id.user_ids.company_ids, self.env.company) + + def test_create_user_inactive(self): + """When creating a user that is inactive, set it active and replace the + companies. + """ + partner = self.env["res.partner"].create( + {"name": "Jane Doe", "email": "jane@example.com"} + ) + other_company = self.env["res.company"].create({"name": "Foo Company"}) + user = self.env["res.users"].create( + { + "partner_id": partner.id, + "login": partner.email, + "company_id": other_company.id, + "company_ids": [fields.Command.set([other_company.id])], + } + ) + user.active = False + membership = partner.create_cooperative_membership(self.env.company) + membership.create_user() + new_user = self.env["res.users"].search([("login", "=", "jane@example.com")]) + self.assertEqual(new_user, user) + self.assertEqual(user.company_id, self.env.company) + self.assertEqual(user.company_ids, self.env.company) + + def test_create_user_new_company(self): + """If calling create_user for a user that already exists, but which + doesn't belong to the membership's company yet, add the company. + """ + partner = self.env["res.partner"].create( + {"name": "Jane Doe", "email": "jane@example.com"} + ) + other_company = self.env["res.company"].create({"name": "Foo Company"}) + user = self.env["res.users"].create( + { + "partner_id": partner.id, + "login": partner.email, + "company_id": other_company.id, + "company_ids": [fields.Command.set([other_company.id])], + } + ) + membership = partner.create_cooperative_membership(self.env.company) + membership.create_user() + new_user = self.env["res.users"].search([("login", "=", "jane@example.com")]) + self.assertEqual(new_user, user) + self.assertEqual(user.company_id, other_company) + self.assertEqual(user.company_ids, self.env.company | other_company) + + def test_create_user_different_login(self): + """If a partner has a user but the user has a different login address, + correctly detect that. + """ + partner = self.env["res.partner"].create( + {"name": "Jane Doe", "email": "jane@example.com"} + ) + other_company = self.env["res.company"].create({"name": "Foo Company"}) + user = self.env["res.users"].create( + { + "partner_id": partner.id, + "login": "other@example.com", + "company_id": other_company.id, + "company_ids": [fields.Command.set([other_company.id])], + } + ) + membership = partner.create_cooperative_membership(self.env.company) + membership.create_user() + self.assertEqual(partner.user_ids, user) + self.assertEqual(user.company_ids, self.env.company | other_company) + self.assertFalse( + self.env["res.users"].search([("login", "=", "jane@example.com")]) + ) + + def test_create_user_multiple_users(self): + """If a partner has multiple users, add the company to all of them.""" + partner = self.env["res.partner"].create( + {"name": "Jane Doe", "email": "jane@example.com"} + ) + other_company = self.env["res.company"].create({"name": "Foo Company"}) + active_user = self.env["res.users"].create( + { + "partner_id": partner.id, + "login": "other@example.com", + "company_id": other_company.id, + "company_ids": [fields.Command.set([other_company.id])], + } + ) + inactive_user = self.env["res.users"].create( + { + "partner_id": partner.id, + "login": "foobar@example.com", + "company_id": other_company.id, + "company_ids": [fields.Command.set([other_company.id])], + } + ) + inactive_user.active = False + membership = partner.create_cooperative_membership(self.env.company) + membership.create_user() + self.assertEqual(len(partner.user_ids), 2) + self.assertEqual(active_user.company_ids, self.env.company | other_company) + self.assertEqual(inactive_user.company_ids, self.env.company) + self.assertEqual(inactive_user.company_id, self.env.company) + self.assertTrue(inactive_user.active)