# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from unittest.mock import patch from stdnum import get_cc_module try: import phonenumbers except ImportError: phonenumbers = None from trytond.exceptions import UserError from trytond.model.exceptions import AccessError from trytond.modules.party.party import ( IDENTIFIER_TYPES, IDENTIFIER_VAT, replace_vat) from trytond.pool import Pool from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.tools import is_instance_method from trytond.transaction import Transaction class PartyCheckEraseMixin: @with_transaction() def test_check_erase_party(self): "Test check erase of party" pool = Pool() Erase = pool.get('party.erase', type='wizard') Session = pool.get('ir.session.wizard') party = self.setup_check_erase_party() session = Session() session.save() Erase(session.id).check_erase(party) def setup_check_erase_party(self): pool = Pool() Party = pool.get('party.party') party = Party(active=False) party.save() return party class PartyCheckReplaceMixin: @with_transaction() def test_check_replace_party(self): "Test fields to replace" pool = Pool() Replace = pool.get('party.replace', type='wizard') for model_name, field_name in Replace.fields_to_replace(): with self.subTest(model_name=model_name, field_name=field_name): Model = pool.get(model_name) field = getattr(Model, field_name) if field._type == 'reference': if isinstance(field.selection, (tuple, list)): self.assertIn('party.party', dict(field.selection)) else: sel_func = getattr(Model, field.selection) instance_sel_func = is_instance_method( Model, field.selection) if not instance_sel_func: self.assertIn('party.party', dict(sel_func())) else: self.assertEqual(field._type, 'many2one') self.assertEqual(field.model_name, 'party.party') class PartyTestCase( PartyCheckEraseMixin, PartyCheckReplaceMixin, ModuleTestCase): 'Test Party module' module = 'party' @with_transaction() def test_category(self): 'Create category' pool = Pool() Category = pool.get('party.category') category1, = Category.create([{ 'name': 'Category 1', }]) self.assertTrue(category1.id) @with_transaction() def test_category_recursion(self): 'Test category recursion' pool = Pool() Category = pool.get('party.category') category1, = Category.create([{ 'name': 'Category 1', }]) category2, = Category.create([{ 'name': 'Category 2', 'parent': category1.id, }]) self.assertTrue(category2.id) self.assertRaises(Exception, Category.write, [category1], { 'parent': category2.id, }) @with_transaction() def test_party(self): 'Create party' pool = Pool() Party = pool.get('party.party') party1, = Party.create([{ 'name': 'Party 1', }]) self.assertTrue(party1.id) @with_transaction() def test_party_code(self): 'Test party code constraint' pool = Pool() Party = pool.get('party.party') party1, = Party.create([{ 'name': 'Party 1', }]) code = party1.code self.assertTrue(party1.code_alnum.isalnum()) self.assertTrue(party1.code_digit) party2, = Party.create([{ 'name': 'Party 2', }]) self.assertRaises(Exception, Party.write, [party2], { 'code': code, }) @with_transaction() def test_party_autocomplete_eu_vat(self): "Test party autocomplete eu_vat" pool = Pool() Party = pool.get('party.party') self.assertEqual( Party.autocomplete('BE500923836'), [{ 'id': None, 'name': 'BE0500923836', 'defaults': { 'identifiers': [{ 'type': 'eu_vat', 'code': 'BE0500923836', }], }, }]) @with_transaction() def test_party_autocomplete_eu_vat_without_country(self): "Test party autocomplete eu_vat without country" pool = Pool() Party = pool.get('party.party') self.assertIn({ 'id': None, 'name': 'BE0500923836', 'defaults': { 'identifiers': [{ 'type': 'eu_vat', 'code': 'BE0500923836', }], }, }, Party.autocomplete('500923836')) @with_transaction() def test_party_autocomplete_be_vat(self): "Test party autocomplete be_vat" pool = Pool() Party = pool.get('party.party') Configuration = pool.get('party.configuration') configuration = Configuration(1) configuration.identifier_types = ['be_vat'] configuration.save() self.assertEqual( Party.autocomplete('BE500923836'), [{ 'id': None, 'name': '0500923836', 'defaults': { 'identifiers': [{ 'type': 'be_vat', 'code': '0500923836', }], }, }]) @with_transaction() def test_party_default_get_eu_vat(self): "Test party default_get eu_vat" pool = Pool() Party = pool.get('party.party') Country = pool.get('country.country') belgium = Country(code='BE', name="Belgium") belgium.save() eu_vat = get_cc_module('eu', 'vat') with patch.object(eu_vat, 'check_vies') as check_vies: check_vies.return_value = { 'valid': True, 'name': "Tryton Foundation", 'address': "Street", } with Transaction().set_context(default_identifiers=[{ 'type': 'eu_vat', 'code': 'BE0500923836', }]): self.assertEqual( Party.default_get(['name', 'addresses', 'identifiers']), { 'name': "Tryton Foundation", 'addresses': [{ 'street': "Street", 'country': belgium.id, 'country.': { 'rec_name': "🇧🇪 Belgium", }, }], 'identifiers': [{ 'type': 'eu_vat', 'code': 'BE0500923836', }], }) @with_transaction() def test_address_strip(self): "Test address strip" pool = Pool() Address = pool.get('party.address') for value, result in [ ('', ''), (' ', ''), ('\n', ''), (' \n \n', ''), ('foo\n\n', 'foo'), (',foo', 'foo'), (',,foo', 'foo'), (', , foo', 'foo'), ('foo, , bar', 'foo, bar'), ('foo,,', 'foo'), ('foo, , ', 'foo'), (',/–foo,/–', 'foo'), ('foo, /bar', 'foo/bar'), ('foo, /, bar', 'foo, bar'), ('foo,/bar\nfoo, –\n/bar, foo', 'foo/bar\nfoo\nbar, foo'), ]: with self.subTest(value=value): self.assertEqual(Address._strip(value), result) @with_transaction() def test_address(self): 'Create address' pool = Pool() Party = pool.get('party.party') Address = pool.get('party.address') party1, = Party.create([{ 'name': 'Party 1', }]) address, = Address.create([{ 'party': party1.id, 'street': 'St sample, 15', 'city': 'City', }]) self.assertTrue(address.id) self.assertMultiLineEqual(address.full_address, "St sample, 15\n" "City") with Transaction().set_context(address_with_party=True): address = Address(address.id) self.assertMultiLineEqual(address.full_address, "Party 1\n" "St sample, 15\n" "City") @with_transaction() def test_address_structured(self): "Test address structured" pool = Pool() Address = pool.get('party.address') Country = pool.get('country.country') us = Country(code='US', name="US") us.save() be = Country(code='BE', name="BE") be.save() address = Address( street_name="St sample", building_number="15", unit_number="B", floor_number=1, room_number=3, ) for country, result in [ (None, "St sample 15/B/1/3"), (us, "15 St sample B"), (be, "St sample 15 box B"), ]: with self.subTest(country=country.code if country else None): address.country = country self.assertEqual(address.street, result) @with_transaction() def test_address_numbers(self): "Test address numbers" pool = Pool() Party = pool.get('party.party') Address = pool.get('party.address') party = Party(name="Dunder Mifflin") party.save() address = Address( party=party, street_name="St sample", building_number="15", unit_number="B", floor_number=1, room_number=3, ) address.save() self.assertEqual(address.numbers, "15/B/1/3") @with_transaction() def test_address_autocomplete_postal_code(self): "Test autocomplete of postal code" pool = Pool() Country = pool.get('country.country') Subdivision = pool.get('country.subdivision') PostalCode = pool.get('country.postal_code') Address = pool.get('party.address') country = Country(name="Country") country.save() subdivision = Subdivision(name="Subdivision", country=country) subdivision.save() postal_code = PostalCode( country=country, subdivision=subdivision, postal_code="12345", city="City", ) postal_code.save() address = Address( country=country, subdivision=subdivision, city="C") completions = address.autocomplete_postal_code() self.assertEqual(completions, ["12345"]) @with_transaction() def test_address_autocomplete_postal_code_empty(self): "Test autocomplete with empty postal code" pool = Pool() Country = pool.get('country.country') PostalCode = pool.get('country.postal_code') Address = pool.get('party.address') country = Country(name="Country") country.save() for postal_code in [None, '12345']: PostalCode( country=country, postal_code=postal_code, city="City", ).save() completions = Address(city="City").autocomplete_postal_code() self.assertEqual(completions, ["12345"]) @with_transaction() def test_address_autocomplete_city(self): "Test autocomplete of city" pool = Pool() Country = pool.get('country.country') Subdivision = pool.get('country.subdivision') PostalCode = pool.get('country.postal_code') Address = pool.get('party.address') country = Country(name="Country") country.save() subdivision = Subdivision(name="Subdivision", country=country) subdivision.save() postal_code = PostalCode( country=country, subdivision=subdivision, postal_code="12345", city="City", ) postal_code.save() address = Address( country=country, subdivision=subdivision, postal_code="123") completions = address.autocomplete_city() self.assertEqual(completions, ["City"]) @with_transaction() def test_address_autocomplete_city_empty(self): "Test autocomplete with empty city" pool = Pool() Country = pool.get('country.country') PostalCode = pool.get('country.postal_code') Address = pool.get('party.address') country = Country(name="Country") country.save() for city in [None, "City"]: PostalCode( country=country, postal_code="12345", city=city, ).save() completions = Address(postal_code="12345").autocomplete_city() self.assertEqual(completions, ["City"]) @with_transaction() def test_full_address_country_subdivision(self): 'Test full address with country and subdivision' pool = Pool() Party = pool.get('party.party') Country = pool.get('country.country') Subdivision = pool.get('country.subdivision') Address = pool.get('party.address') party, = Party.create([{ 'name': 'Party', }]) country = Country(name='Country') country.save() subdivision = Subdivision( name='Subdivision', country=country, code='SUB', type='area') subdivision.save() address, = Address.create([{ 'party': party.id, 'subdivision': subdivision.id, 'country': country.id, }]) self.assertMultiLineEqual(address.full_address, "Subdivision\n" "COUNTRY") @with_transaction() def test_address_get_no_type(self): "Test address_get with no type" pool = Pool() Party = pool.get('party.party') Address = pool.get('party.address') party, = Party.create([{}]) address1, address2 = Address.create([{ 'party': party.id, 'sequence': 1, }, { 'party': party.id, 'sequence': 2, }]) address = party.address_get() self.assertEqual(address, address1) @with_transaction() def test_address_get_no_address(self): "Test address_get with no address" pool = Pool() Party = pool.get('party.party') party, = Party.create([{}]) address = party.address_get() self.assertEqual(address, None) @with_transaction() def test_address_get_inactive(self): "Test address_get with inactive" pool = Pool() Party = pool.get('party.party') Address = pool.get('party.address') party, = Party.create([{}]) address1, address2 = Address.create([{ 'party': party.id, 'sequence': 1, 'active': False, }, { 'party': party.id, 'sequence': 2, 'active': True, }]) address = party.address_get() self.assertEqual(address, address2) @with_transaction() def test_address_get_type(self): "Test address_get with type" pool = Pool() Party = pool.get('party.party') Address = pool.get('party.address') party, = Party.create([{}]) address1, address2 = Address.create([{ 'party': party.id, 'sequence': 1, 'postal_code': None, }, { 'party': party.id, 'sequence': 2, 'postal_code': '1000', }]) address = party.address_get(type='postal_code') self.assertEqual(address, address2) @with_transaction() def test_party_label_report(self): 'Test party label report' pool = Pool() Party = pool.get('party.party') Label = pool.get('party.label', type='report') party1, = Party.create([{ 'name': 'Party 1', }]) oext, content, _, _ = Label.execute([party1.id], {}) self.assertEqual(oext, 'odt') self.assertTrue(content) @with_transaction() def test_party_without_name(self): 'Create party without name' pool = Pool() Party = pool.get('party.party') party2, = Party.create([{}]) self.assertTrue(party2.id) code = party2.code self.assertEqual(party2.rec_name, '[' + code + ']') @unittest.skipIf(phonenumbers is None, 'requires phonenumbers') @with_transaction() def test_phone_number_format(self): 'Test phone number format' pool = Pool() Party = pool.get('party.party') ContactMechanism = pool.get('party.contact_mechanism') transaction = Transaction() def create(mtype, mvalue): party1, = Party.create([{ 'name': 'Party 1', }]) return ContactMechanism.create([{ 'party': party1.id, 'type': mtype, 'value': mvalue, }])[0] # Test format on create mechanism = create('phone', '+442083661177') self.assertEqual(mechanism.value, '+44 20 8366 1177') self.assertEqual(mechanism.value_compact, '+442083661177') # Test format on write mechanism.value = '+442083661178' mechanism.save() self.assertEqual(mechanism.value, '+44 20 8366 1178') self.assertEqual(mechanism.value_compact, '+442083661178') ContactMechanism.write([mechanism], { 'value': '+442083661179', }) self.assertEqual(mechanism.value, '+44 20 8366 1179') self.assertEqual(mechanism.value_compact, '+442083661179') # Test rejection of a phone type mechanism to non-phone value with self.assertRaises(UserError): mechanism.value = 'notaphone@example.com' mechanism.save() transaction.rollback() # Test rejection of invalid phone number creation with self.assertRaises(UserError): mechanism = create('phone', 'alsonotaphone@example.com') transaction.rollback() # Test acceptance of a non-phone value when type is non-phone mechanism = create('email', 'name@example.com') @with_transaction() def test_set_contact_mechanism(self): "Test set_contact_mechanism" pool = Pool() Party = pool.get('party.party') party = Party(email='test@example.com') party.save() self.assertEqual(party.email, 'test@example.com') @with_transaction() def test_set_contact_mechanism_with_value(self): "Test set_contact_mechanism" pool = Pool() Party = pool.get('party.party') party = Party(email='foo@example.com') party.save() party.email = 'bar@example.com' with self.assertRaises(AccessError): party.save() @with_transaction() def test_contact_mechanism_get_no_usage(self): "Test contact_mechanism_get with no usage" pool = Pool() Party = pool.get('party.party') ContactMechanism = pool.get('party.contact_mechanism') party, = Party.create([{}]) contact1, contact2 = ContactMechanism.create([{ 'party': party.id, 'sequence': 1, 'type': 'email', 'value': 'test1@example.com', }, { 'party': party.id, 'sequence': 2, 'type': 'email', 'value': 'test2@example.com', }]) contact = party.contact_mechanism_get('email') self.assertEqual(contact, contact1) @with_transaction() def test_contact_mechanism_get_many_types(self): "Test contact_mechanism_get with many types" pool = Pool() Party = pool.get('party.party') ContactMechanism = pool.get('party.contact_mechanism') party, = Party.create([{}]) contact1, contact2 = ContactMechanism.create([{ 'party': party.id, 'sequence': 1, 'type': 'other', 'value': 'test', }, { 'party': party.id, 'sequence': 2, 'type': 'email', 'value': 'test2@example.com', }]) contact = party.contact_mechanism_get({'email', 'phone'}) self.assertEqual(contact, contact2) @with_transaction() def test_contact_mechanism_get_no_contact_mechanism(self): "Test contact_mechanism_get with no contact mechanism" pool = Pool() Party = pool.get('party.party') party, = Party.create([{}]) contact = party.contact_mechanism_get() self.assertEqual(contact, None) @with_transaction() def test_contact_mechanism_get_no_type(self): "Test contact_mechanism_get with no type" pool = Pool() Party = pool.get('party.party') ContactMechanism = pool.get('party.contact_mechanism') party, = Party.create([{}]) ContactMechanism.create([{ 'party': party.id, 'type': 'email', 'value': 'test1@example.com', }]) contact = party.contact_mechanism_get('phone') self.assertEqual(contact, None) @with_transaction() def test_contact_mechanism_get_any_type(self): "Test contact_mechanism_get with any type" pool = Pool() Party = pool.get('party.party') ContactMechanism = pool.get('party.contact_mechanism') party, = Party.create([{}]) email1, = ContactMechanism.create([{ 'party': party.id, 'type': 'email', 'value': 'test1@example.com', }]) contact = party.contact_mechanism_get() self.assertEqual(contact, email1) @with_transaction() def test_contact_mechanism_get_inactive(self): "Test contact_mechanism_get with inactive" pool = Pool() Party = pool.get('party.party') ContactMechanism = pool.get('party.contact_mechanism') party, = Party.create([{}]) contact1, contact2 = ContactMechanism.create([{ 'party': party.id, 'sequence': 1, 'type': 'email', 'value': 'test1@example.com', 'active': False, }, { 'party': party.id, 'sequence': 2, 'type': 'email', 'value': 'test2@example.com', 'active': True, }]) contact = party.contact_mechanism_get() self.assertEqual(contact, contact2) @with_transaction() def test_contact_mechanism_get_usage(self): "Test contact_mechanism_get with usage" pool = Pool() Party = pool.get('party.party') ContactMechanism = pool.get('party.contact_mechanism') party, = Party.create([{}]) contact1, contact2 = ContactMechanism.create([{ 'party': party.id, 'sequence': 1, 'type': 'email', 'value': 'test1@example.com', 'name': None, }, { 'party': party.id, 'sequence': 2, 'type': 'email', 'value': 'test2@example.com', 'name': 'email', }]) contact = party.contact_mechanism_get(usage='name') self.assertEqual(contact, contact2) @with_transaction() def test_tax_identifier_types(self): "Ensure tax identifier types are in identifier types" pool = Pool() Party = pool.get('party.party') identifiers = dict(IDENTIFIER_TYPES).keys() tax_identifiers = set(Party.tax_identifier_types()) self.assertLessEqual(tax_identifiers, identifiers) @with_transaction() def test_identifier_vat_types(self): "Ensure VAT identifiers are identifier types" identifiers = dict(IDENTIFIER_TYPES).keys() vat_identifiers = set(map(replace_vat, IDENTIFIER_VAT)) self.assertLessEqual(vat_identifiers, identifiers) @with_transaction() def test_party_distance(self): "Test party distance" pool = Pool() Party = pool.get('party.party') A, B, = Party.create([{ 'name': 'A', }, { 'name': 'B', }]) parties = Party.search([]) self.assertEqual([p.distance for p in parties], [None] * 2) with Transaction().set_context(related_party=A.id): parties = Party.search([]) self.assertEqual( [(p.name, p.distance) for p in parties], [('A', 0), ('B', None)]) del ModuleTestCase